How I connected a React frontend to an ink! smart contract using Polkadot-API (PAPI)


šŸ‘‹ Hey builders,
Have you ever tried connecting a frontend to a blockchain contract and felt like you were solving a Rubik’s cube in the dark? Yeah… me too. šŸ˜…

That’s why I wrote this guide, to walk you step-by-step through how I managed to connect a React + Vite frontend to an ink! smart contract using PAPI (Polkadot-API).
Spoiler alert: once I figured out the workflow, it felt less like rocket science and more like following a recipe. šŸš€āœØ

Why this guide?

Instead of drowning in docs and scattered tutorials, I wanted a single walkthrough that shows:

  • How to build & deploy an ink! contract.

  • How to generate type-safe bindings with PAPI.

  • How to make a frontend talk to the contract through a wallet.

If you’ve been curious about ink! + PAPI but don’t know where to start, this article is for you. šŸ™Œ

lock in



Why PAPI?

Polkadot-API (PAPI) is the modern, modular JavaScript/TypeScript SDK for building dApps in the Polkadot ecosystem. It provides strong TypeScript support, a light-client-first approach, and SDKs (including an Ink! SDK) that generate typed bindings from contract metadata — making contract interactions safer and easier to write.

What I built

A small React + Vite app that:

  • Connects to a browser wallet (Polkadot.js / Talisman).

  • Reads a PSP22 contract balance (read-only query).

  • Sends a transfer (signed transaction).

The contract metadata is included in the repo and PAPI generated TypeScript descriptors were used to produce strongly typed calls.



Steps I followed (practical)

1. Build the ink! contract
Install cargo-contract and build:

cargo install cargo-contract --force
cargo contract build --release

Enter fullscreen mode

Exit fullscreen mode

cargo contract build produces the contract artifact and the metadata required by PAPI.

2. Generate PAPI descriptors from the .contract

I used the PAPI CLI to add my chain and the contract metadata:

pnpm papi add -w wss:// mychain
pnpm papi ink add ./path/to/.contract
Enter fullscreen mode

Exit fullscreen mode

This creates typed descriptors available as @polkadot-api/descriptors for import in the frontend. The Ink! tooling in PAPI generates types and helpers for dry-runs, deploys, queries, storage access and sending messages.

3. Frontend: create client & Ink SDK

In src/papiClient.ts:

import { createClient } from "polkadot-api";
import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat";
import { getWsProvider } from "polkadot-api/ws-provider/web";
import { createInkSdk } from "@polkadot-api/sdk-ink";
import { contracts, mychain } from "@polkadot-api/descriptors";

const client = createClient(
  withPolkadotSdkCompat(getWsProvider("wss://"))
);
const typedApi = client.getTypedApi(mychain);
const inkSdk = createInkSdk(typedApi, contracts.);
export { inkSdk };

Enter fullscreen mode

Exit fullscreen mode

This gives typed, chain-aware contract helpers.

4. Wallet connection & signer

I used PAPI’s pjs-signer helpers to connect the Polkadot.js/Talisman extension and obtain a polkadotSigner to sign transactions:

import { getInjectedExtensions, connectInjectedExtension } from "polkadot-api/pjs-signer";
const exts = getInjectedExtensions();
const selected = await connectInjectedExtension(exts[0]); // choose extension
const accounts = selected.getAccounts();
const signer = accounts[0].polkadotSigner;

Enter fullscreen mode

Exit fullscreen mode

The signer implements the PolkadotSigner interface that PAPI methods accept.

5. Query and send

Using the Ink SDK:

const instance = inkSdk.getContract(CONTRACT_ADDRESS);

// Query (dry-run)
const q = await instance.query("PSP22::balance_of", {
  origin: alice,
  data: { owner: alice },
});

// Send (signed tx)
const res = await instance.send("PSP22::transfer", {
  origin: alice,
  data: { to: bob, value: 100n },
}).signAndSubmit(signer);

Enter fullscreen mode

Exit fullscreen mode

The SDK exposes .query(...) and .send(...).signAndSubmit(...), very helpful for dry-runs and signed transactions.

Final notes & tips

  • Use the generated descriptors, they save time and reduce errors.
    papi.how

  • Always use dry-runs (.query() or .dryRun()) before sending signed txs to check gas/storage cost.
    papi.how

  • Do not commit private keys to the repo; use browser wallets for signing.
    papi.how

Links & resources

  • PAPI docs & guide (Polkadot Developer Docs).
    docs.polkadot.com

  • PAPI ink! guide (codegen & usage).
    papi.how

  • Ink! SDK docs (createInkSdk, getDeployer, getContract).
    papi.how

  • Signers / pjs-signer usage.
    papi.how

  • cargo contract build (ink! docs).
    ink!

  • Vercel & Netlify deploy docs.
    Vercel

Am done



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *