Реалізація dApp-інтерфейсу для смарт-контрактів на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація dApp-інтерфейсу для смарт-контрактів на сайті
Складна
~5 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

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

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація dApp-інтерфейсу для смарт-контрактів на веб-сайті

dApp-інтерфейс — це фронтенд, який дозволяє користувачам взаємодіяти зі смарт-контрактами без знання Solidity або командного рядка. Кнопка "Stake" запускає транзакцію stake(amount). Форма "Swap" викликає swapExactTokensForTokens. Таблиця "Positions" читає дані контракту в реальному часі.

Складність залежить від контракту: одиночний контракт з 5 функціями — це одна історія; протокол з проксі, мультиконтрактними взаємодіями та offchain-ціноутворенням — зовсім інша.

Архітектура dApp-фронтенду

dapp/
├── abis/                    # ABI-файли контрактів
│   ├── StakingPool.json
│   └── RewardToken.json
├── lib/
│   ├── contracts.ts         # Типизовані інстанси контрактів
│   ├── wagmiConfig.ts       # Налаштування wagmi + viem
│   └── multicall.ts         # Батчинг read-запитів
├── hooks/
│   ├── useStakingPool.ts    # Читання стану контракту
│   ├── useStake.ts          # Транзакція stake
│   └── useApprove.ts        # ERC-20 approve
└── components/
    ├── StakingWidget/
    ├── PositionsTable/
    └── TransactionStatus/

Налаштування wagmi та клієнтів

// lib/wagmiConfig.ts
import { createConfig, http } from 'wagmi';
import { mainnet, polygon, arbitrum } from 'wagmi/chains';
import { injected, walletConnect, coinbaseWallet } from 'wagmi/connectors';

export const wagmiConfig = createConfig({
  chains: [mainnet, polygon, arbitrum],
  connectors: [
    injected(),
    walletConnect({ projectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID! }),
    coinbaseWallet({ appName: 'MyDApp' }),
  ],
  transports: {
    [mainnet.id]: http(process.env.ETH_RPC_URL!),
    [polygon.id]: http(process.env.POLYGON_RPC_URL!),
    [arbitrum.id]: http(process.env.ARBITRUM_RPC_URL!),
  },
});

Типизовані хуки для читання контракту

Замість роботи з сирими ABI-масивами — типізація через @wagmi/cli:

# wagmi.config.ts
npx wagmi generate
// wagmi.config.ts
import { defineConfig } from '@wagmi/cli';
import { react } from '@wagmi/cli/plugins';

export default defineConfig({
  out: 'src/generated.ts',
  contracts: [
    {
      name: 'StakingPool',
      address: {
        1: '0xContractOnMainnet',
        137: '0xContractOnPolygon',
      },
      abi: StakingPoolAbi,
    },
  ],
  plugins: [react()],
});

Генерує типізовані хуки useReadStakingPool, useWriteStakingPool, useSimulateStakingPool. Помилки компіляції при неверних аргументах — замість runtime-помилок.

Читання стану з multicall

// hooks/useStakingPool.ts
import { useAccount, useReadContracts } from 'wagmi';
import { StakingPoolAbi } from '@/abis/StakingPool';

const CONTRACT = '0xYourContract' as const;

export function useStakingPool() {
  const { address } = useAccount();

  const { data, isLoading } = useReadContracts({
    contracts: [
      { address: CONTRACT, abi: StakingPoolAbi, functionName: 'totalStaked' },
      { address: CONTRACT, abi: StakingPoolAbi, functionName: 'rewardRate' },
      { address: CONTRACT, abi: StakingPoolAbi, functionName: 'periodFinish' },
      ...(address ? [
        { address: CONTRACT, abi: StakingPoolAbi, functionName: 'balanceOf', args: [address] },
        { address: CONTRACT, abi: StakingPoolAbi, functionName: 'earned', args: [address] },
      ] : []),
    ],
    query: { refetchInterval: 12_000 },
  });

  return {
    isLoading,
    totalStaked: data?.[0].result as bigint | undefined,
    rewardRate: data?.[1].result as bigint | undefined,
    periodFinish: data?.[2].result as bigint | undefined,
    userBalance: address ? data?.[3].result as bigint | undefined : undefined,
    userEarned: address ? data?.[4].result as bigint | undefined : undefined,
  };
}

ERC-20 Approve + дія — стандартний двохшаговий флоу

Більшість DeFi-операцій вимагають спочатку approve токена, потім вызов контракту. Потрібно перевіряти поточний allowance та пропускати approve, якщо він достатній:

// hooks/useStake.ts
import { useReadContract, useWriteContract, useWaitForTransactionReceipt } from 'wagmi';
import { erc20Abi, parseUnits } from 'viem';

const STAKING_CONTRACT = '0xStaking' as const;
const STAKE_TOKEN = '0xToken' as const;

export function useStake() {
  const { address } = useAccount();

  const { data: allowance, refetch: refetchAllowance } = useReadContract({
    address: STAKE_TOKEN,
    abi: erc20Abi,
    functionName: 'allowance',
    args: [address!, STAKING_CONTRACT],
    query: { enabled: !!address },
  });

  const { writeContractAsync } = useWriteContract();
  const [txHash, setTxHash] = useState<`0x${string}` | undefined>();

  const { isLoading: isWaiting, isSuccess } = useWaitForTransactionReceipt({ hash: txHash });

  const stake = async (amount: string, decimals: number) => {
    const amountWei = parseUnits(amount, decimals);

    // Крок 1: approve, якщо потрібен
    if (!allowance || allowance < amountWei) {
      const approveTx = await writeContractAsync({
        address: STAKE_TOKEN,
        abi: erc20Abi,
        functionName: 'approve',
        args: [STAKING_CONTRACT, amountWei],
      });
      // Чекаємо підтвердження approve
      await waitForTransactionReceipt(wagmiConfig, { hash: approveTx });
      await refetchAllowance();
    }

    // Крок 2: stake
    const stakeTx = await writeContractAsync({
      address: STAKING_CONTRACT,
      abi: StakingPoolAbi,
      functionName: 'stake',
      args: [amountWei],
    });
    setTxHash(stakeTx);
  };

  return { stake, isWaiting, isSuccess, txHash };
}

Відстеження подій контракту

Оновлення в реальному часі через підписку на события — важливіше за поллінг:

import { useWatchContractEvent } from 'wagmi';

export function useStakeEvents(onStake: (amount: bigint) => void) {
  useWatchContractEvent({
    address: STAKING_CONTRACT,
    abi: StakingPoolAbi,
    eventName: 'Staked',
    onLogs(logs) {
      for (const log of logs) {
        if (log.args.user === address) {
          onStake(log.args.amount as bigint);
        }
      }
    },
  });
}

Симуляція транзакцій перед відправкою

import { useSimulateContract } from 'wagmi';

// Перевіряємо, що транзакція пройде, до підписи користувачем
const { data: simulation, error: simError } = useSimulateContract({
  address: STAKING_CONTRACT,
  abi: StakingPoolAbi,
  functionName: 'stake',
  args: [amountWei],
  query: { enabled: amountWei > 0n },
});

// simError містить причину відказу контракту — показуємо користувачу
// до того, як він підписав і витратив gas

Часова шкала

Інтерфейс для одного контракту з 3–5 write-функціями, читанням стану та відображенням позицій користувача — 5–7 днів. Мультиконтрактний протокол з approve-флоу, підпиской на события, історією транзакцій та підтримкою кількох мереж — 2–3 тижні.