Разработка SDK для взаимодействия со смарт-контрактами

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка SDK для взаимодействия со смарт-контрактами
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • 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
    1058
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    828

Разработка SDK для взаимодействия со смарт-контрактами

Смарт-контракт написан, задеплоен, верифицирован. Теперь фронтенд-разработчик пытается с ним работать: копирует ABI из etherscan, вручную кодирует параметры через ethers.utils.defaultAbiCoder.encode, ловит unknown error без stacktrace потому что контракт вернул revert без причины. Хороший SDK убирает весь этот friction и делает контракт пригодным к интеграции за часы, а не дни.

Что отличает хороший SDK от обёртки над ethers.js

Типичный «SDK», который пишут быстро — это файл с функциями-обёртками:

// плохо
export async function transfer(to: string, amount: string) {
  const contract = new ethers.Contract(ADDRESS, ABI, signer);
  return contract.transfer(to, amount);
}

Проблемы: нет типизации параметров, amount — строка или BigInt?, нет обработки ошибок, нет события о завершении, нет поддержки нескольких чейнов.

Нормальный SDK — это слой с чёткими контрактами:

import { type Address, parseUnits, formatUnits } from "viem";

export interface TransferParams {
  to: Address;
  amount: bigint;           // всегда wei, не строка
  chainId: SupportedChain;
}

export interface TransferResult {
  hash: `0x${string}`;
  waitForConfirmation: () => Promise<TransactionReceipt>;
}

export async function transfer(params: TransferParams): Promise<TransferResult>

amount — всегда bigint в wei. Никаких строк. Никакой двусмысленности. TypeScript не даст передать неправильный тип.

Архитектура SDK

Строим на viem для новых проектов. viem заменил ethers.js v5 в большинстве наших проектов: tree-shakeable, строгая типизация, нативные BigInt, значительно меньший bundle size.

sdk/
├── src/
│   ├── contracts/
│   │   ├── abi/            # типизированные ABI (wagmi/viem generate)
│   │   └── addresses.ts    # адреса по chainId
│   ├── actions/            # функции-действия (transfer, mint, stake)
│   ├── queries/            # read-only запросы (balanceOf, getAllowance)
│   ├── types/              # общие типы и interfaces
│   ├── errors/             # кастомные ошибки с человеческими сообщениями
│   └── index.ts            # public API
├── tests/
└── package.json

Типизированные ABI через codegen. Вместо const ABI = [...] без типов — генерируем через @wagmi/cli:

npx wagmi generate

Это даёт const ABI = [...] as const с полной типизацией. viem использует эти типы для автодополнения аргументов функций и типов возвращаемых значений на уровне TypeScript.

Обработка ошибок — самое важное

Контракт reverts — пользователь видит execution reverted. Это бесполезно. Нужно:

  1. Декодировать custom error из revert data
  2. Перевести в человеческое сообщение
  3. Добавить контекст (какая операция, с какими параметрами)
import { decodeErrorResult, BaseError, ContractFunctionRevertedError } from "viem";

export function parseContractError(error: unknown): SdkError {
  if (error instanceof BaseError) {
    const revertError = error.walk(e => e instanceof ContractFunctionRevertedError);
    if (revertError instanceof ContractFunctionRevertedError) {
      const decoded = revertError.data;
      
      switch (decoded?.errorName) {
        case "InsufficientBalance":
          return new SdkError("INSUFFICIENT_BALANCE", 
            `Недостаточно средств: требуется ${formatUnits(decoded.args[0], 18)} токенов`);
        case "Unauthorized":
          return new SdkError("UNAUTHORIZED", "Нет прав для этой операции");
        default:
          return new SdkError("CONTRACT_ERROR", decoded?.errorName ?? "Неизвестная ошибка контракта");
      }
    }
  }
  return new SdkError("UNKNOWN", "Непредвиденная ошибка");
}

Это важнее любой другой части SDK. Разработчики, интегрирующие контракт, тратят 60% времени на отладку ошибок — хороший error handling сокращает это кратно.

Мультичейн поддержка

Контракт на Ethereum и Polygon — не два разных SDK, а один с конфигурацией:

const ADDRESSES: Record<SupportedChain, Address> = {
  [mainnet.id]: "0x...",
  [polygon.id]: "0x...",
  [arbitrum.id]: "0x...",
};

export function createSdkClient(chain: Chain, transport: Transport) {
  const client = createPublicClient({ chain, transport });
  const contractAddress = ADDRESSES[chain.id];
  
  if (!contractAddress) {
    throw new Error(`Chain ${chain.name} not supported`);
  }
  
  return {
    transfer: (params: TransferParams) => transfer({ ...params, client, contractAddress }),
    balanceOf: (address: Address) => balanceOf({ address, client, contractAddress }),
  };
}

Тестирование SDK

Unit-тесты через viem testClient + anvil (локальный fork mainnet):

import { createTestClient, http } from "viem";
import { foundry } from "viem/chains";

const testClient = createTestClient({
  chain: foundry,
  transport: http("http://127.0.0.1:8545"),
  mode: "anvil",
});

test("transfer updates balances correctly", async () => {
  await testClient.impersonateAccount({ address: WHALE_ADDRESS });
  
  const result = await sdk.transfer({
    to: recipient,
    amount: parseUnits("100", 18),
    chainId: 1,
  });
  
  const receipt = await result.waitForConfirmation();
  expect(receipt.status).toBe("success");
  
  const balance = await sdk.balanceOf(recipient);
  expect(balance).toBe(parseUnits("100", 18));
});

Anvil форкает mainnet со всем state — тестируем против реальных контрактов, не моков.

Документация и публикация

Генерируем документацию через TypeDoc из JSDoc комментариев. Публикуем на npm (приватный реестр для закрытых проектов, публичный для open source).

Версионирование — semver: patch для bagfixes, minor для новых функций без breaking changes, major для изменений API.

Срок разработки базового SDK для одного контракта — 3-4 дня. SDK с мультичейн поддержкой, полным покрытием ошибок, тестами и документацией — 5-7 дней.