diff --git a/src/components/onboarding/wallets/metamask/MetamaskConnect.vue b/src/components/onboarding/wallets/metamask/MetamaskConnect.vue
index db9d97eaa5dd455d970b091deb1cc9ec46f9aaaf..2f5c780cc59d40a619d0705ae50046f03d7c777c 100644
--- a/src/components/onboarding/wallets/metamask/MetamaskConnect.vue
+++ b/src/components/onboarding/wallets/metamask/MetamaskConnect.vue
@@ -142,9 +142,6 @@ onMounted(() => {
   void connect(false);
 });
 
-const copyContent = (content: string) => {
-  navigator.clipboard.writeText(String(content));
-};
 const generateAccountUpdateTransaction = async(): Promise<string> => {
   const wax = await getWax();
   const tx = await wax.createTransaction();
@@ -251,12 +248,12 @@ const updateAccountName = (value: string | any) => {
               </div>
             </div>
             <div class="flex items-center flex-col">
-              <Button :disabled="isLoading" @click="copyContent(getAuthorityUpdateSigningLink())" variant="outline" size="lg" class="mt-4 px-8 py-4 border-[#FF5C16] border-[1px]">
+              <Button :disabled="isLoading" :copy="getAuthorityUpdateSigningLink" variant="outline" size="lg" class="mt-4 px-8 py-4 border-[#FF5C16] border-[1px]">
                 <span class="text-md font-bold">Copy signing link</span>
               </Button>
               <Separator label="Or" class="mt-8" />
               <div class="flex justify-center mt-4">
-                <Button :disabled="isLoading" @click="generateAccountUpdateTransaction()" variant="outline" size="lg" class="px-8 opacity-[0.9] py-4 border-[#FF5C16] border-[1px]">
+                <Button :disabled="isLoading" :copy="generateAccountUpdateTransaction" variant="outline" size="lg" class="px-8 opacity-[0.9] py-4 border-[#FF5C16] border-[1px]">
                   <span class="text-md font-bold">Copy entire transaction</span>
                 </Button>
               </div>
@@ -277,7 +274,7 @@ const updateAccountName = (value: string | any) => {
               </div>
             </div>
             <div class="flex items-center flex-col">
-              <Button :disabled="isLoading" @click="copyContent(getAccountCreateSigningLink())" variant="outline" size="lg" class="mt-4 px-8 py-4 border-[#FF5C16] border-[1px]">
+              <Button :copy="getAccountCreateSigningLink" :disabled="isLoading" variant="outline" size="lg" class="mt-4 px-8 py-4 border-[#FF5C16] border-[1px]">
                 <span class="text-md font-bold">Copy signing link</span>
               </Button>
             </div>
diff --git a/src/components/ui/button/Button.vue b/src/components/ui/button/Button.vue
index 3ba549bf4422162af85c42cf4750617820cea54d..ba01e17dceb546df0d75194e598e4e3f5fc8db23 100644
--- a/src/components/ui/button/Button.vue
+++ b/src/components/ui/button/Button.vue
@@ -1,9 +1,10 @@
 <script setup lang="ts">
-import type { HTMLAttributes } from 'vue'
+import { ref, type HTMLAttributes } from 'vue'
 import { cn } from '@/lib/utils'
 import { Primitive, type PrimitiveProps } from 'reka-ui'
 import { type ButtonVariants, buttonVariants } from '.'
-import { mdiLoading } from '@mdi/js'
+import { mdiCheck, mdiLoading } from '@mdi/js'
+import { copyText } from '@/utils/copy'
 
 interface Props extends PrimitiveProps {
   variant?: ButtonVariants['variant']
@@ -11,12 +12,31 @@ interface Props extends PrimitiveProps {
   class?: HTMLAttributes['class']
   loading?: boolean
   disabled?: boolean
+  copy?: string | (() => (string | Promise<string>))
 }
 
 const props = withDefaults(defineProps<Props>(), {
   as: 'button',
   loading: false
 })
+
+const copyLoading = ref(false);
+
+const copyBtn = () => {
+  if (!props.copy) return;
+  const text = typeof props.copy === 'function' ? props.copy() : props.copy;
+  if(text instanceof Promise)
+    text.then((text) => {
+      copyText(text);
+    });
+  else
+    copyText(text);
+
+  copyLoading.value = true;
+  setTimeout(() => {
+    copyLoading.value = false;
+  }, 1000);
+};
 </script>
 
 <template>
@@ -25,13 +45,19 @@ const props = withDefaults(defineProps<Props>(), {
     :as-child="asChild"
     :disabled="loading || disabled"
     :class="[ cn(buttonVariants({ variant, size }), props.class)]"
+    @click="copyBtn"
   >
+    <span v-if="copy && copyLoading" class="absolute mx-auto">
+      <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path :style="{
+        fill: !variant || variant === 'default' ? 'hsl(var(--background))' : 'hsl(var(--foreground))'
+      }" :d="mdiCheck"/></svg>
+    </span>
     <span v-if="loading" class="animate-spin absolute mx-auto">
       <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path :style="{
         fill: !variant || variant === 'default' ? 'hsl(var(--background))' : 'hsl(var(--foreground))'
       }" :d="mdiLoading"/></svg>
     </span>
-    <span :style="{ 'visibility': loading ? 'hidden' : 'visible' }" class="inline-flex items-center justify-center gap-2">
+    <span :style="{ 'visibility': loading || copyLoading ? 'hidden' : 'visible' }" class="inline-flex items-center justify-center gap-2">
       <slot/>
     </span>
   </Primitive>
diff --git a/src/components/ui/copybutton/Button.vue b/src/components/ui/copybutton/Button.vue
index 6694833d4dfc5257aae85a0608e77e29f9710e74..4aa3c9306779837c5beebeb9b1f66f7bc164c6a6 100644
--- a/src/components/ui/copybutton/Button.vue
+++ b/src/components/ui/copybutton/Button.vue
@@ -4,6 +4,7 @@ import { cn } from '@/lib/utils'
 import { Primitive, type PrimitiveProps } from 'reka-ui'
 import { buttonVariants } from '.'
 import { mdiCheck, mdiContentCopy } from '@mdi/js'
+import { copyText } from '@/utils/copy'
 
 interface Props extends PrimitiveProps {
   class?: HTMLAttributes['class'];
@@ -18,7 +19,7 @@ const copyBtn = (event: MouseEvent) => {
   const target = event.target as HTMLElement;
   const value = target.getAttribute("data-copy");
   if (!value) return;
-  navigator.clipboard.writeText(String(value));
+  copyText(value);
 
   const oldAttribute = target.children[0].children[0].getAttribute('d');
   target.children[0].children[0].setAttribute('d', mdiCheck);
diff --git a/src/utils/copy.ts b/src/utils/copy.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b58173c0e3349ac642e47bcc602ea0e1294be8da
--- /dev/null
+++ b/src/utils/copy.ts
@@ -0,0 +1,34 @@
+/**
+ * Copies given text into clipboard.
+ *
+ * This function supports 3 ways of copying and fallbacks if any of them fails:
+ * 1. Using the modern Clipboard API (navigator.clipboard.writeText) - on most browsers works only in secure context (HTTPS) or localhost
+ * 2. Using the deprecated document.execCommand("copy") - deprecated, but still supported by most browsers - with Firefox works only on "click" event triggered by the user
+ * 3. Using the prompt function - the most universal way, but requires user interaction
+ */
+export const copyText = (text: string) => {
+  try {
+    if (navigator.clipboard) { // is secure context
+      return navigator.clipboard.writeText(text).catch(() => {
+        prompt("Copy to clipboard: Ctrl+C, Enter", text);
+      });
+    } else if (document.queryCommandSupported && document.queryCommandSupported("copy")) { // check if we can use deprecated copy command
+      const textarea = document.createElement("textarea");
+      textarea.textContent = text;
+      textarea.style.width = "0";
+      textarea.style.height = "0";
+      textarea.style.position = "fixed"; // Prevent scrolling to bottom of page in MS Edge.
+      document.body.appendChild(textarea);
+      textarea.select();
+      try {
+        if(!document.execCommand("copy")) // Security exception may be thrown by some browsers.
+          throw new Error("Copy command was unsuccessful");
+      } finally {
+        document.body.removeChild(textarea);
+      }
+    }
+    throw new Error("No clipboard support");
+  } catch {
+    prompt("Copy to clipboard: Ctrl+C, Enter", text);
+  }
+}