Skip to main content

Overview

The repository includes example applications in the examples/ directory:
  1. Flappy Bird (Web) — Cartridge Controller, policies, and paymaster (gasless) on a game contract; contract assumed deployed, calls on-chain. Live demo.
  2. Web Example — Vanilla TypeScript + Vite with multiple wallet connection methods (Cartridge, Privy, WebAuthn, private key).
  3. Mobile Example — React Native + Expo with login, account creation, balances, transfers, staking.
  4. Server Example — Express.js backend for Privy wallet operations and paymaster proxy.
We integrate the Cartridge Controller — a wallet — with session keys (so users don’t sign each transaction) and a paymaster (gasless), all thanks to native account abstraction on Starknet. The demo game contract is already deployed; the app calls it on-chain and shows a leaderboard of scores.Live demo.

Run

cd examples/flappy-bird
npm install
npm run dev
Open https://localhost:xxxx (HTTPS required for Cartridge). Click Connect Controller, sign in, then play; each pipe increments your score on-chain. Use the Leaderboard button to query on-chain scores.

Controller via SDK: session keys + paymaster

  • Controller = wallet: The Cartridge Controller is the user’s wallet (social login / passkey). We connect it through the SDK with policies that define which contract calls the session may make.
  • Session keys: Once the user approves the policies, the session can send those calls without a signature per tx — no popup for each pipe or game over.
  • Paymaster: Matching calls are sponsored (gasless).
  • All of this is built on Starknet’s account abstraction (single approval, then batched/sponsored execution).

Demo contract (already deployed)

Contract on Starknet Sepolia (Voyager): 0x03730b941e8d3ece030a4a0d5f1008f34fbde0976e86577a78648c8b35079464To learn how to create and deploy your own contract on Starknet, check the Quickstart, and Cairo sections.Entrypoints and how they map to Flappy Bird:
EntrypointWhen we call it
start_new_gameUser starts a new round (tap / Space).
increment_scoreBird passes a pipe (each pipe = one call).
end_gameGame over (hit pipe or ground).
get_high_scoreShow player’s best score.
get_current_leaderboard_idCurrent leaderboard period.
get_leaderboardTop scores — used by the Leaderboard button to show on-chain data.

Leaderboard button

A Leaderboard button in the UI calls get_leaderboard() on the contract (via StarkZap’s Contract + wallet.getProvider()) and displays the top scores from chain.

How the controller is integrated and how we call the contract

1. Init SDK and connect Controller (policies = what the session can call):
import { StarkZap, OnboardStrategy, Contract, type RpcProvider } from "starkzap";

const sdk = new StarkZap({ network: "sepolia" });
const policies = [
  { target: GAME_CONTRACT, method: "start_new_game" },
  { target: GAME_CONTRACT, method: "increment_score" },
  { target: GAME_CONTRACT, method: "end_game" },
];
const onboard = await sdk.onboard({
  strategy: OnboardStrategy.Cartridge,
  cartridge: { policies },
  deploy: "never",
});
const wallet = onboard.wallet;
2. Contract calls (writes) — start game, increment score, end game:
await wallet.execute(
  [{ contractAddress: GAME_CONTRACT, entrypoint: "start_new_game", calldata: [] }],
  { feeMode: "sponsored" }
);
await wallet.execute(
  [{ contractAddress: GAME_CONTRACT, entrypoint: "increment_score", calldata: [] }],
  { feeMode: "sponsored" }
);
await wallet.execute(
  [{ contractAddress: GAME_CONTRACT, entrypoint: "end_game", calldata: [] }],
  { feeMode: "sponsored" }
);
3. Read on-chain data (leaderboard, high score) — use provider from wallet:
const provider = wallet.getProvider();
const contract = new Contract({ abi: GAME_ABI, address: GAME_CONTRACT, providerOrAccount: provider });
const leaderboard = await contract.get_leaderboard();
const highScore = await contract.get_high_score(wallet.address.toString(), await contract.get_current_leaderboard_id());

What is a contract ABI?

An ABI (Application Binary Interface) is a description of a smart contract’s public interface: its callable functions (entrypoints), their names, arguments, and return types. We use it above when building the Contract instance for read-only calls (get_leaderboard, get_high_score, etc.): the ABI tells the SDK how to encode the call and how to decode the result. You typically get the ABI from the contract’s project (e.g. compiled artifact) or from an explorer (e.g. Voyager). For writes we only need the entrypoint name and calldata; for reads we pass the ABI into Contract so it can expose typed methods.

Common Patterns

Error Handling

All examples include proper error handling:
try {
  const tx = await wallet.execute(calls);
  await tx.wait();
  console.log("Success!");
} catch (error) {
  console.error("Transaction failed:", error);
  // Show user-friendly error message
}

Loading States

Examples demonstrate loading state management:
function setButtonLoading(btn: HTMLButtonElement, loading: boolean) {
  if (loading) {
    btn.disabled = true;
    btn.innerHTML = '<span class="spinner"></span>';
  } else {
    btn.disabled = false;
    btn.textContent = "Submit";
  }
}

Transaction Tracking

All examples show how to track transaction status:
const tx = await wallet.execute(calls);
console.log(`Tx hash: ${tx.hash}`);
console.log(`Explorer: ${tx.explorerUrl}`);

await tx.wait();
console.log("Transaction confirmed!");

Next Steps

  • Explore the example code in the examples/ directory
  • Adapt the patterns to your own application
  • Check the API Reference for detailed method documentation
  • Review the Troubleshooting guide if you encounter issues