SDK development for smart contract interaction

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
SDK development for smart contract interaction
Medium
~3-5 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1238
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1167
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    867
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1080
  • image_logo-advance_0.png
    B2B Advance company logo design
    563
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    829

Developing SDKs for Smart Contract Interaction

The smart contract is written, deployed, verified. Now a frontend developer tries to work with it: copies the ABI from etherscan, manually encodes parameters via ethers.utils.defaultAbiCoder.encode, catches unknown error without stacktrace because the contract reverted without reason. A good SDK removes all this friction and makes the contract usable for integration in hours, not days.

What Distinguishes a Good SDK from an ethers.js Wrapper

A typical "SDK" written quickly is a file with wrapper functions:

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

Problems: no parameter type checking, amount — string or BigInt?, no error handling, no completion event, no multi-chain support.

A proper SDK is a layer with clear contracts:

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

export interface TransferParams {
  to: Address;
  amount: bigint;           // always wei, not string
  chainId: SupportedChain;
}

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

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

amount is always bigint in wei. No strings. No ambiguity. TypeScript won't let you pass the wrong type.

SDK Architecture

Build on viem for new projects. viem replaced ethers.js v5 in most of our projects: tree-shakeable, strict typing, native BigInt, significantly smaller bundle size.

sdk/
├── src/
│   ├── contracts/
│   │   ├── abi/            # typed ABIs (wagmi/viem generate)
│   │   └── addresses.ts    # addresses by chainId
│   ├── actions/            # action functions (transfer, mint, stake)
│   ├── queries/            # read-only queries (balanceOf, getAllowance)
│   ├── types/              # common types and interfaces
│   ├── errors/             # custom errors with human messages
│   └── index.ts            # public API
├── tests/
└── package.json

Typed ABIs via codegen. Instead of const ABI = [...] without types — generate via @wagmi/cli:

npx wagmi generate

This gives const ABI = [...] as const with full typing. viem uses these types for autocomplete and return type inference at the TypeScript level.

Error Handling — Most Important

Contract reverts — user sees execution reverted. Useless. Need to:

  1. Decode custom error from revert data
  2. Translate to a human message
  3. Add context (which operation, with what parameters)
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", 
            `Insufficient balance: requires ${formatUnits(decoded.args[0], 18)} tokens`);
        case "Unauthorized":
          return new SdkError("UNAUTHORIZED", "No permissions for this operation");
        default:
          return new SdkError("CONTRACT_ERROR", decoded?.errorName ?? "Unknown contract error");
      }
    }
  }
  return new SdkError("UNKNOWN", "Unexpected error");
}

This is more important than any other SDK part. Developers integrating the contract spend 60% of time debugging errors — good error handling cuts this dramatically.

Multi-Chain Support

Contract on Ethereum and Polygon isn't two different SDKs, it's one with configuration:

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 Testing

Unit tests via viem testClient + anvil (local mainnet fork):

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 forks mainnet with all state — we test against real contracts, not mocks.

Documentation and Publishing

Generate documentation via TypeDoc from JSDoc comments. Publish on npm (private registry for closed projects, public for open source).

Versioning — semver: patch for bugfixes, minor for new features without breaking changes, major for API changes.

Timeline for developing a basic SDK for one contract: 3-4 days. SDK with multi-chain support, full error coverage, tests, and documentation — 5-7 days.