From 7ea4844761482df4539a03dab9dc5a3981c2de11 Mon Sep 17 00:00:00 2001
From: mtyszczak <mateusz.tyszczak@gmail.com>
Date: Tue, 11 Mar 2025 16:21:59 +0100
Subject: [PATCH] Add keychain, peakvault and metamask signing functionality

---
 src/App.vue                                   | 10 ++-
 src/components/MetamaskExample.vue            | 75 -------------------
 src/components/onboarding/SelectWallet.vue    | 21 +++++-
 .../wallets/metamask/MetamaskConnect.vue      | 20 ++---
 src/components/utilcards/MemoEncryptCard.vue  | 52 +++++++++++--
 .../utilcards/SignTransactionCard.vue         | 35 ++++++++-
 src/stores/metamask.store.ts                  |  2 +-
 src/stores/settings.store.ts                  |  1 +
 src/stores/wallet.store.ts                    | 45 +++++++++++
 src/utils/wallet/abstraction.ts               |  7 ++
 src/utils/wallet/keychain/index.ts            | 62 +++++++++++++++
 src/utils/wallet/metamask/metamask.ts         | 22 +++++-
 src/utils/wallet/metamask/snap.ts             |  4 +-
 src/utils/wallet/peakvault/index.ts           | 31 ++++++++
 14 files changed, 279 insertions(+), 108 deletions(-)
 delete mode 100644 src/components/MetamaskExample.vue
 create mode 100644 src/stores/wallet.store.ts
 create mode 100644 src/utils/wallet/abstraction.ts
 create mode 100644 src/utils/wallet/keychain/index.ts
 create mode 100644 src/utils/wallet/peakvault/index.ts

diff --git a/src/App.vue b/src/App.vue
index c4e788b..32f2705 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,22 +1,28 @@
 <script setup lang="ts">
 import { ref, onMounted, defineAsyncComponent } from 'vue';
 import { useSettingsStore, UsedWallet } from '@/stores/settings.store';
+import { useWalletStore } from '@/stores/wallet.store';
 import AppHeader from '@/components/AppHeader.vue';
 
 const WalletOnboarding = defineAsyncComponent(() => import('@/components/onboarding/index'));
 
 const hasUser = ref(true);
 const settingsStore = useSettingsStore();
+const walletStore = useWalletStore();
 onMounted(() => {
   settingsStore.loadSettings();
   hasUser.value = settingsStore.settings.account !== undefined;
+  if (hasUser.value)
+    void walletStore.createWalletFor(settingsStore.settings);
 });
 const complete = (data: { account: string; wallet: UsedWallet }) => {
   hasUser.value = true;
-  settingsStore.setSettings({
+  const settings = {
     account: data.account,
     wallet: data.wallet
-  });
+  };
+  settingsStore.setSettings(settings);
+  void walletStore.createWalletFor(settings);
 };
 </script>
 
diff --git a/src/components/MetamaskExample.vue b/src/components/MetamaskExample.vue
deleted file mode 100644
index a1fb975..0000000
--- a/src/components/MetamaskExample.vue
+++ /dev/null
@@ -1,75 +0,0 @@
-<script setup lang="ts">
-import { ref, onMounted, computed } from 'vue';
-import { useMetamaskStore } from '@/stores/metamask.store';
-import { Button } from '@/components/ui/button';
-import { useSettingsStore } from '@/stores/settings.store';
-
-const walletStore = useMetamaskStore();
-
-const metamaskFound = ref(false);
-const isConnected = computed(() => walletStore.isConnected);
-const isFlask = computed(() => walletStore.isFlask);
-const performingOperation = computed(() => walletStore.performingOperation);
-const isInstalled = computed(() => walletStore.isInstalled);
-
-const frontError = ref<undefined | string>();
-const frontResult = ref<undefined | string>();
-
-const safeCall = async(storeFn: (...args: any) => any) => {
-  frontResult.value = undefined;
-  frontError.value = undefined;
-
-  try {
-    frontResult.value = JSON.stringify(await storeFn(), undefined, 2);
-  } catch(error) {
-    frontError.value = error instanceof Error ? error.message : `Unknown error: ${error}`;
-  }
-};
-
-const settingsStore = useSettingsStore();
-
-const connect = safeCall.bind(undefined, () => walletStore.connect());
-const install = safeCall.bind(undefined, () => walletStore.install());
-const call = (method: string, params?: any) => safeCall(() => walletStore.call(method, params));
-const getPublicKeys = () => call('hive_getPublicKeys', { keys: [ { role: 'memo' }, { role: 'posting' }, { role: 'active' }, { role: 'owner' } ] });
-
-// Automatically try to connect on mount (client-side)
-onMounted(() => {
-  walletStore.connect().then(() => metamaskFound.value = true).catch(error => {
-    console.error(error);
-  });
-});
-</script>
-
-<template>
-  <div>
-    <h1>Metamask Example</h1>
-    <p>Has supported extension: {{ metamaskFound }}</p>
-    <p v-if="metamaskFound">Connected: {{ isConnected }}</p>
-    <div v-if="isConnected">
-      <p>isFlask: {{ isFlask }}</p>
-      <p>isInstalled: {{ isInstalled }}</p>
-      <Button :disabled="performingOperation" @click="connect">Reconnect</Button>
-      <Button :disabled="performingOperation" @click="install">{{ isInstalled ? "Reinstall" : "Install" }}</Button>
-      <Button :disabled="performingOperation" @click="getPublicKeys" v-if="isInstalled">getPublicKeys</Button>
-      <Button @click="settingsStore.resetSettings">logout</Button>
-    </div>
-    <div v-else-if="metamaskFound">
-      <Button :disabled="performingOperation" @click="connect">Connect</Button>
-    </div>
-    <div v-if="frontError">
-      <p :style="{ color: 'darkred' }">Error:</p>
-      <code><pre>{{ frontError }}</pre></code>
-    </div>
-    <div v-if="frontResult">
-      <p :style="{ color: 'darkgreen' }">Result:</p>
-      <code><pre>{{ frontResult }}</pre></code>
-    </div>
-  </div>
-</template>
-
-<style scoped>
-pre {
-  text-align: left;
-}
-</style>
diff --git a/src/components/onboarding/SelectWallet.vue b/src/components/onboarding/SelectWallet.vue
index 7aa938b..d4e0362 100644
--- a/src/components/onboarding/SelectWallet.vue
+++ b/src/components/onboarding/SelectWallet.vue
@@ -1,20 +1,33 @@
 <script setup lang="ts">
 import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
 import OnboardingButton from "@/components/onboarding/OnboardingWalletButton.vue";
-import { onMounted, ref } from 'vue';
+import { onMounted, onUnmounted, ref } from 'vue';
 import { useMetamaskStore } from "@/stores/metamask.store";
 import { getWalletIcon, UsedWallet } from "@/stores/settings.store";
 
 const hasMetamask = ref(false);
 const hasKeychain = ref(false);
 const hasPeakVault = ref(false);
+let timeoutId: number;
 
 const metamaskStore = useMetamaskStore();
 
+const checkForWallets = () => {
+  if (!hasMetamask.value)
+    metamaskStore.connect().then(() => hasMetamask.value = true).catch(console.error);
+  if (!hasKeychain.value)
+    hasKeychain.value = "hive_keychain" in window;
+  if (!hasPeakVault.value)
+    hasPeakVault.value = "peakvault" in window;
+};
+
 onMounted(() => {
-  metamaskStore.connect().then(() => hasMetamask.value = true).catch(console.error);
-  hasKeychain.value = "hive_keychain" in window;
-  hasPeakVault.value = "peakvault" in window;
+  timeoutId = setTimeout(() => checkForWallets(), 1500) as unknown as number;
+  checkForWallets();
+});
+
+onUnmounted(() => {
+  clearTimeout(timeoutId);
 });
 
 const emit = defineEmits(["walletSelect"]);
diff --git a/src/components/onboarding/wallets/metamask/MetamaskConnect.vue b/src/components/onboarding/wallets/metamask/MetamaskConnect.vue
index 9fe0b38..9a1d92c 100644
--- a/src/components/onboarding/wallets/metamask/MetamaskConnect.vue
+++ b/src/components/onboarding/wallets/metamask/MetamaskConnect.vue
@@ -11,6 +11,7 @@ import { Combobox, ComboboxAnchor, ComboboxTrigger, ComboboxEmpty, ComboboxGroup
 import { Check, Search } from 'lucide-vue-next';
 import { Separator } from '@/components/ui/separator';
 import PublicKey from '@/components/hive/PublicKey.vue';
+import { getWax } from '@/stores/wax.store';
 
 const emit = defineEmits(["setaccount", "close"]);
 
@@ -46,22 +47,13 @@ const applyPublicKeys = async () => {
 
     metamaskPublicKeys.value = publicKeys;
 
-    const response = await (await fetch("https://api.hive.blog", {
-      method: "POST",
-      body: JSON.stringify({
-        method: "account_by_key_api.get_key_references",
-        jsonrpc:"2.0",
-        id: "1",
-        params: {
-          keys: publicKeys.map((node: { publicKey: string }) => node.publicKey)
-        }
-      })
-    })).json();
+    const wax = await getWax();
 
-    if (response.error)
-      throw new Error(response.error.message);
+    const response = await wax.api.account_by_key_api.get_key_references({
+      keys: publicKeys.map((node: { publicKey: string }) => node.publicKey)
+    });
 
-    accountsMatchingKeys.value = [...new Set(response.result.accounts.flatMap((node: string[]) => node))] as string[];
+    accountsMatchingKeys.value = [...new Set(response.accounts.flatMap((node: string[]) => node))] as string[];
   } catch (error) {
     if (typeof error === "object" && error && "message" in error)
       errorMsg.value = error.message as string;
diff --git a/src/components/utilcards/MemoEncryptCard.vue b/src/components/utilcards/MemoEncryptCard.vue
index a4717ef..0cd93dc 100644
--- a/src/components/utilcards/MemoEncryptCard.vue
+++ b/src/components/utilcards/MemoEncryptCard.vue
@@ -1,13 +1,53 @@
 <script setup lang="ts">
 import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
 import { mdiMessageLockOutline } from '@mdi/js';
-import { ref } from 'vue';
+import { ref, computed } from 'vue';
 import { Textarea } from '@/components/ui/textarea';
 import { Button } from '@/components/ui/button';
 import { Input } from '@/components/ui/input';
 import { Switch } from '@/components/ui/switch';
+import { useWalletStore } from '@/stores/wallet.store';
+import { getWax } from '@/stores/wax.store';
+import { useSettingsStore } from '@/stores/settings.store';
+
+const walletStore = useWalletStore();
+const settingsStore = useSettingsStore();
+
+const hasWallet = computed(() => walletStore.hasWallet);
+const wallet = computed(() => walletStore.wallet);
 
 const isEncrypt = ref(false);
+const encryptForKey = ref('');
+const inputData = ref('');
+const outputData = ref('');
+
+const getMemoKeyForUser = async(user: string) => {
+  const wax = await getWax();
+  const response = await wax.api.database_api.find_accounts({
+    accounts: [user.startsWith('@') ? user.slice(1) : user],
+    delayed_votes_active: true
+  });
+  return response.accounts[0].memo_key;
+}
+
+const useMyMemoKey = async () => {
+  encryptForKey.value = await getMemoKeyForUser(settingsStore.account!);
+}
+
+const encryptOrDecrypt = async () => {
+  if (isEncrypt.value) {
+    let publicKey: string;
+    let accountOrKey = encryptForKey.value;
+    if (accountOrKey.startsWith('STM')) {
+      publicKey = accountOrKey;
+    } else {
+      publicKey = await getMemoKeyForUser(accountOrKey);
+    }
+    outputData.value = await wallet.value!.encrypt(inputData.value, publicKey);
+  } else {
+    outputData.value = await wallet.value!.decrypt(inputData.value);
+  }
+};
 </script>
 
 <template>
@@ -25,13 +65,13 @@ const isEncrypt = ref(false);
         <Switch v-model="isEncrypt" />
         <span>Encrypt</span>
       </div>
-      <Textarea placeholder="Input" class="my-4"/>
-      <Input v-if="isEncrypt" placeholder="Receiver account or public key" class="mt-4"/>
+      <Textarea v-model="inputData" placeholder="Input" class="my-4"/>
+      <Input v-model="encryptForKey" v-if="isEncrypt" placeholder="Receiver account or public key" class="mt-4"/>
       <div class="flex mb-4 underline text-sm" v-if="isEncrypt">
-        <a class="ml-auto mr-1 cursor-pointer" style="color: hsla(var(--foreground) / 70%)" @click="">Use my memo key</a>
+        <a @click="useMyMemoKey" class="ml-auto mr-1 cursor-pointer" style="color: hsla(var(--foreground) / 70%)">Use my memo key</a>
       </div>
-      <Button>{{ isEncrypt ? "Encrypt" : "Decrypt" }}</Button>
-      <Textarea placeholder="Output" copy-enabled class="my-4" disabled/>
+      <Button :disabled="!hasWallet" @click="encryptOrDecrypt">{{ isEncrypt ? "Encrypt" : "Decrypt" }}</Button>
+      <Textarea v-model="outputData" placeholder="Output" copy-enabled class="my-4" disabled/>
     </CardContent>
   </Card>
 </template>
\ No newline at end of file
diff --git a/src/components/utilcards/SignTransactionCard.vue b/src/components/utilcards/SignTransactionCard.vue
index 9b5ae54..aa738da 100644
--- a/src/components/utilcards/SignTransactionCard.vue
+++ b/src/components/utilcards/SignTransactionCard.vue
@@ -1,8 +1,37 @@
 <script setup lang="ts">
 import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
 import { mdiFileSign } from '@mdi/js';
+import { computed, ref } from 'vue';
 import { Textarea } from '@/components/ui/textarea';
 import { Button } from '@/components/ui/button';
+import { useWalletStore } from '@/stores/wallet.store';
+import { getWax } from '@/stores/wax.store';
+import type { TRole } from '@hiveio/wax/vite';
+
+const walletStore = useWalletStore();
+
+const hasWallet = computed(() => walletStore.hasWallet);
+const wallet = computed(() => walletStore.wallet);
+
+const inputData = ref('');
+const outputData = ref('');
+
+const sign = async () => {
+  const wax = await getWax();
+
+  const tx = wax.createTransactionFromJson(inputData.value);
+
+  const authorities = tx.requiredAuthorities;
+  let authorityLevel: TRole = 'posting';
+  if (authorities.owner.size)
+    authorityLevel = 'owner';
+  else if (authorities.active.size)
+    authorityLevel = 'active';
+
+  // TODO: Handle "other" authority
+
+  outputData.value = await wallet.value!.signTransaction(tx, authorityLevel);
+};
 </script>
 
 <template>
@@ -15,11 +44,11 @@ import { Button } from '@/components/ui/button';
       <CardDescription class="mr-4">Use this module to sign the provided transaction</CardDescription>
     </CardHeader>
     <CardContent>
-      <Textarea placeholder="Transaction in API JSON form" class="my-4"/>
+      <Textarea v-model="inputData" placeholder="Transaction in API JSON form" class="my-4"/>
       <div class="my-4 space-x-4">
-        <Button>Sign transaction</Button>
+        <Button :disabled="!hasWallet" @click="sign">Sign transaction</Button>
       </div>
-      <Textarea placeholder="Signed Transaction output" copy-enabled class="my-4" disabled/>
+      <Textarea v-model="outputData" placeholder="Signed Transaction output" copy-enabled class="my-4" disabled/>
     </CardContent>
   </Card>
 </template>
\ No newline at end of file
diff --git a/src/stores/metamask.store.ts b/src/stores/metamask.store.ts
index d3ca2c9..81bc758 100644
--- a/src/stores/metamask.store.ts
+++ b/src/stores/metamask.store.ts
@@ -1,7 +1,7 @@
 import { defineStore } from "pinia"
 import { connectMetamask, type MetamaskWallet } from "../utils/wallet/metamask"
 
-export const useMetamaskStore = defineStore('wallet', {
+export const useMetamaskStore = defineStore('metamask', {
   state: () => ({
     metamask: undefined as undefined | MetamaskWallet,
     performingOperation: false
diff --git a/src/stores/settings.store.ts b/src/stores/settings.store.ts
index 3cae3bb..aa135c2 100644
--- a/src/stores/settings.store.ts
+++ b/src/stores/settings.store.ts
@@ -35,6 +35,7 @@ const settings = {
   wallet: undefined as UsedWallet | undefined,
   account: undefined as string | undefined,
 };
+export type Settings = Required<typeof settings>;
 
 export const useSettingsStore = defineStore('settings', {
   state: () => ({
diff --git a/src/stores/wallet.store.ts b/src/stores/wallet.store.ts
new file mode 100644
index 0000000..1da5f95
--- /dev/null
+++ b/src/stores/wallet.store.ts
@@ -0,0 +1,45 @@
+import { defineStore } from "pinia";
+import type { Wallet } from "@/utils/wallet/abstraction";
+import { type Settings, UsedWallet } from "./settings.store";
+import { useMetamaskStore } from "./metamask.store";
+import { createKeychainWalletFor } from "@/utils/wallet/keychain";
+import { createPeakVaultWalletFor } from "@/utils/wallet/peakvault";
+
+export const useWalletStore = defineStore('wallet', {
+  state: () => ({
+    wallet: undefined as undefined | Wallet
+  }),
+  getters: {
+    hasWallet: state => !!state.wallet,
+  },
+  actions: {
+    async createWalletFor(settings: Settings) {
+      switch(settings.wallet) {
+        case UsedWallet.METAMASK: {
+          const metamaskStore = useMetamaskStore();
+
+          await metamaskStore.connect();
+
+          this.wallet = metamaskStore.metamask;
+
+          break;
+        }
+        case UsedWallet.KEYCHAIN: {
+          this.wallet = createKeychainWalletFor(settings.account!);
+
+          break;
+        }
+        case UsedWallet.PEAKVAULT: {
+          this.wallet = createPeakVaultWalletFor(settings.account!);
+
+          break;
+        }
+        default:
+          throw new Error("Unsupported wallet");
+      }
+    },
+    resetWallet() {
+      this.wallet = undefined;
+    }
+  }
+})
diff --git a/src/utils/wallet/abstraction.ts b/src/utils/wallet/abstraction.ts
new file mode 100644
index 0000000..6addbee
--- /dev/null
+++ b/src/utils/wallet/abstraction.ts
@@ -0,0 +1,7 @@
+import type { TPublicKey, TRole, ITransactionBase } from "@hiveio/wax/vite";
+
+export interface Wallet {
+  signTransaction(transaction: ITransactionBase, role: TRole): Promise<string>;
+  encrypt(buffer: string, recipient: TPublicKey): Promise<string>;
+  decrypt(buffer: string): Promise<string>;
+}
diff --git a/src/utils/wallet/keychain/index.ts b/src/utils/wallet/keychain/index.ts
new file mode 100644
index 0000000..2c42db4
--- /dev/null
+++ b/src/utils/wallet/keychain/index.ts
@@ -0,0 +1,62 @@
+import type { TRole, TPublicKey, TAccountName, ITransactionBase } from "@hiveio/wax/vite";
+import type { Wallet } from "../abstraction";
+
+export const createKeychainWalletFor = (account: TAccountName) => {
+  return new KeychainWallet(account);
+};
+
+export class KeychainWallet implements Wallet {
+  public constructor(
+    private readonly account: TAccountName
+  ) {}
+
+  public async signTransaction(transaction: ITransactionBase, role: TRole): Promise<string> {
+    const response = await new Promise((resolve, reject) => (window as any).hive_keychain.requestSignTx(
+      this.account,
+      JSON.parse(transaction.toLegacyApi()),
+      role,
+      (response: any) => {
+        if (response.error)
+          reject(response);
+        else
+          resolve(response);
+      }
+    )) as any;
+
+    return response.result.signatures;
+  }
+
+  public async encrypt(buffer: string, recipient: TPublicKey): Promise<string> {
+    const response = await new Promise((resolve, reject) => (window as any).hive_keychain.requestEncodeWithKeys(
+      this.account,
+      [recipient],
+      buffer.startsWith("#") ? buffer : `#${buffer}`,
+      "memo",
+      (response: any) => {
+        if (response.error)
+          reject(response);
+        else
+          resolve(response);
+      }
+    )) as any;
+
+    return Object.values(response.result)[0] as string;
+  }
+
+  public async decrypt(buffer: string): Promise<string> {
+    const response = await new Promise((resolve, reject) => (window as any).hive_keychain.requestVerifyKey(
+      this.account,
+      buffer,
+      "memo",
+      (response: any) => {
+        if (response.error)
+          reject(response);
+        else
+          resolve(response);
+      }
+    )) as any;
+
+
+    return response.result;
+  }
+}
diff --git a/src/utils/wallet/metamask/metamask.ts b/src/utils/wallet/metamask/metamask.ts
index 1da6ed0..bfc8e5b 100644
--- a/src/utils/wallet/metamask/metamask.ts
+++ b/src/utils/wallet/metamask/metamask.ts
@@ -1,5 +1,7 @@
 import type { MetaMaskInpageProvider } from "@metamask/providers";
 import { defaultSnapOrigin, defaultSnapVersion, isLocalSnap } from "./snap";
+import type { Wallet } from "../abstraction";
+import type { TPublicKey, TRole, ITransactionBase } from "@hiveio/wax/vite";
 
 export type MetamaskSnapData = {
   permissionName: string;
@@ -9,7 +11,7 @@ export type MetamaskSnapData = {
 };
 export type MetamaskSnapsResponse = Record<string, MetamaskSnapData>;
 
-export class MetamaskWallet {
+export class MetamaskWallet implements Wallet {
   /**
    * Indicates either the snap is installed or not.
    * If you want to install or reinstall the snap, use {@link installSnap}
@@ -33,6 +35,24 @@ export class MetamaskWallet {
     return this.provider.request(params ? { method, params } : { method });
   }
 
+  public async signTransaction(transaction: ITransactionBase, role: TRole) {
+    const response = await this.invokeSnap('hive_signTransaction', { transaction: transaction.toApi(), keys: [{ role }] }) as any;
+
+    return response.signatures[0];
+  }
+
+  public async encrypt(buffer: string, recipient: TPublicKey): Promise<string> {
+    const response = await this.invokeSnap('hive_encrypt', { buffer, firstKey: { role: "memo" as TRole }, secondKey: recipient }) as any;
+
+    return response.buffer;
+  }
+
+  public async decrypt(buffer: string): Promise<string> {
+    const response = await this.invokeSnap('hive_decrypt', { buffer, firstKey: { role: "memo" as TRole } }) as any;
+
+    return response.buffer;
+  }
+
   /**
    * Request the snap to be installed or reinstalled.
    * You can check if snap is installed using {@link isInstalled}
diff --git a/src/utils/wallet/metamask/snap.ts b/src/utils/wallet/metamask/snap.ts
index 8d313df..ffb7a9b 100644
--- a/src/utils/wallet/metamask/snap.ts
+++ b/src/utils/wallet/metamask/snap.ts
@@ -6,9 +6,9 @@
  * don't. Instead, rename `.env.production.dist` to `.env.production` and set the production URL
  * there. Running `yarn build` will automatically use the production environment variables.
  */
-export const defaultSnapOrigin = import.meta.env.SNAP_ORIGIN ?? `npm:@hiveio/metamask-snap`;
+export const defaultSnapOrigin = import.meta.env.SNAP_ORIGIN ?? `npm:@hiveio/metamask-snap`; // local:http://localhost:8080
 
-export const defaultSnapVersion: string | undefined = import.meta.env.SNAP_VERSION ?? '1.0.1';
+export const defaultSnapVersion: string | undefined = import.meta.env.SNAP_VERSION ?? '1.2.1';
 
 /**
  * Check if a snap ID is a local snap ID.
diff --git a/src/utils/wallet/peakvault/index.ts b/src/utils/wallet/peakvault/index.ts
new file mode 100644
index 0000000..98836cb
--- /dev/null
+++ b/src/utils/wallet/peakvault/index.ts
@@ -0,0 +1,31 @@
+
+import type { TRole, TPublicKey, TAccountName, ITransactionBase } from "@hiveio/wax/vite";
+import type { Wallet } from "../abstraction";
+
+export const createPeakVaultWalletFor = (account: TAccountName) => {
+  return new PeakVaultWallet(account);
+};
+
+export class PeakVaultWallet implements Wallet {
+  public constructor(
+    private readonly account: TAccountName
+  ) {}
+
+  public async signTransaction(transaction: ITransactionBase, role: TRole): Promise<string> {
+    const response = await (window as any).peakvault.requestSignTx(this.account, JSON.parse(transaction.toLegacyApi()), role);
+
+    return response.result.signatures[0];
+  }
+
+  public async encrypt(buffer: string, recipient: TPublicKey): Promise<string> {
+    const response = await (window as any).peakvault.requestEncodeWithKeys(this.account, "memo", [recipient], buffer.startsWith("#") ? buffer : `#${buffer}`);
+
+    return response.result[0];
+  }
+
+  public async decrypt(buffer: string): Promise<string> {
+    const response = await (window as any).peakvault.requestDecode(this.account, buffer, "memo");
+
+    return response.result;
+  }
+}
-- 
GitLab