SIGIL · INTEGRATION DOCS← Back to site
[ GUIDES ]

Vue 3

Vue 3 Composition API integration with a reusable useSigil composable and ready-to-use single-file components. Works with Vue Router and Nuxt.

Install

TERMINAL
npm install @sigil-oss/connect

useSigil composable

Drop this in composables/useSigil.ts. Returns reactive status, result, and error refs alongside the request() function.

composables/useSigil.ts
// composables/useSigil.ts
import { ref } from 'vue';
import {
  sigilRequest,
  type SigilRequest,
  type SigilCallbackResponse,
} from '@sigil-oss/connect';

type Status = 'idle' | 'pending' | 'success' | 'error';

export function useSigil() {
  const status = ref<Status>('idle');
  const result = ref<SigilCallbackResponse | null>(null);
  const error = ref<Error | null>(null);
  let pending = false;

  async function request(req: SigilRequest) {
    if (pending) throw new Error('A Sigil request is already in progress');
    pending = true;
    status.value = 'pending';
    result.value = null;
    error.value = null;

    try {
      const res = await sigilRequest(req);
      result.value = res;
      status.value = 'success';
      return res;
    } catch (err) {
      error.value = err instanceof Error ? err : new Error(String(err));
      status.value = 'error';
      throw error.value;
    } finally {
      pending = false;
    }
  }

  function reset() {
    status.value = 'idle';
    result.value = null;
    error.value = null;
  }

  return { request, status, result, error, reset };
}

Callback route

Register a route at /__sigil__ that renders the callback component. It calls handleRedirect() on mount and closes the tab after broadcasting the result.

components/SigilCallback.vue
<!-- components/SigilCallback.vue -->
<script setup lang="ts">
import { onMounted } from 'vue';
import { handleRedirect } from '@sigil-oss/connect';

onMounted(() => {
  handleRedirect(); // reads ?result=, broadcasts, closes tab
});
</script>

<template>
  <!-- tab closes itself — nothing to render -->
</template>

Vue Router

router/index.ts
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import SigilCallback from '@/components/SigilCallback.vue';
import Home from '@/views/Home.vue';

export const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: Home },
    { path: '/__sigil__', component: SigilCallback },
    // … rest of your routes
  ],
});

Nuxt

Create a page file at pages/__sigil__.vue — Nuxt's file-based routing picks it up automatically.

pages/__sigil__.vue
<!-- pages/__sigil__.vue — Nuxt auto-routing picks this up -->
<script setup lang="ts">
import { onMounted } from 'vue';
import { handleRedirect } from '@sigil-oss/connect';

onMounted(() => {
  handleRedirect();
});
</script>

<template><!-- closes itself --></template>

Connect wallet

components/ConnectButton.vue
<!-- components/ConnectButton.vue -->
<script setup lang="ts">
import { createConnectRequest } from '@sigil-oss/connect';
import { useSigil } from '@/composables/useSigil';

const emit = defineEmits<{ connected: [identity: string] }>();
const { request, status, result, reset } = useSigil();

async function connect() {
  const res = await request(
    createConnectRequest({
      type: 'connect',
      dapp: { name: 'My App', origin: 'https://myapp.example' },
      permissions: ['transfer', 'sign_message'],
    })
  );
  if (res.status === 'connected') emit('connected', res.identity);
}
</script>

<template>
  <div v-if="status === 'success' && result?.status === 'connected'">
    <p>Connected: {{ result.identity.slice(0, 8) }}…</p>
    <button @click="reset">Disconnect</button>
  </div>
  <button v-else @click="connect" :disabled="status === 'pending'">
    {{ status === 'pending' ? 'Opening Sigil…' : 'Connect Wallet' }}
  </button>
</template>

Sign in with Qubic

components/SignInButton.vue
<!-- components/SignInButton.vue -->
<script setup lang="ts">
import { createSignMessageRequest } from '@sigil-oss/connect';
import { useSigil } from '@/composables/useSigil';

const emit = defineEmits<{ signedIn: [identity: string] }>();
const { request, status, error } = useSigil();

async function signIn() {
  const nonce = crypto.randomUUID();

  const res = await request(
    createSignMessageRequest({
      type: 'sign_message',
      dapp: { name: 'My App', origin: 'https://myapp.example' },
      message: [`Sign in to My App`, `nonce: ${nonce}`, `issuedAt: ${new Date().toISOString()}`].join('\n'),
    })
  );

  if (res.status !== 'signed' || res.type !== 'sign_message') return;

  const response = await fetch('/api/auth/qubic', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      identity: res.identity,
      signature: res.signature,
      public_key: res.public_key,
      nonce,
    }),
  });

  if (response.ok) emit('signedIn', res.identity);
}
</script>

<template>
  <button @click="signIn" :disabled="status === 'pending'">
    {{ status === 'pending' ? 'Waiting for Sigil…' : 'Sign in with Qubic' }}
  </button>
  <p v-if="error" style="color: red">{{ error.message }}</p>
</template>

Request a transfer

components/TransferButton.vue
<!-- components/TransferButton.vue -->
<script setup lang="ts">
import { createTransferRequest } from '@sigil-oss/connect';
import { useSigil } from '@/composables/useSigil';

const props = defineProps<{ to: string; amount: number }>();
const emit = defineEmits<{ sent: [txHash: string] }>();
const { request, status, result } = useSigil();

async function send() {
  const res = await request(
    createTransferRequest({
      type: 'transfer',
      dapp: { name: 'My App', origin: 'https://myapp.example' },
      to: props.to,
      amount: props.amount,
    })
  );
  if (res.status === 'signed' && (res.type === 'transfer' || res.type === 'sc_call')) emit('sent', res.tx_hash);
}
</script>

<template>
  <p v-if="status === 'success' && result?.status === 'signed' && (result.type === 'transfer' || result.type === 'sc_call')">
    Sent — tx: {{ result.tx_hash.slice(0, 12) }}…
  </p>
  <button v-else @click="send" :disabled="status === 'pending'">
    {{ status === 'pending' ? 'Waiting for Sigil…' : `Send ${amount.toLocaleString()} QU` }}
  </button>
</template>