Реализация 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 };
}

Отслеживание событий контракта

Реальное время обновлений через event-подписку — важнее поллинга:

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-флоу, event-подпиской, историей транзакций и поддержкой нескольких сетей — 2–3 недели.