controller-backend
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
basePathspecifies 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
- Configure session policies
- Set up SessionProvider with cloud storage
- Handle authentication redirects
- 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;
},
};
More from cartridge-gg/docs
controller-setup
Integrate Cartridge Controller wallet into Starknet applications. Use when setting up Controller for the first time, installing packages, configuring chains/RPC endpoints, or troubleshooting basic integration issues. Covers installation, Controller instantiation, ControllerConnector vs SessionConnector choice, chain configuration, and package compatibility.
69controller-sessions
Configure session keys and policies for Cartridge Controller to enable gasless, pre-approved transactions. Use when defining contract interaction policies, setting spending limits, configuring signed message policies, or implementing error handling for session-based transactions. Covers SessionPolicies type, policy definitions, verified sessions, and error display modes.
68controller-react
Integrate Cartridge Controller into React applications using starknet-react. Use when building React/Next.js web apps with Controller, setting up StarknetConfig provider, using hooks like useConnect/useAccount, or implementing wallet connection components. Covers ControllerConnector setup, provider configuration, and transaction execution patterns.
67controller-signers
Configure authentication methods for Cartridge Controller including passkeys, social login, and external wallets. Use when implementing user authentication, adding multiple signers for account recovery, customizing signup options, or integrating external wallets like MetaMask or Phantom. Covers WebAuthn passkeys, Google/Discord/Twitter OAuth, wallet connections, and dynamic authentication flows.
65slot-vrng
Integrate Cartridge's verifiable random number generator (vRNG) into onchain games.
59slot-rpc
Configure Cartridge RPC endpoints with API token authentication and CORS whitelisting.
58