π§ 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)