diff --git a/package.json b/package.json index 5310e2a..fc863ac 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@vueform/toggle": "^2.1.4", "@vueuse/core": "^14.2.1", "@walletconnect/core": "^2.23.5", - "cashconnect": "^0.0.20", + "cashconnect": "0.0.25", "chota": "^0.9.2", "electron-store": "^11.0.2", "mainnet-js": "^3.1.6", diff --git a/src/components/cashconnect/CCSessions.vue b/src/components/cashconnect/CCSessions.vue index 17bc344..3af5978 100644 --- a/src/components/cashconnect/CCSessions.vue +++ b/src/components/cashconnect/CCSessions.vue @@ -58,7 +58,7 @@
{{ session.peer.metadata.description }}
-
+
diff --git a/src/components/connectDapp.vue b/src/components/connectDapp.vue index c46e2a7..d5406e0 100644 --- a/src/components/connectDapp.vue +++ b/src/components/connectDapp.vue @@ -24,9 +24,7 @@ const { _wallet } = storeToRefs(store); const walletconnectStore = useWalletconnectStore(_wallet as Ref) const web3wallet = walletconnectStore.web3wallet - const isHDWallet = settingsStore.getWalletType(store.activeWalletName) === 'hd'; - const hasCashConnect = !isHDWallet && !!(_wallet.value as Wallet | null)?.privateKey; - const cashconnectStore = hasCashConnect ? useCashconnectStore(_wallet as Ref) : undefined; + const cashconnectStore = useCashconnectStore(_wallet as Ref); // Props. const props = defineProps<{ @@ -71,7 +69,7 @@ try { // Promise will wait for state indicating whether WC and CC are initialized const { isWcAndCcInitialized } = storeToRefs(store); - await waitForInitialized(isWcAndCcInitialized); + await waitForInitialized(isWcAndCcInitialized); // If the URI begins with "wc:" (walletconnect)... if(dappUri.startsWith('wc:')) { @@ -80,9 +78,6 @@ // Otherwise, if the URI begins with "cc:" (cashconnect)... else if (dappUri.startsWith('cc:')) { - if (!hasCashConnect) { - throw new Error(t('dapp.errors.cashConnectNotSupported')); - } await cashconnectRef.value?.connectDappUriInput(dappUri); } @@ -140,7 +135,7 @@ - +
diff --git a/src/components/settings/addWallet.vue b/src/components/settings/addWallet.vue index f070d91..ec638b8 100644 --- a/src/components/settings/addWallet.vue +++ b/src/components/settings/addWallet.vue @@ -124,9 +124,6 @@ {{ walletType === 'hd' ? t('onboarding.walletType.hdDescription') : t('onboarding.walletType.singleDescription') }}
-
- {{ t('addWallet.hdNote') }} -
{{ t('addWallet.createNew.hint') }} diff --git a/src/components/walletOnboarding.vue b/src/components/walletOnboarding.vue index c556c4b..6ee2601 100644 --- a/src/components/walletOnboarding.vue +++ b/src/components/walletOnboarding.vue @@ -175,9 +175,6 @@
{{ walletType === 'hd' ? t('onboarding.walletType.hdDescription') : t('onboarding.walletType.singleDescription') }}
-
- {{ t('onboarding.walletType.hdNote') }} -
{{ t('onboarding.create.seedPhraseNote') }} diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 52b1c95..9409f83 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -98,8 +98,7 @@ "single": "Single address", "hdDescription": "Uses a new address for each transaction. Better privacy, but requires selecting an address when connecting to dApps.", "singleDescription": "Uses one fixed address for all transactions. All assets in one place, but less private.", - "singleAddressNote": "Note: Only the first address from your seed phrase will be used. If you've used this seed with other wallets, funds on other addresses won't appear.", - "hdNote": "Note: CashConnect is currently not supported with HD wallets." + "singleAddressNote": "Note: Only the first address from your seed phrase will be used. If you've used this seed with other wallets, funds on other addresses won't appear." }, "derivationPath": { "label": "Derivation path:", @@ -349,8 +348,7 @@ "connectButton": "Connect New dApp", "errors": { "invalidUri": "Invalid WalletConnect or CashConnect URI", - "notValidUri": "Not a valid WalletConnect or CashConnect URI", - "cashConnectNotSupported": "CashConnect is currently not supported with HD wallets" + "notValidUri": "Not a valid WalletConnect or CashConnect URI" } }, "history": { @@ -473,7 +471,6 @@ "bitcoindotcom": "Bitcoin.com wallet" }, "singleAddressNote": "Note: Only the first address from your seed phrase will be used. If you've used this seed with other wallets, funds on other addresses won't appear.", - "hdNote": "Note: CashConnect is currently not supported with HD wallets.", "importButton": "Import" }, "walletsOverview": { @@ -716,7 +713,13 @@ "notifications": { "sessionApproved": "Session approved", "successfullySignedTransaction": "Successfully signed transaction", - "networkMismatch": "This Dapp requires wallet to be on {network}. Please navigate to settings and change network to use this Dapp." + "networkMismatch": "This Dapp requires wallet to be on {network}. Please navigate to settings and change network to use this Dapp.", + "notStarted": "CashConnect is not started.", + "privateKeyNotFound": "Private key could not be found for address: {address}", + "failedToConvertCashAddr": "Failed to convert CashAddr to Locking Bytecode", + "invalidDerivationPath": "Invalid derivation path for CashConnect: expected at least 4 components, got {length}.", + "failedToDeriveHdNode": "Failed to derive HDNode from seed", + "walletTypeNotSupported": "WalletType not supported: Must be a Single Address (WIF) Wallet or HDWallet." } }, "walletConnect": { diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json index b3d5122..1e48b0b 100644 --- a/src/i18n/locales/es.json +++ b/src/i18n/locales/es.json @@ -98,8 +98,7 @@ "single": "Dirección única", "hdDescription": "Usa una nueva dirección para cada transacción. Mayor privacidad, pero requiere seleccionar una dirección al conectarse a dApps.", "singleDescription": "Usa una dirección fija para todas las transacciones. Todos los activos en un solo lugar, pero menos privado.", - "singleAddressNote": "Nota: Solo se usará la primera dirección de tu frase semilla. Si has usado esta semilla con otras billeteras, los fondos en otras direcciones no aparecerán.", - "hdNote": "Nota: CashConnect actualmente no es compatible con billeteras HD." + "singleAddressNote": "Nota: Solo se usará la primera dirección de tu frase semilla. Si has usado esta semilla con otras billeteras, los fondos en otras direcciones no aparecerán." }, "derivationPath": { "label": "Ruta de derivación:", @@ -349,8 +348,7 @@ "connectButton": "Conectar nueva dApp", "errors": { "invalidUri": "URI de WalletConnect o CashConnect inválida", - "notValidUri": "No es una URI válida de WalletConnect o CashConnect", - "cashConnectNotSupported": "CashConnect actualmente no es compatible con billeteras HD" + "notValidUri": "No es una URI válida de WalletConnect o CashConnect" } }, "history": { @@ -473,7 +471,6 @@ "bitcoindotcom": "Billetera Bitcoin.com" }, "singleAddressNote": "Nota: Solo se usará la primera dirección de tu frase semilla. Si has usado esta semilla con otras billeteras, los fondos en otras direcciones no aparecerán.", - "hdNote": "Nota: CashConnect actualmente no es compatible con billeteras HD.", "importButton": "Importar" }, "walletsOverview": { @@ -716,7 +713,13 @@ "notifications": { "sessionApproved": "Sesión aprobada", "successfullySignedTransaction": "Transacción firmada exitosamente", - "networkMismatch": "Esta dApp requiere que la billetera esté en {network}. Por favor navega a configuración y cambia la red para usar esta dApp." + "networkMismatch": "Esta dApp requiere que la billetera esté en {network}. Por favor navega a configuración y cambia la red para usar esta dApp.", + "notStarted": "CashConnect no está iniciado.", + "privateKeyNotFound": "No se pudo encontrar la clave privada para la dirección: {address}", + "failedToConvertCashAddr": "Error al convertir CashAddr a Locking Bytecode", + "invalidDerivationPath": "Ruta de derivación inválida para CashConnect: se esperaban al menos 4 componentes, se recibieron {length}.", + "failedToDeriveHdNode": "Error al derivar el HDNode a partir de la semilla", + "walletTypeNotSupported": "Tipo de billetera no compatible: debe ser una billetera de dirección única (WIF) o una HDWallet." } }, "walletConnect": { diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index ba975bd..03c5913 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -98,8 +98,7 @@ "single": "Adresse unique", "hdDescription": "Utilise une nouvelle adresse pour chaque transaction. Meilleure confidentialité, mais nécessite de sélectionner une adresse lors de la connexion aux dApps.", "singleDescription": "Utilise une seule adresse fixe pour toutes les transactions. Tous les actifs au même endroit, mais moins privé.", - "singleAddressNote": "Remarque : Seule la première adresse de votre phrase de récupération sera utilisée. Si vous avez utilisé cette phrase avec d'autres portefeuilles, les fonds sur d'autres adresses n'apparaîtront pas.", - "hdNote": "Remarque : CashConnect n'est actuellement pas pris en charge avec les portefeuilles HD." + "singleAddressNote": "Remarque : Seule la première adresse de votre phrase de récupération sera utilisée. Si vous avez utilisé cette phrase avec d'autres portefeuilles, les fonds sur d'autres adresses n'apparaîtront pas." }, "derivationPath": { "label": "Chemin de dérivation :", @@ -349,8 +348,7 @@ "connectButton": "Connecter nouvelle dApp", "errors": { "invalidUri": "URI WalletConnect ou CashConnect invalide", - "notValidUri": "Ce n'est pas une URI WalletConnect ou CashConnect valide", - "cashConnectNotSupported": "CashConnect n'est actuellement pas pris en charge avec les portefeuilles HD" + "notValidUri": "Ce n'est pas une URI WalletConnect ou CashConnect valide" } }, "history": { @@ -473,7 +471,6 @@ "bitcoindotcom": "Portefeuille Bitcoin.com" }, "singleAddressNote": "Remarque : Seule la première adresse de votre phrase de récupération sera utilisée. Si vous avez utilisé cette phrase avec d'autres portefeuilles, les fonds sur d'autres adresses n'apparaîtront pas.", - "hdNote": "Remarque : CashConnect n'est actuellement pas pris en charge avec les portefeuilles HD.", "importButton": "Importer" }, "walletsOverview": { @@ -716,7 +713,13 @@ "notifications": { "sessionApproved": "Session approuvée", "successfullySignedTransaction": "Transaction signée avec succès", - "networkMismatch": "Cette dApp nécessite que le portefeuille soit sur {network}. Veuillez aller dans les paramètres et changer de réseau pour utiliser cette dApp." + "networkMismatch": "Cette dApp nécessite que le portefeuille soit sur {network}. Veuillez aller dans les paramètres et changer de réseau pour utiliser cette dApp.", + "notStarted": "CashConnect n'est pas démarré.", + "privateKeyNotFound": "La clé privée est introuvable pour l'adresse : {address}", + "failedToConvertCashAddr": "Échec de la conversion de CashAddr en Locking Bytecode", + "invalidDerivationPath": "Chemin de dérivation invalide pour CashConnect : au moins 4 composants attendus, {length} reçu(s).", + "failedToDeriveHdNode": "Échec de la dérivation du HDNode à partir de la seed", + "walletTypeNotSupported": "Type de portefeuille non pris en charge : doit être un portefeuille à adresse unique (WIF) ou un HDWallet." } }, "walletConnect": { diff --git a/src/stores/cashconnectStore.ts b/src/stores/cashconnectStore.ts index 0f1d3e9..d7cd5e4 100644 --- a/src/stores/cashconnectStore.ts +++ b/src/stores/cashconnectStore.ts @@ -8,7 +8,7 @@ import CCSignTransactionDialogVue from "src/components/cashconnect/CCSignTransac import CCErrorDialogVue from "src/components/cashconnect/CCErrorDialog.vue"; // Import MainnetJs and CashConnect -import { convert, type Wallet } from "mainnet-js"; +import { convert } from "mainnet-js"; import type { WalletType } from "src/interfaces/interfaces" import { i18n } from 'src/boot/i18n' const { t } = i18n.global @@ -27,6 +27,9 @@ import { binToHex, hexToBin, cashAddressToLockingBytecode, + deriveHdPath, + deriveSeedFromBip39Mnemonic, + deriveHdPrivateNodeFromSeed, walletTemplateP2pkhNonHd, } from "@bitauth/libauth"; import { useSettingsStore } from 'src/stores/settingsStore'; @@ -39,10 +42,6 @@ const settingsStore = useSettingsStore() // Passing in a Ref so it remains reactive (like when changing wallets) export const useCashconnectStore = (wallet: Ref) => { const store = defineStore("cashconnectStore", () => { - - // Store a state variable to make sure we don't call "start" more than once. - const isStarted = ref(false); - // Auto-approve the following RPC methods. // NOTE: We hard-code these for now, but they could be customized on a per-Dapp basis in the future. const autoApprovedMethods = [ @@ -57,10 +56,22 @@ export const useCashconnectStore = (wallet: Ref) => { const sessions = ref>({}); // The CashConnect Wallet instance. - const cashConnectWallet = ref( - new CashConnectWallet( - // The master private key. - (wallet.value as Wallet).privateKey, + const cashConnectWallet = ref(); + + async function start() { + // Make sure we don't start CC more than once. + // Otherwise, we'll register multiple handlers and end up with multiple dialogs. + if (cashConnectWallet.value) { + return; + } + + // Get the Master Private Key to use for CashConnect. + const masterPrivateKey = getMasterPrivateKeyForWallet(wallet.value); + + // Instantiate CashConnect. + cashConnectWallet.value = new CashConnectWallet( + // The master private key for use with CashConnect. + masterPrivateKey, // Project ID. walletConnectProjectId, // Metadata. @@ -86,21 +97,36 @@ export const useCashconnectStore = (wallet: Ref) => { }, } ) - ); - - async function start() { - // Make sure we don't start CC more than once. - // Otherwise, we'll register multiple handlers and end up with multiple dialogs. - if (isStarted.value) return; // Start CashConnect (WC Core) service. await cashConnectWallet.value.start(); + } - // Set our state variable so we don't start it again when switching wallets. - isStarted.value = true; + async function stop() { + // If already stopped, do nothing. + if(!cashConnectWallet.value) { + return; + } + + // NOTE: This is a bit of a work-around. + // When Cashonize resets wallet state, it does a fire-and-forget. + // This causes a race-condition whereby CashConnect hasn't finished closing connections yet. + // And the `start()` call thinks that the existing instance still exists. + const cashConnectPrevInstance = cashConnectWallet.value; + cashConnectWallet.value = undefined; + + // Stop the previous instance. + await cashConnectPrevInstance.stop(); + + // Disconnect all sessions and stop the previous instance. + await cashConnectPrevInstance.disconnectAllSessions(); } async function pair(wcUri: string) { + if(!cashConnectWallet.value) { + throw new Error(t('cashConnect.notifications.notStarted')); + } + try { // Pair with the service. await cashConnectWallet.value.pair(wcUri); @@ -109,12 +135,19 @@ export const useCashconnectStore = (wallet: Ref) => { } } + async function disconnectSession(topicId: string) { + if(!cashConnectWallet.value) { + return; + } + + await cashConnectWallet.value.disconnectSession(topicId); + } + //----------------------------------------------------------------------------- // Session Hooks //----------------------------------------------------------------------------- - // eslint-disable-next-line @typescript-eslint/require-await - async function onSessionsUpdated( + function onSessionsUpdated( updatedSessions: Record ) { sessions.value = updatedSessions; @@ -165,8 +198,7 @@ export const useCashconnectStore = (wallet: Ref) => { }); } - // eslint-disable-next-line @typescript-eslint/require-await - async function onSessionDelete() { + function onSessionDelete() { console.log("Session deleted"); } @@ -270,17 +302,30 @@ export const useCashconnectStore = (wallet: Ref) => { console.error('Failed to reconnect:', error); } } - - const utxos = await wallet.value.getUtxos(); - const lockingBytecode = cashAddressToLockingBytecode(wallet.value.getDepositAddress()); - if (typeof lockingBytecode === "string") { - throw new Error("Failed to convert CashAddr to Locking Bytecode"); - } + // Get the UTXOs from the wallet. + // NOTE: For Mainnet, we need to marry them up with their Private Key later using the wallet's "walletCache". + const utxos = await wallet.value.getUtxos(); const transformed = utxos.map((utxo) => { - let token: Output["token"] | undefined = undefined; + // Get the Wallet's Internal Information about this address (we need the Private Key for signing). + const walletUTXO = wallet.value.walletCache.get(utxo.address); + + // If the Private Key cannot be retrieved, throw an error. + if(!walletUTXO || !('privateKey' in walletUTXO)) { + throw new Error( + t('cashConnect.notifications.privateKeyNotFound', { address: utxo.address }) + ); + } + + // Convert the Address of this UTXO to Locking Bytecode. + const lockingBytecode = cashAddressToLockingBytecode(utxo.address); + if (typeof lockingBytecode === "string") { + throw new Error(t('cashConnect.notifications.failedToConvertCashAddr')); + } + // If this UTXO has a token, include it. + let token: Output["token"] | undefined = undefined; if (utxo.token) { token = { amount: BigInt(utxo.token.amount), @@ -295,6 +340,7 @@ export const useCashconnectStore = (wallet: Ref) => { } } + // Return the LibAuth Template. return { outpointTransactionHash: hexToBin(utxo.txid), outpointIndex: utxo.vout, @@ -307,7 +353,7 @@ export const useCashconnectStore = (wallet: Ref) => { data: { keys: { privateKeys: { - key: (wallet.value as Wallet).privateKey, + key: walletUTXO.privateKey, }, }, }, @@ -319,20 +365,85 @@ export const useCashconnectStore = (wallet: Ref) => { return transformed; } - // eslint-disable-next-line @typescript-eslint/require-await - async function getChangeTemplate() { + function getChangeTemplate() { + // Get the latest deposit address. + const changeAddress = wallet.value.getChangeAddress(); + + // Get the Private Key for this Change Address. + const walletUTXO = wallet.value.walletCache.get(changeAddress); + + // If the Private Key cannot be retrieved, throw an error. + if(!walletUTXO || !walletUTXO.privateKey) { + throw new Error( + t('cashConnect.notifications.privateKeyNotFound', { address: changeAddress }) + ); + } + + // Return the Libauth Change Template. return { template: walletTemplateP2pkhNonHd, data: { keys: { privateKeys: { - key: (wallet.value as Wallet).privateKey, + key: walletUTXO.privateKey, }, }, }, }; } + //----------------------------------------------------------------------------- + // Utils + //----------------------------------------------------------------------------- + + function derivationPathToCashConnectPath(derivationPath: string, purpose = 5001) { + // Replace the "purpose" in the derivation path with "5001" (CashConnect). + const parts = derivationPath.split('/'); + if (parts.length < 4) { + throw new Error( + t('cashConnect.notifications.invalidDerivationPath', { length: parts.length }) + ) + } + parts[1] = `${purpose}'`; + return parts.join('/'); + } + + function getMasterPrivateKeyForWallet(wallet: WalletType) { + // If this is a single address (WIF) wallet, we just use the WIF's Private Key. + if('privateKey' in wallet) { + return wallet.privateKey; + } + + // If this is a HD Wallet, we use the Wallet's Derivation Path, but replace the purpose with 5001 (CashConnect). + if('mnemonic' in wallet) { + // Derive the seed and HDNode from the Wallet's mnemonic. + const seed = deriveSeedFromBip39Mnemonic(wallet.mnemonic); + const hdNode = deriveHdPrivateNodeFromSeed(seed); + if(!hdNode) { + throw new Error( + t('cashConnect.notifications.failedToDeriveHdNode') + ); + } + + // Take the existing derivation, but set the 'purpose' to 5001 (CashConnectV1). + // NOTE: We put CashConnect on its own derivation to improve security and keep it detached from other Wallet UTXOs. + // I.e. We deliberately keep CashConnect in the dark about the top-level mnemonic to enhance security. + // NOTE: It is important we don't make assumptions (i.e. hard-code the path) here: + // If we do this, we could get conflicting CashConnect instances due to the "account" part of the standard derivation paths. + const cashConnectPath = derivationPathToCashConnectPath(wallet.derivation); + + // Derive the master key for CashConnect. + const masterKeyNode = deriveHdPath(hdNode, cashConnectPath); + + // Return the master private key. + return masterKeyNode.privateKey; + } + + throw new Error( + t('cashConnect.notifications.walletTypeNotSupported') + ); + } + //----------------------------------------------------------------------------- // Expose //----------------------------------------------------------------------------- @@ -340,7 +451,9 @@ export const useCashconnectStore = (wallet: Ref) => { return { // Methods start, + stop, pair, + disconnectSession, // Properties cashConnectWallet, diff --git a/src/stores/store.ts b/src/stores/store.ts index 483d31e..d0bc5d5 100644 --- a/src/stores/store.ts +++ b/src/stores/store.ts @@ -87,7 +87,7 @@ export const useStore = defineStore('store', () => { const latestGithubRelease = ref(undefined as undefined | string); // Computed properties - const network = computed(() => wallet.value.network == NetworkType.Mainnet ? "mainnet" : "chipnet") + const network = computed(() => wallet.value.network == NetworkType.Mainnet ? "mainnet" : "chipnet") const explorerUrl = computed(() => network.value == "mainnet" ? settingsStore.explorerMainnet : settingsStore.explorerChipnet); // The wallet computed property, throws if it were to be accessed when _wallet is null @@ -172,7 +172,7 @@ export const useStore = defineStore('store', () => { } // Note: browser forward button won't work correctly with this implementation. - // popstate can't distinguish back from forward, so forward acts as another back. + // popstate can't distinguish back from forward, so forward acts as another back. addEventListener('popstate', () => { if (viewStack.length <= 1) return; viewStack.pop(); @@ -191,12 +191,12 @@ export const useStore = defineStore('store', () => { if(newWallet.network == NetworkType.Mainnet){ const connectionMainnet = new Connection("mainnet", `wss://${settingsStore.electrumServerMainnet}:50004`) // @ts-ignore currently no other way to set a specific provider - newWallet.provider = connectionMainnet.networkProvider as ElectrumNetworkProvider + newWallet.provider = connectionMainnet.networkProvider as ElectrumNetworkProvider } - if(newWallet.network == NetworkType.Testnet){ + if(newWallet.network == NetworkType.Testnet){ const connectionChipnet = new Connection("testnet", `wss://${settingsStore.electrumServerChipnet}:50004`) // @ts-ignore currently no other way to set a specific provider - newWallet.provider = connectionChipnet.networkProvider as ElectrumNetworkProvider + newWallet.provider = connectionChipnet.networkProvider as ElectrumNetworkProvider } _wallet.value?.stop().catch(() => {}); _wallet.value = newWallet; @@ -301,7 +301,7 @@ export const useStore = defineStore('store', () => { } } catch (error) { displayAndLogError(error); - } + } } async function setUpWalletSubscriptions(){ @@ -440,7 +440,7 @@ export const useStore = defineStore('store', () => { // TODO: investigate if disconnecting session this way is properly working // Call disconnects as fire-and-forget promises networkChangeCallbacks.forEach((callback) => void callback()); - // clear the networkChangeCallbacks before initialising newWallet + // clear the networkChangeCallbacks before initialising newWallet networkChangeCallbacks = [] // cancel active listeners @@ -577,12 +577,6 @@ export const useStore = defineStore('store', () => { } async function initializeCashConnect() { - // CashConnect requires a single-address wallet with a private key - const walletPrivateKey = (_wallet.value as Wallet | null)?.privateKey; - if (settingsStore.getWalletType(activeWalletName.value) === 'hd' || !walletPrivateKey) { - isCcInitialized.value = true; - return; - } try{ // Initialize CashConnect. const cashconnectWallet = useCashconnectStore(_wallet as Ref); @@ -594,7 +588,7 @@ export const useStore = defineStore('store', () => { // Setup network change callback to disconnect all sessions. // NOTE: This must be wrapped, otherwise we don't have the appropriate context. networkChangeCallbacks.push(async () => { - await cashconnectWallet.cashConnectWallet.disconnectAllSessions(); + await cashconnectWallet.stop(); }); // Monitor the wallet for balance changes and notify CashConnect to refresh wallet state. @@ -604,7 +598,9 @@ export const useStore = defineStore('store', () => { // Invoke wallet state has changed so that CashConnect can retrieve fresh UTXOs (and token balances). // fire-and-forget promise - void cashconnectWallet.cashConnectWallet.walletStateHasChanged(chainIdFormatted); + if(cashconnectWallet.cashConnectWallet) { + void cashconnectWallet.cashConnectWallet.walletStateHasChanged(chainIdFormatted); + } }); } catch (error) { console.error("Error initializing CashConnect:", error); @@ -729,7 +725,7 @@ export const useStore = defineStore('store', () => { const registries = await fetchNftMetadataFromIndexer(category, commitment, bcmrIndexer.value, bcmrRegistries.value); bcmrRegistries.value = registries; } - + function parseNftCommitment( categoryId: string, @@ -817,7 +813,7 @@ export const useStore = defineStore('store', () => { try { const response = await fetch('https://api.github.com/repos/cashonize/cashonize-wallet/releases/latest'); if (!response.ok) throw new Error('Network response was not ok'); - + const releaseData = await response.json(); // Extract the version tag (e.g. 'v0.2.4') latestGithubRelease.value = releaseData.tag_name; diff --git a/yarn.lock b/yarn.lock index 9c53656..959f3f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2834,10 +2834,10 @@ caniuse-lite@^1.0.30001766: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001767.tgz#0279c498e862efb067938bba0a0aabafe8d0b730" integrity sha512-34+zUAMhSH+r+9eKmYG+k2Rpt8XttfE4yXAjoZvkAPs15xcYQhyBYdalJ65BzivAvGRMViEjy6oKr/S91loekQ== -cashconnect@^0.0.20: - version "0.0.20" - resolved "https://registry.yarnpkg.com/cashconnect/-/cashconnect-0.0.20.tgz#067c6ac36ff9da1f0598326faef9e0b641f71180" - integrity sha512-tdyp+YHuR3qRAt7QzcprB4a7wsZH2alvYDJ5Z4e6IhJaAOteJ5jcIWfB79ZeLLoKNS1WWmRc2shdKuy3ilf96w== +cashconnect@0.0.25: + version "0.0.25" + resolved "https://registry.yarnpkg.com/cashconnect/-/cashconnect-0.0.25.tgz#d11a494b3415a9614c33f5893a4a97a3b94e6db1" + integrity sha512-D/LIo89iNysaMprGr/vWTciDdWny4F6euhbDLb87QLbUFkDWZOjI5YJ2a62hu0gEmexH9/11AR5ZBvyl5eQD6g== dependencies: "@bitauth/libauth" "^3.1.0-next.2" "@reown/walletkit" "^1.2.10"