๐ง 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_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
๐งฉ 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
RpcProvider
and a read-onlyContract
instance.
3. Write to Counter
- Uses
Contract
with 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
RpcProvider
for reads, and walletaccount
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
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)