Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
cartridge-gg avatar

Controller Backend

  • 62 installs
  • 4 repo stars
  • Updated August 1, 2026
  • cartridge-gg/docs

Integrate Cartridge Controller into Node.js, Rust, or headless backends to execute pre-approved Starknet transactions without manual approval.

About

Guides integrating Cartridge Controller into server-side and headless environments using SessionProvider, the Rust SDK, or custom signing keys. A developer uses it to build game backends, bots, or automated systems that execute Starknet transactions.

  • Integrates Controller into Node.js, Rust, or headless backends
  • Uses SessionProvider for pre-approved Starknet transactions without manual approval

Controller Backend by the numbers

  • 62 all-time installs (skills.sh)
  • Ranked #192 of 480 Web3 & Blockchain skills by installs in the Skillselion catalog
  • Data as of Aug 1, 2026 (Skillselion catalog sync)
npx skills add https://github.com/cartridge-gg/docs --skill controller-backend

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs62
repo stars4
Last updatedAugust 1, 2026
Repositorycartridge-gg/docs

What it does

Integrate Cartridge Controller into Node.js, Rust, or headless backends to execute pre-approved Starknet transactions without manual approval.

Files

SKILL.mdMarkdownGitHub ↗

Controller Backend Integration

Integrate Controller into server-side applications and automated systems.

Sessions enable pre-approved transactions without manual user approval, making them ideal for automated backends.

Node.js

Uses SessionProvider with file-based session storage.

Installation

pnpm add @cartridge/controller starknet

Setup

import SessionProvider, {
  ControllerError,
} from "@cartridge/controller/session/node";
import { constants } from "starknet";
import path from "path";

const ETH_CONTRACT =
  "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7";

async function main() {
  const storagePath =
    process.env.CARTRIDGE_STORAGE_PATH ||
    path.join(process.cwd(), ".cartridge");

  const provider = new SessionProvider({
    rpc: "https://api.cartridge.gg/x/starknet/mainnet",
    chainId: constants.StarknetChainId.SN_MAIN,
    policies: {
      contracts: {
        [ETH_CONTRACT]: {
          methods: [
            {
              name: "approve",
              entrypoint: "approve",
              spender: "0x1234567890abcdef1234567890abcdef12345678",
              amount: "0xffffffffffffffffffffffffffffffff",
              description: "Approve spending of tokens",
            },
            { name: "transfer", entrypoint: "transfer" },
          ],
        },
      },
    },
    basePath: storagePath,
  });

  try {
    const account = await provider.connect();

    if (account) {
      console.log("Account address:", account.address);

      // Example: Transfer ETH
      const amount = "0x0";
      const recipient = account.address; // Replace with actual recipient

      const result = await account.execute([
        {
          contractAddress: ETH_CONTRACT,
          entrypoint: "transfer",
          calldata: [recipient, amount, "0x0"],
        },
      ]);

      console.log("Transaction hash:", result.transaction_hash);
    } else {
      console.log("Please complete the session creation in your browser");
    }
  } catch (error: unknown) {
    const controllerError = error as ControllerError;
    if (controllerError.code) {
      console.error("Session error:", {
        code: controllerError.code,
        message: controllerError.message,
        data: controllerError.data,
      });
    } else {
      console.error("Session error:", error);
    }
  }
}

main().catch(console.error);

Notes

  • basePath specifies session storage directory (must be writable)
  • First run requires browser-based session creation
  • Session persists between runs

Rust

Uses account_sdk crate for native Rust integration.

Installation

[dependencies]
account_sdk = { git = "https://github.com/cartridge-gg/controller-rs.git", package = "account_sdk" }
starknet = "0.10"
tokio = { version = "1", features = ["full"] }

Setup

use account_sdk::{controller::Controller, signers::Signer};
use starknet::{
    accounts::Account,
    providers::Provider,
    signers::SigningKey,
    core::types::FieldElement,
};
use std::env;

#[tokio::main]
async fn main() {
    // Load private key from environment
    let private_key = env::var("PRIVATE_KEY")
        .expect("PRIVATE_KEY must be set");

    let owner = Signer::Starknet(
        SigningKey::from_secret_scalar(
            FieldElement::from_hex_be(&private_key).unwrap()
        )
    );

    let rpc_url = "https://api.cartridge.gg/x/starknet/mainnet";
    let provider = Provider::try_from(rpc_url).unwrap();
    let chain_id = provider.chain_id().await.unwrap();

    let controller = Controller::new(
        "my_app".to_string(),
        "username".to_string(),
        FieldElement::from_hex_be("0xCLASS_HASH").unwrap(),
        rpc_url.parse().unwrap(),
        owner,
        FieldElement::from_hex_be("0xCONTROLLER_ADDRESS").unwrap(),
        chain_id,
    );

    // Deploy if needed
    controller.deploy().await.unwrap();

    // Execute transaction
    let call = starknet::core::types::FunctionCall {
        contract_address: FieldElement::from_hex_be("0xCONTRACT").unwrap(),
        entry_point_selector: starknet::core::utils::get_selector_from_name("transfer").unwrap(),
        calldata: vec![FieldElement::from(123)],
    };

    controller.execute(vec![call], None).await.unwrap();
}

Headless Controller

For server-side execution with custom signing keys.

Use Cases

  • Single owner, multiple accounts
  • Automated game backends (bots, NPCs)
  • Custom key management requirements

C++ Example

Warning: Never commit private keys. Use environment variables or secret managers.
#include "controller.hpp"
#include <cstdlib>

int main() {
    // Load from environment - NEVER hardcode!
    const char* pk = std::getenv("PRIVATE_KEY");
    if (!pk) {
        std::cerr << "PRIVATE_KEY not set" << std::endl;
        return 1;
    }
    std::string private_key(pk);

    auto owner = controller::Owner::init(private_key);

    auto ctrl = controller::Controller::new_headless(
        "my_app",
        "bot_account",
        controller::get_controller_class_hash(controller::Version::kLatest),
        "https://api.cartridge.gg/x/starknet/mainnet",
        owner,
        "0x534e5f4d41494e"  // SN_MAIN
    );

    // Register new account onchain
    ctrl->signup(
        controller::SignerType::kStarknet,
        std::nullopt,
        std::nullopt
    );

    // Execute transaction
    controller::Call call{
        "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
        "transfer",
        {"0xRECIPIENT", "0x100", "0x0"}
    };

    std::string tx_hash = ctrl->execute({call});
    std::cout << "Transaction: " << tx_hash << std::endl;

    return 0;
}

Security Best Practices

Never commit private keys. Use:

  • Environment variables
  • Secret managers (AWS Secrets Manager, HashiCorp Vault)
  • Hardware security modules (HSM)
// Good
const privateKey = process.env.PRIVATE_KEY;

// Bad - NEVER do this
const privateKey = "0x1234...";

Telegram Mini-Apps

Use SessionConnector with Telegram WebApp context.

Integration Flow

1. Configure session policies 2. Set up SessionProvider with cloud storage 3. Handle authentication redirects 4. Execute transactions via session

Setup

import { SessionConnector } from "@cartridge/connector";
import { constants } from "starknet";

const policies = {
  contracts: {
    "0x...": {
      methods: [{ name: "action", entrypoint: "action" }],
    },
  },
};

const connector = new SessionConnector({
  policies,
  rpc: "https://api.cartridge.gg/x/starknet/mainnet",
  chainId: constants.StarknetChainId.SN_MAIN,
  redirectUrl: window.Telegram?.WebApp?.initData
    ? "https://t.me/MyBot/app"
    : "https://myapp.com/callback",
});

Next.js WebAssembly Configuration

For Telegram mini-apps using Next.js:

// next.config.js
module.exports = {
  webpack: (config) => {
    config.experiments = {
      ...config.experiments,
      asyncWebAssembly: true,
    };
    return config;
  },
};

Related skills

Web3 & Blockchainbackendintegrations

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.