Skip to content

Complete Vue Example

This is a complete, production-ready example of a Silent Swap implementation using Vue 3 composables.

Full Component

<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import { getSilentSwapClient, getSilentSwapOrders, getSilentSwapAuth } from '@silentswap/vue';
import { useAccount, useSignMessage, useSignTypedData } from 'wagmi';
import { 
  ENVIRONMENT,
  DeliveryMethod,
  caip19FungibleEvmToken,
  FacilitatorKeyType,
  PublicKeyArgGroups,
  createHdFacilitatorGroupFromEntropy,
} from '@silentswap/sdk';
import { hexToBytes } from 'viem';
 
const { address, isConnected } = useAccount();
const { signMessageAsync } = useSignMessage();
const { signTypedDataAsync } = useSignTypedData();
 
// Initialize client
const { client } = getSilentSwapClient({
  config: {
    environment: ENVIRONMENT.MAINNET,
    baseUrl: 'https://api.silentswap.com',
  },
});
 
// Get composables
const {
  isLoading,
  error,
  getNonce,
  authenticate,
  getQuote,
  createOrder,
} = getSilentSwapOrders({ client });
 
const {
  createSignInMessage,
  createEip712DocForOrder,
  createEip712DocForWalletGeneration,
} = getSilentSwapAuth();
 
// State
const authResult = ref<any>(null);
const walletEntropy = ref<Uint8Array | null>(null);
const facilitatorGroup = ref<any>(null);
const quote = ref<any>(null);
const order = ref<any>(null);
 
// Form state
const sourceAmount = ref('');
const destinationAddress = ref('');
 
// Authentication flow
const handleSignIn = async () => {
  if (!address.value) return;
 
  try {
    // Get nonce
    const nonceResponse = await getNonce(address.value);
    if (!nonceResponse) {
      throw new Error('Failed to get nonce');
    }
 
    // Create SIWE message
    const message = createSignInMessage(
      address.value,
      nonceResponse.nonce,
      window.location.host
    );
 
    // Sign message
    const signature = await signMessageAsync({ message });
 
    // Authenticate
    const result = await authenticate({
      siwe: {
        message,
        signature: signature as `0x${string}`,
      },
    });
 
    if (!result) {
      throw new Error('Authentication failed');
    }
 
    authResult.value = result;
    console.log('Authenticated:', result.address);
 
    // Generate wallet entropy
    await generateWalletEntropy(result.secretToken);
  } catch (err) {
    console.error('Sign in failed:', err);
  }
};
 
// Generate wallet entropy
const generateWalletEntropy = async (secretToken: string) => {
  if (!address.value) return;
 
  try {
    const walletDoc = createEip712DocForWalletGeneration(
      `eip155:43114:${address.value}`,
      secretToken
    );
 
    const signature = await signTypedDataAsync(walletDoc);
    walletEntropy.value = hexToBytes(signature as `0x${string}`);
 
    // Create facilitator group
    const group = await createHdFacilitatorGroupFromEntropy(
      walletEntropy.value,
      0 // nonce
    );
    facilitatorGroup.value = group;
 
    console.log('Wallet entropy generated');
  } catch (err) {
    console.error('Wallet generation failed:', err);
  }
};
 
// Get quote
const handleGetQuote = async () => {
  if (!facilitatorGroup.value || !sourceAmount.value || !destinationAddress.value) {
    return;
  }
 
  try {
    const viewer = await facilitatorGroup.value.viewer();
    const { publicKeyBytes: viewerPk } = viewer.exportPublicKey('*', FacilitatorKeyType.SECP256K1);
    const groupPks = await facilitatorGroup.value.exportPublicKeys(1, PublicKeyArgGroups.GENERIC);
 
    const quoteResult = await getQuote({
      signer: address.value!,
      viewer: viewerPk,
      outputs: [{
        method: DeliveryMethod.SNIP,
        asset: caip19FungibleEvmToken(1, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'),
        value: (parseFloat(sourceAmount.value) * 1e6).toString() as `${bigint}`,
        recipient: destinationAddress.value as `0x${string}`,
        facilitatorPublicKeys: groupPks[0],
      }],
    });
 
    if (quoteResult) {
      quote.value = quoteResult;
      console.log('Quote received:', quoteResult.quoteId);
    }
  } catch (err) {
    console.error('Quote failed:', err);
  }
};
 
// Create order
const handleCreateOrder = async () => {
  if (!quote.value || !facilitatorGroup.value) {
    return;
  }
 
  try {
    // Create EIP-712 document
    const orderDoc = createEip712DocForOrder(quote.value);
 
    // Sign the document
    const signature = await signTypedDataAsync(orderDoc);
 
    // Approve proxy authorizations
    const facilitatorReplies = await facilitatorGroup.value.approveProxyAuthorizations(
      quote.value.facilitators,
      {
        proxyPublicKey: client.proxyPublicKey,
      }
    );
 
    // Create order
    const orderResult = await createOrder({
      quote: quote.value.quote,
      quoteId: quote.value.quoteId,
      authorizations: [], // You'd sign these separately based on quote.authorizations
      eip712Domain: orderDoc.domain,
      signature,
      facilitators: facilitatorReplies,
    });
 
    if (orderResult) {
      order.value = orderResult;
      console.log('Order created:', orderResult.response.orderId);
    }
  } catch (err) {
    console.error('Order creation failed:', err);
  }
};
 
// Computed properties
const isAuthenticated = computed(() => authResult.value !== null);
const isWalletReady = computed(() => facilitatorGroup.value !== null);
const canGetQuote = computed(() => 
  isWalletReady.value && 
  sourceAmount.value && 
  parseFloat(sourceAmount.value) > 0 &&
  destinationAddress.value
);
const canCreateOrder = computed(() => quote.value !== null && isWalletReady.value);
 
// Watch for errors
watch(error, (err) => {
  if (err) {
    console.error('Error:', err.message);
  }
});
</script>
 
<template>
  <div class="swap-container">
    <h1>SilentSwap - Vue Integration</h1>
 
    <!-- Connection Status -->
    <div v-if="!isConnected" class="status">
      <p>Please connect your wallet</p>
    </div>
 
    <!-- Authentication -->
    <div v-else-if="!isAuthenticated" class="auth-section">
      <h2>Authentication</h2>
      <button 
        @click="handleSignIn" 
        :disabled="isLoading"
        class="btn-primary"
      >
        {{ isLoading ? 'Signing In...' : 'Sign In with Ethereum' }}
      </button>
    </div>
 
    <!-- Main Swap Form -->
    <div v-else class="swap-form">
      <div v-if="!isWalletReady" class="status">
        <p>Generating wallet...</p>
      </div>
 
      <div v-else>
        <!-- Error Display -->
        <div v-if="error" class="error">
          Error: {{ error.message }}
        </div>
 
        <!-- Source Amount -->
        <div class="form-group">
          <label>Source Amount</label>
          <input
            v-model="sourceAmount"
            type="number"
            placeholder="0.0"
            step="0.000001"
          />
        </div>
 
        <!-- Destination Address -->
        <div class="form-group">
          <label>Destination Address</label>
          <input
            v-model="destinationAddress"
            type="text"
            placeholder="0x..."
          />
        </div>
 
        <!-- Actions -->
        <div class="actions">
          <button
            @click="handleGetQuote"
            :disabled="!canGetQuote || isLoading"
            class="btn-secondary"
          >
            {{ isLoading ? 'Getting Quote...' : 'Get Quote' }}
          </button>
 
          <button
            @click="handleCreateOrder"
            :disabled="!canCreateOrder || isLoading"
            class="btn-primary"
          >
            {{ isLoading ? 'Creating Order...' : 'Create Order' }}
          </button>
        </div>
 
        <!-- Quote Display -->
        <div v-if="quote" class="quote-details">
          <h3>Quote Details</h3>
          <div class="detail-row">
            <span>Quote ID:</span>
            <code>{{ quote.quoteId }}</code>
          </div>
          <div class="detail-row">
            <span>Authorizations:</span>
            <span>{{ quote.authorizations.length }}</span>
          </div>
          <div class="detail-row">
            <span>Facilitators:</span>
            <span>{{ quote.facilitators.length }}</span>
          </div>
        </div>
 
        <!-- Order Display -->
        <div v-if="order" class="order-details">
          <h3>Order Created</h3>
          <div class="detail-row">
            <span>Order ID:</span>
            <code>{{ order.response.orderId }}</code>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
 
<style scoped>
.swap-container {
  max-width: 600px;
  margin: 0 auto;
  padding: 2rem;
}
 
.status {
  text-align: center;
  padding: 1rem;
  background: #f0f0f0;
  border-radius: 8px;
  margin-bottom: 1rem;
}
 
.error {
  padding: 1rem;
  background: #fee;
  border: 1px solid #fcc;
  border-radius: 8px;
  margin-bottom: 1rem;
  color: #c00;
}
 
.form-group {
  margin-bottom: 1rem;
}
 
.form-group label {
  display: block;
  margin-bottom: 0.5rem;
  font-weight: 500;
}
 
.form-group input {
  width: 100%;
  padding: 0.75rem;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 1rem;
}
 
.actions {
  display: flex;
  gap: 1rem;
  margin: 1.5rem 0;
}
 
.btn-primary,
.btn-secondary {
  flex: 1;
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
  font-size: 1rem;
  cursor: pointer;
  transition: opacity 0.2s;
}
 
.btn-primary {
  background: #007bff;
  color: white;
}
 
.btn-secondary {
  background: #6c757d;
  color: white;
}
 
.btn-primary:disabled,
.btn-secondary:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
 
.quote-details,
.order-details {
  margin-top: 2rem;
  padding: 1rem;
  background: #f9f9f9;
  border-radius: 8px;
}
 
.detail-row {
  display: flex;
  justify-content: space-between;
  padding: 0.5rem 0;
  border-bottom: 1px solid #eee;
}
 
.detail-row:last-child {
  border-bottom: none;
}
 
code {
  font-family: monospace;
  background: #f0f0f0;
  padding: 0.25rem 0.5rem;
  border-radius: 4px;
  font-size: 0.9em;
}
</style>

Key Features

  1. Reactive State - Uses Vue's reactivity system for automatic UI updates
  2. Complete Flow - Handles authentication, wallet generation, quotes, and orders
  3. Error Handling - Comprehensive error handling with user feedback
  4. Type Safety - Full TypeScript support
  5. Computed Properties - Smart computed values for form validation

Flow Summary

  1. Connect Wallet - User connects wallet via wagmi
  2. Authenticate - Sign in with Ethereum (SIWE)
  3. Generate Wallet - Create facilitator wallet from entropy
  4. Get Quote - Fetch quote with facilitator public keys
  5. Create Order - Sign and submit order

Next Steps