Розробка фронтенду dApp на Vue.js

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка фронтенду dApp на Vue.js
Середній
~1-2 тижні
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1306
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1218
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    920
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1147
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    610
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    885

Розробка фронтенда dApp на Vue.js

Vue.js у Web3 — не другий сорт після React. Проблема в тому, що більшість Web3 бібліотек писалися React-first: wagmi, RainbowKit, ConnectKit — все це React hooks. Для Vue потрібен інший стек, і він існує: wagmi/core (framework-agnostic) + vue-query + web3modal. Або ethers.js напряму з Vue 3 Composition API. Розберемо обидва варіанти.

Stack та архітектура

Варіант 1: wagmi/core + vue-query

@wagmi/core — headless версія wagmi без прив'язки до React. Все actions (connect, readContract, writeContract, watchAccount) працюють як звичайні функції. vue-query (TanStack Query для Vue) управляє кешуванням та реактивністю.

// wagmi.config.ts
import { createConfig, http } from '@wagmi/core'
import { mainnet, polygon } from '@wagmi/core/chains'
import { walletConnect, injected } from '@wagmi/connectors'

export const config = createConfig({
  chains: [mainnet, polygon],
  connectors: [
    injected(), // MetaMask та інші EIP-1193
    walletConnect({ projectId: import.meta.env.VITE_WC_PROJECT_ID }),
  ],
  transports: {
    [mainnet.id]: http(),
    [polygon.id]: http(),
  },
})

Composable для роботи з аккаунтом:

// composables/useWallet.ts
import { ref, computed, watchEffect } from 'vue'
import { connect, disconnect, getAccount, watchAccount } from '@wagmi/core'
import { config } from '@/wagmi.config'

export function useWallet() {
  const account = ref(getAccount(config))

  const unwatch = watchAccount(config, {
    onChange(data) { account.value = data }
  })

  onUnmounted(() => unwatch())

  return {
    address: computed(() => account.value.address),
    isConnected: computed(() => account.value.isConnected),
    chainId: computed(() => account.value.chainId),
    connect: (connector) => connect(config, { connector }),
    disconnect: () => disconnect(config),
  }
}

Варіант 2: ethers.js + Vue 3 Composition API

Якщо проект вже використовує ethers.js або команда краще знає ethers:

// composables/useEthers.ts
import { ref, shallowRef } from 'vue'
import { BrowserProvider, JsonRpcSigner } from 'ethers'

export function useEthers() {
  const provider = shallowRef<BrowserProvider | null>(null)
  const signer = shallowRef<JsonRpcSigner | null>(null)
  const address = ref<string | null>(null)

  async function connectWallet() {
    if (!window.ethereum) throw new Error('No wallet detected')
    const _provider = new BrowserProvider(window.ethereum)
    const _signer = await _provider.getSigner()
    provider.value = _provider
    signer.value = _signer
    address.value = await _signer.getAddress()

    window.ethereum.on('accountsChanged', (accounts: string[]) => {
      address.value = accounts[0] ?? null
    })
    window.ethereum.on('chainChanged', () => window.location.reload())
  }

  return { provider, signer, address, connectWallet }
}

shallowRef для provider та signer — важлива деталь. Глибока реактивність Vue на ethers.js об'єктах викликає performance проблеми та дивні баги.

Читання даних контракту

З vue-query кешування та refetch працюють прозоро:

// composables/useTokenBalance.ts
import { useQuery } from '@tanstack/vue-query'
import { readContract } from '@wagmi/core'
import { erc20Abi } from 'viem'
import { config } from '@/wagmi.config'

export function useTokenBalance(tokenAddress: Ref<`0x${string}`>, owner: Ref<`0x${string}` | undefined>) {
  return useQuery({
    queryKey: computed(() => ['balance', tokenAddress.value, owner.value]),
    queryFn: () => readContract(config, {
      address: tokenAddress.value,
      abi: erc20Abi,
      functionName: 'balanceOf',
      args: [owner.value!],
    }),
    enabled: computed(() => !!owner.value),
    staleTime: 10_000, // 10 секунд кеш
  })
}

Запис у контракт та стан трансакції

Мутації через vue-query useMutation, стан трансакції через waitForTransactionReceipt:

import { useMutation } from '@tanstack/vue-query'
import { writeContract, waitForTransactionReceipt } from '@wagmi/core'

export function useApprove(tokenAddress: Ref<`0x${string}`>) {
  return useMutation({
    mutationFn: async ({ spender, amount }: { spender: `0x${string}`, amount: bigint }) => {
      const hash = await writeContract(config, {
        address: tokenAddress.value,
        abi: erc20Abi,
        functionName: 'approve',
        args: [spender, amount],
      })
      await waitForTransactionReceipt(config, { hash })
      return hash
    },
  })
}

У компоненті:

<script setup>
const { mutate: approve, isPending, isSuccess, error } = useApprove(tokenAddress)
</script>

<template>
  <button @click="approve({ spender, amount })" :disabled="isPending">
    {{ isPending ? 'Підтвердіть у гаманці...' : 'Approve' }}
  </button>
  <p v-if="error">{{ error.shortMessage }}</p>
</template>

Web3Modal для Multi-wallet підключення

WalletConnect's Web3Modal працює з Vue через @web3modal/wagmi/vue:

import { createWeb3Modal } from '@web3modal/wagmi/vue'

createWeb3Modal({
  wagmiConfig: config,
  projectId: import.meta.env.VITE_WC_PROJECT_ID,
  enableAnalytics: false,
})

Після цього в будь-якому компоненті:

<w3m-button />

Готовий UI з підтримкою MetaMask, WalletConnect, Coinbase Wallet, Injected та 300+ гаманців. Кастомізація через CSS змінні.

State management: Pinia для глобального Web3 стану

Для даних, які потрібні в кількох несвязаних компонентах (баланс, allowances), Pinia store:

// stores/web3.ts
import { defineStore } from 'pinia'
import { useWallet } from '@/composables/useWallet'

export const useWeb3Store = defineStore('web3', () => {
  const wallet = useWallet()
  // computed getters, actions для batch операцій
  return { ...wallet }
})

Сборка: Vite + @vitejs/plugin-vue

Node polyfills для бібліотек, які очікують Node.js окружень (деякі частини ethers.js):

// vite.config.ts
import { nodePolyfills } from 'vite-plugin-node-polyfills'

export default defineConfig({
  plugins: [vue(), nodePolyfills({ include: ['buffer', 'stream', 'util'] })],
  resolve: {
    alias: { '@': '/src' }
  }
})

Без polyfills Buffer is not defined або process is not defined — класичні помилки при першому запуску Web3 проекту на Vite.