DEV Community

Cover image for How I connected a React frontend to an ink! smart contract using Polkadot-API (PAPI)
Allan Githaiga
Allan Githaiga

Posted on

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 dApp that::

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

  • Reads the current counter value (read-only query).

  • Increments or decrements the counter (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

This produces the .contract artifact and 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 ink add -k counter ./contracts/counter.contract
pnpm papi generate
Enter fullscreen mode Exit fullscreen mode

This generates typed descriptors in frontend/.papi/ which are importable in the frontend.

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 } from "@polkadot-api/descriptors";

const client = createClient(
  withPolkadotSdkCompat(getWsProvider("wss://westend-rpc.polkadot.io"))
);

const inkSdk = createInkSdk(client.getTypedApi("westend"), contracts.counter);

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 counter
const current = await instance.query("get", { origin: accounts[0].address });
console.log("Counter value:", current.output);

// Increment
await instance.send("increment", { origin: accounts[0].address })
  .signAndSubmit(signer);

// Decrement
await instance.send("decrement", { origin: accounts[0].address })
  .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

Top comments (1)

Collapse
 
prime_1 profile image
Roshan Sharma

Cool project! Connecting React with an Ink! A smart contract using Polkadot’s API is legit impressive.