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

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка фронтенда dApp на Vue.js
Средняя
~1-2 недели
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1221
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1163
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    855
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1056
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    828

Разработка фронтенда 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. Разберём оба варианта.

Стек и архитектура

Вариант 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 для мультикошелькового подключения

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 variables.

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.