Skip to content

Getting Started

Controller implements a standard StarkNet account interface and can be seamlessly integrated into your application like any other wallet.

Quick Start

The fastest way to get started is to install the controller package and connect to Cartridge:

import Controller from "@cartridge/controller";
 
const controller = new Controller({});
const account = await controller.connect();
 
// You're ready to execute transactions!

For more advanced use cases, you can also pass dynamic authentication options:

import Controller from "@cartridge/controller";
 
const controller = new Controller({});
 
// Use default signupOptions from constructor
const account = await controller.connect();
 
// Or override with specific options for this connection
const phantomAccount = await controller.connect(["phantom-evm"]);

When connect() is called, users will see an improved controller creation interface with username autocomplete functionality. As users type their username, they'll see matching existing accounts with user profiles, making it easier to connect to existing controllers or choose unique usernames for new accounts.

For session-based applications, users will see permissions organized into clear sections: an expandable "Authorize [game]" card containing contract methods, followed by dedicated spending limit cards for token approvals, making it easy to understand what they're authorizing.

Installation

npm install @cartridge/controller starknet

Basic Usage

Here's a simple example of how to initialize and use the controller:

Without session policies

import Controller from "@cartridge/controller";
 
// All transactions will require manual approval
const controller = new Controller();

With session policies

Pass session policies to the Controller constructor to enable gasless transactions and pre-approved transactions.

import Controller from "@cartridge/controller";
import { SessionPolicies } from "@cartridge/controller";
 
const policies: SessionPolicies = {
  contracts: {
    // Your game contract
    "0x1234...": {
      name: "My Game Contract",
      methods: [
        { name: "move_player", entrypoint: "move_player" },
        { name: "attack", entrypoint: "attack" },
      ],
    },
  },
};
 
// `move_player` and `attack` txs will not require approval
const controller = new Controller({ policies });

With custom configuration

The Controller ships with sensible defaults for chain RPCs, but you can override them if needed.

import { constants } from "starknet";
import Controller from "@cartridge/controller";
 
const controller = new Controller({
  // Optional chain configuration
  chains: [
    { rpcUrl: "https://api.cartridge.gg/x/starknet/sepolia" },
    { rpcUrl: "https://api.cartridge.gg/x/starknet/mainnet" },
  ],
  defaultChainId: constants.StarknetChainId.SN_MAIN,
});

Standalone Authentication

The Controller provides an open() method for standalone authentication, which opens the keychain in first-party context. This is useful for establishing first-party storage and enabling seamless iframe access across all games.

import Controller from "@cartridge/controller";
 
const controller = new Controller();
 
// Basic usage - redirect to current page after authentication
controller.open();
 
// With custom redirect URL
controller.open({
  redirectUrl: "https://mygame.com/dashboard",
});
 
// With preset theme and redirect
controller.open({
  redirectUrl: "https://mygame.com/play",
  preset: "mygame",
});

Parameters

The open() method accepts an options object with the following properties:

  • redirectUrl?: string - The URL to redirect to after authentication (defaults to current page)
  • preset?: string - The preset theme to use for the keychain interface

Providers

Controller is initialized through a "provider." There are two providers, each with a different security and signing model.

"Connectors" are thin wrappers that plug providers into frameworks like starknet-react.

ControllerProvider (iframe-based)

ControllerProvider is the recommended provider for web applications.

The default ControllerProvider (often exported as Controller) embeds the Cartridge keychain in a sandboxed iframe. Both the owner signer and a session key live inside the iframe, employing a trust model similar to any injected wallet (e.g. MetaMask, Argent).

When your app calls execute(), the request is forwarded to the iframe via postMessage. The keychain first tries to sign with the session key. If the call matches the app's session policies, it signs automatically — no user prompt. If the call doesn't match any policy, it falls back to the owner key and prompts the user for approval.

The session's authorization is cached on-chain on first use, so no explicit registerSession() call is needed.

SessionProvider (redirect-based)

SessionProvider is the recommended provider for native applications.

The SessionProvider is designed for environments where an iframe cannot be used, such as native mobile apps (Capacitor, React Native) or server-side Node.js.

Instead of embedding the owner key in an iframe, it:

  1. Opens a browser to the Cartridge keychain for one-time user authentication
  2. Generates an ephemeral session keypair locally
  3. Registers the session key on-chain with the approved policies compiled into a merkle root
  4. Stores the session private key locally (in localStorage or on the filesystem)

After registration, transactions are signed with the session key and executed via executeFromOutside() — no further UI is needed. Because the session key is not the owner key, policies are enforced on-chain — every transaction must include a merkle proof showing the call matches the registered policies. The owner key is never exposed through this provider, so there is no fallback for calls outside the approved policies.

When to use which

ControllerProviderSessionProvider
EnvironmentWeb browsersNative apps, Node.js, or environments without iframe support
SigningSession key in iframe (owner key fallback)Ephemeral session key stored locally
Policy enforcementKeychain (wallet-level)On-chain (merkle proofs)
Non-policy callsPrompts user, signs with owner keyNot supported
Auth UXEmbedded keychain modalBrowser redirect + deep link back
After authIframe signs each transactionTransactions execute without UI

ControllerConnector (Web)

ControllerConnector wraps ControllerProvider for use with frontend frameworks like starknet-react.

import React from "react";
 
import { constants } from "starknet";
import { sepolia, mainnet } from "@starknet-react/chains";
import { StarknetConfig, jsonRpcProvider, cartridge } from "@starknet-react/core";
 
import { SessionPolicies } from "@cartridge/controller";
import { ControllerConnector } from "@cartridge/connector";
 
const policies: SessionPolicies = {
  // Define session policies here
};
 
// Create the controller connector
const controller = new ControllerConnector({
  policies,
});
 
// Configure the JSON RPC provider
const provider = jsonRpcProvider({
  rpc: (chain) => {
    switch (chain) {
      case mainnet:
      default:
        return { nodeUrl: "https://api.cartridge.gg/x/starknet/mainnet" };
      case sepolia:
        return { nodeUrl: "https://api.cartridge.gg/x/starknet/sepolia" };
    }
  },
});
 
// Create the Starknet provider
export function StarknetProvider({ children }: { children: React.ReactNode }) {
  return (
    <StarknetConfig
      defaultChainId={mainnet.id}
      chains={[mainnet, sepolia]}
      provider={provider}
      connectors={[controller]}
      explorer={cartridge}
    >
      {children}
    </StarknetConfig>
  );
}

Dynamic Authentication Options

The ControllerConnector supports dynamic authentication configuration per connection, enabling multiple branded authentication flows:

import { useConnect } from "@starknet-react/core";
import { ControllerConnector } from "@cartridge/connector";
 
function MyConnectComponent() {
  const { connect } = useConnect();
  const connector = ControllerConnector.fromConnectors(connectors);
 
  return (
    <div>
      {/* Use default signupOptions from constructor */}
      <button onClick={() => connect({ connector })}>
        Connect
      </button>
 
      {/* Override with specific options for branded auth flows */}
      <button onClick={() => connector.connect({ signupOptions: ["phantom-evm"] })}>
        Connect with Phantom
      </button>
 
      <button onClick={() => connector.connect({ signupOptions: ["google"] })}>
        Connect with Google
      </button>
 
      <button onClick={() => connector.connect({ signupOptions: ["discord"] })}>
        Connect with Discord
      </button>
    </div>
  );
}

Wallet Standard Integration

By default, the Controller exposes the StarknetWindowObject interface. However, the ControllerConnector also supports the get-starknet wallet standard, enabling integration with libraries like starknet-react and solid.js.

You can use the ControllerConnector to cast Controller to the wallet-standard compatible WalletWithStarknetFeatures interface:

import { ControllerConnector } from "@cartridge/connector";
import type { WalletWithStarknetFeatures } from "@starknet-io/get-starknet-wallet-standard/features";
 
// Now compatible with any library that expects WalletWithStarknetFeatures
const controller: WalletWithStarknetFeatures = (new ControllerConnector()).asWalletStandard();

SessionConnector (Native)

SessionConnector wraps SessionProvider for use in native and mobile applications. It requires explicit rpc and chainId parameters because it operates outside a framework like starknet-react that would normally provide these. The redirectUrl is where the browser will redirect after authentication completes.

import { constants } from "starknet";
 
import { SessionPolicies } from "@cartridge/controller";
import { SessionConnector } from "@cartridge/connector";
 
const policies: SessionPolicies = {
  // Define session policies here
 };
 
const sessionConnector = new SessionConnector({
  policies,
  rpc: "https://api.cartridge.gg/x/starknet/mainnet",  // Required: no framework provider
  chainId: constants.StarknetChainId.SN_MAIN,          // Required: no framework provider
  redirectUrl: "myapp://auth-callback",                // Required: where to redirect after auth
  // Optional: specify keychain URL for custom deployments
  keychainUrl: "https://x.cartridge.gg",
});

Migration Notes for v0.10.0

StarkNet v8 Breaking Changes

If you're using WalletAccount directly in your application, you'll need to update the constructor call:

// Before (v0.9.x)
const account = new WalletAccount(provider, address, signer);
 
// After (v0.10.0)
const account = new WalletAccount({
  nodeUrl: provider,
  address: address,
  signer: signer
});

Ethereum Wallet Integration Changes

The MetaMask SDK has been removed in favor of the EIP-6963 standard. If your application relied on MetaMask SDK-specific features:

  • Wallet detection now uses EIP-6963 standard wallet detection
  • All Ethereum wallets are now handled through a shared base class
  • This change improves bundle size and wallet compatibility

Lodash Removal

The lodash dependency has been completely removed. If you were importing lodash utilities from this package, you'll need to replace them with custom utilities or install lodash separately in your application.

Development Workflow

If you're contributing to the Cartridge Controller or running the examples locally, you have two development modes available:

Local Development Mode

pnpm dev

This runs all services locally with local API endpoints, perfect for offline development and testing changes to the controller itself.

Production API Testing Mode

pnpm dev:live

This hybrid mode runs the keychain and examples locally while connecting to production APIs. The dev:live mode provides production RPC endpoints, Auth0, Stripe, and Turnkey configurations while keeping the keychain frame at localhost:3001 and your application at localhost:3002.

Examples

For more detailed examples of how to use Cartridge Controller in different environments, check out our integration guides:

  1. React

    • Integration with starknet-react
    • Hooks and components
    • State management
  2. Svelte

    • Svelte stores and reactivity
    • Component lifecycle
    • Event handling
  3. Rust

    • Native integration
    • Error handling
    • Async operations

Each guide provides comprehensive examples and best practices for integrating Cartridge Controller in your preferred environment.

Next Steps