π§ 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);
        }
    }
}
π Key Features
- 
Access Control: increase_counteris restricted to the owner.
- 
View Function: get_counteris 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
π§© 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;
π Interaction Flow
1. Connect Wallet
- Uses window.starknet.enable()to initiate connection.
2. Read Counter
- Uses RpcProviderand a read-onlyContractinstance.
3. Write to Counter
- Uses Contractwith the connected wallet account to callincrease_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 RpcProviderfor reads, and walletaccountfor 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
                        Interface
                   Invoking Read function
                  Invoking Write function
π§Ύ 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)