Разработка системы batch-транзакций

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка системы batch-транзакций
Средняя
от 1 рабочего дня до 3 рабочих дней
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1258
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    873
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1092
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    830

Разработка системы batch-транзакций

Пользователь хочет добавить ликвидность в пул Uniswap V3. По факту это: approve токена A → approve токена B → mint позиции. Три транзакции, три подтверждения в MetaMask, три оплаты газа. Если между approve и mint произойдёт фронтран — позиция создастся по нежелательной цене. Batch-транзакции решают это: одно подтверждение, один газ, атомарное исполнение.

Два подхода к batch: router и EIP-4337

Router-паттерн — контракт-агрегатор, который принимает массив вызовов и исполняет их последовательно. Самый простой вариант — Multicall3 от MakerDAO, задеплоенный на большинстве EVM-сетей по адресу 0xcA11bde05977b3631167028862bE2a173976CA11.

struct Call3 {
    address target;
    bool allowFailure;
    bytes callData;
}

function aggregate3(Call3[] calldata calls)
    external
    payable
    returns (Result[] memory returnData);

allowFailure: false делает весь batch атомарным — если один вызов reverts, откатывается всё. allowFailure: true позволяет продолжить batch при ошибке отдельного вызова — используем когда нужна partial execution.

Проблема router-паттерна: approve токена на адрес контракта-роутера. Пользователь должен доверять роутеру, что тот не уведёт токены. Для кастомных роутеров это создаёт UX-барьер и требует аудита.

EIP-4337 (Account Abstraction) — другой уровень. Пользователь контролирует smart contract wallet, который может исполнять несколько вызовов в одной UserOperation. Approve + действие атомарны, без промежуточного доверия роутеру. Стек: Biconomy, Safe{Core} AA SDK, ZeroDev.

Выбор зависит от контекста: для протокольного batch-инга — router, для wallet-уровневой автоматизации — EIP-4337.

Экономия газа: считаем честно

Каждая транзакция в EVM стоит минимум 21 000 gas (intrinsic cost). Batch из 5 операций в отдельных транзакциях: 5 × 21 000 = 105 000 gas только на intrinsic. Через Multicall3 — один раз 21 000 + overhead роутера (~2 000 gas) + gas на каждый вызов без intrinsic cost.

Сценарий Отдельные транзакции Batch (Multicall3) Экономия
3 ERC-20 transfer 3 × 65 000 = 195 000 ~125 000 ~36%
5 approve + swap 5 × 46 000 = 230 000 ~148 000 ~36%
10 NFT mint 10 × 120 000 = 1 200 000 ~650 000 ~46%

Реальные числа зависят от логики каждого вызова, но экономия 30-50% на gas intrinsic cost — консервативная оценка.

Кастомная batch-система: когда Multicall3 не хватает

Multicall3 не принимает ETH с распределением по вызовам (только общий msg.value). Не поддерживает callback-и. Не хранит состояние между вызовами в пакете.

Для сложных сценариев пишем кастомный BatchExecutor:

contract BatchExecutor {
    struct BatchCall {
        address target;
        uint256 value;
        bytes data;
        bool requireSuccess;
    }

    function executeBatch(BatchCall[] calldata calls)
        external
        payable
        returns (bytes[] memory results)
    {
        results = new bytes[](calls.length);
        for (uint256 i = 0; i < calls.length; i++) {
            (bool success, bytes memory result) = calls[i].target.call{
                value: calls[i].value
            }(calls[i].data);

            if (calls[i].requireSuccess) {
                require(success, _getRevertMsg(result));
            }
            results[i] = result;
        }
    }
}

Обязательная проверка безопасности: делегировать ли пользователю выбор target адресов? Если контракт принимает произвольные target — атакующий может вызвать произвольный контракт от имени BatchExecutor. Если BatchExecutor держит approve-ы на токены — это drain. Ограничиваем target whitelist-ом или проверяем, что контракт не держит чужих активов.

Интеграция с frontend через wagmi/viem

На клиентской стороне формируем список вызовов и кодируем через viem:

import { encodeFunctionData } from 'viem';
import { multicall3Abi } from './abis';

const calls = [
  {
    target: tokenAddress,
    allowFailure: false,
    callData: encodeFunctionData({
      abi: erc20Abi,
      functionName: 'approve',
      args: [spenderAddress, amount]
    })
  },
  {
    target: protocolAddress,
    allowFailure: false,
    callData: encodeFunctionData({
      abi: protocolAbi,
      functionName: 'deposit',
      args: [amount]
    })
  }
];

await walletClient.writeContract({
  address: MULTICALL3_ADDRESS,
  abi: multicall3Abi,
  functionName: 'aggregate3',
  args: [calls]
});

Сроки разработки: интеграция Multicall3 в существующий dApp — 1-2 дня. Кастомный BatchExecutor с whitelist логикой и тестами — 3-5 дней.