DEV Community

Cover image for πŸš€ Building and Interacting with a StarkNet Contract using React and StarkNet.js
Aditya41205
Aditya41205

Posted on

πŸš€ Building and Interacting with a StarkNet Contract using React and StarkNet.js

🧠 Introduction

As blockchain scalability becomes increasingly important, Layer 2 solutions are gaining momentum. Among them, StarkNet stands out due to its use of zero-knowledge proofs (ZK-STARKs) to enable scalable and secure computations. However, StarkNet development involves a unique stack β€” from Cairo smart contracts to frontend integration via StarkNet.js.

In this article, you'll learn how to:

  • Write a Cairo 1.0 smart contract with access control.
  • Deploy it on the StarkNet testnet.
  • Build a frontend using React.
  • Connect it to wallets like Argent X or Braavos.
  • Interact with the contract using StarkNet.js.

🧱 Writing the Cairo Smart Contract

Let’s start with a simple secure counter contract.

Only the contract owner can increment the counter, while anyone can view it.

🧾 Contract Code (Cairo 1.0)

#[starknet::interface]
trait ICounterContract<TContractState> {
    fn get_counter(self: @TContractState) -> u32;
    fn increase_counter(ref self: TContractState);
}

#[starknet::contract]
mod counter {
    use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
    use starknet::get_caller_address;
    use starknet::ContractAddress;

    #[storage]
    struct Storage {
        counter: u32,
        owner: ContractAddress,
    }

    #[constructor]
    fn constructor(ref self: ContractState, x: u32, owner: ContractAddress) {
        self.counter.write(x);
        self.owner.write(owner);
    }

    #[abi(embed_v0)]
    impl abc of super::ICounterContract<ContractState> {
        fn get_counter(self: @ContractState) -> u32 {
            self.counter.read()
        }

        fn increase_counter(ref self: ContractState) {
            let caller = get_caller_address();
            let owner = self.owner.read();
            assert(caller == owner, 'Owner can only call');
            let current = self.counter.read();
            self.counter.write(current + 1);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ” Key Features

  • Access Control: increase_counter is restricted to the owner.
  • View Function: get_counter is publicly accessible.
  • Constructor: Initializes counter value and sets the contract owner.

You can compile this with Scarb, and deploy using tools like starkli.


🌐 Building the React Frontend

Now, let’s build a minimal React frontend to interact with the smart contract.

πŸ›  Tools Used

  • React for UI
  • StarkNet.js for smart contract interactions
  • RpcProvider to read blockchain state
  • window.starknet to connect wallet extensions like Argent X or Braavos

πŸ“ 1. Setting Up the Project

npx create-react-app starknet-dapp
cd starknet-dapp
npm install starknet
Enter fullscreen mode Exit fullscreen mode

🧩 2. Complete React Code

import React, { useEffect, useState } from "react";
import { RpcProvider, Contract } from "starknet";

const CONTRACT_ADDRESS = "0xYOUR_CONTRACT_ADDRESS"; // Replace with actual deployed address
const ABI = [/* ABI JSON here */];

const rpc = new RpcProvider({
  nodeUrl: "https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_8/YOUR_API_KEY",
});

function App() {
  const [walletConnected, setWalletConnected] = useState(false);
  const [counter, setCounter] = useState(null);
  const [contract, setContract] = useState(null);
  const [userAddress, setUserAddress] = useState("");

  const connectWallet = async () => {
    if (!window.starknet) {
      alert("Install Argent X or Braavos");
      return;
    }

    try {
      await window.starknet.enable();
      const account = window.starknet.account;
      const ctr = new Contract(ABI, CONTRACT_ADDRESS, account);
      setContract(ctr);
      setWalletConnected(true);
      setUserAddress(account.address.slice(0, 6) + "..." + account.address.slice(-4));
    } catch (err) {
      console.error("Wallet connection failed:", err);
    }
  };

  const fetchCounter = async () => {
    try {
      const readContract = new Contract(ABI, CONTRACT_ADDRESS, rpc);
      const res = await readContract.get_counter();
      setCounter(res.toString(10));
    } catch (err) {
      console.error("Failed to fetch counter:", err);
    }
  };

  const increaseCounter = async () => {
    try {
      const tx = await contract.increase_counter();
      await rpc.waitForTransaction(tx.transaction_hash);
      fetchCounter();
    } catch (err) {
      console.error("Transaction failed:", err);
    }
  };

  return (
    <div className="App">
      <h1>StarkNet Counter</h1>
      {!walletConnected ? (
        <button onClick={connectWallet}>Connect Wallet</button>
      ) : (
        <>
          <p>Connected: {userAddress}</p>
          <button onClick={fetchCounter}>Get Counter</button>
          <p>Counter: {counter !== null ? counter : "Not fetched"}</p>
          <button onClick={increaseCounter}>Increase Counter</button>
        </>
      )}
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

πŸ”„ Interaction Flow

1. Connect Wallet

  • Uses window.starknet.enable() to initiate connection.

2. Read Counter

  • Uses RpcProvider and a read-only Contract instance.

3. Write to Counter

  • Uses Contract with the connected wallet account to call increase_counter().

βœ… Deployment & Testing

Once your contract is deployed:

  • Use Voyager to inspect it.
  • Interact using your React dApp.
  • Refer to StarkNet.js docs for more interaction patterns.
  • Use starkli for testing locally.

🧠 Learnings and Best Practices

  • Split Read/Write Logic: Use RpcProvider for reads, and wallet account for writes.
  • Use Clean ABIs: Flatten or manually simplify ABI if needed.
  • Wallet Support: Ensure compatibility with Argent X & Braavos.
  • Handle Errors: Wrap all async calls with try/catch.

Interactions:

Wallet connection

                   Wallet connection
Enter fullscreen mode Exit fullscreen mode

Interface

                        Interface
Enter fullscreen mode Exit fullscreen mode

Invoking Read function

                   Invoking Read function
Enter fullscreen mode Exit fullscreen mode

Invoking Write function

                  Invoking Write function
Enter fullscreen mode Exit fullscreen mode

🧾 Conclusion

With the rise of ZK-powered Layer 2s, StarkNet offers a powerful and developer-friendly ecosystem for writing scalable dApps.

You just:

  • Wrote a secure Cairo contract βœ…
  • Deployed it on StarkNet testnet βœ…
  • Built a full React frontend βœ…
  • Integrated wallet connectivity βœ…
  • Interacted with it using StarkNet.js βœ…

Top comments (0)