
Controller Native
- 62 installs
- 4 repo stars
- Updated August 1, 2026
- cartridge-gg/docs
Integrate Cartridge Controller into native iOS, Android, React Native, or Capacitor apps with local keypair signing and passkeys.
About
Guides integrating Cartridge Controller into native mobile and Capacitor web-wrapper apps with local keypair signing. A developer uses it to add Controller wallet functionality to iOS, Android, or React Native games.
- Integrates Controller into iOS, Android, React Native, and Capacitor apps
- Covers SessionConnector, Controller.c FFI bindings, and passkey AASA setup
Controller Native by the numbers
- 62 all-time installs (skills.sh)
- Ranked #580 of 1,039 Mobile Development 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-nativeAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 62 |
|---|---|
| repo stars | ★ 4 |
| Last updated | August 1, 2026 |
| Repository | cartridge-gg/docs ↗ |
What it does
Integrate Cartridge Controller into native iOS, Android, React Native, or Capacitor apps with local keypair signing and passkeys.
Files
Controller Native Integration
Integrate Controller into native and mobile applications.
Choosing an Approach
| Approach | Use When | Platforms |
|---|---|---|
| Native Bindings (Controller.c) | Performance-critical, native apps | iOS, Android, React Native, C/C++ |
| Web Wrapper (Capacitor) | Existing web app → app store | iOS, Android |
Key Concepts
- SessionConnector: Web/Capacitor apps with browser-based auth
- SessionAccount: Native apps using Controller.c FFI bindings
- ControllerAccount: Headless/server-side with custom signing keys
SessionConnector (Web/Capacitor)
For web apps wrapped with Capacitor:
import { SessionConnector } from "@cartridge/connector";
import { constants } from "starknet";
const policies = {
contracts: {
"0x1234...": {
methods: [{ name: "play", entrypoint: "play" }],
},
},
};
const connector = new SessionConnector({
policies,
rpc: "https://api.cartridge.gg/x/starknet/mainnet",
chainId: constants.StarknetChainId.SN_MAIN,
redirectUrl: "myapp://auth-callback",
disconnectRedirectUrl: "myapp://logout",
});Authentication Flow
1. App generates local keypair 2. User authenticates in browser (deep link) 3. Browser redirects back with session credentials 4. App signs transactions locally
React Native
Prerequisites: Node.js >= 20
Uses TurboModules with Controller.c bindings. The native module is generated locally:
pnpm install
pnpm exec expo prebuildimport { SessionAccount, type SessionPolicy, type Call } from "./modules/controller/src";
// Create session from subscription flow
const session = SessionAccount.createFromSubscribe(
privateKey,
sessionPolicies,
"https://api.cartridge.gg/x/starknet/mainnet",
"https://api.cartridge.gg"
);
// Access session metadata
const address = session.address();
const username = session.username();
// Execute transactions
const calls: Call[] = [
{
contractAddress: "0x...",
entrypoint: "transfer",
calldata: ["0x...", "0x100", "0x0"],
},
];
const txHash = session.executeFromOutside(calls);iOS (Swift)
Prerequisites: Xcode 15+
Uses UniFFI-generated Swift bindings.
import ControllerAccount
// Create owner from private key
let owner = try Owner.init(privateKey: "0x...")
// Create headless controller
let controller = try ControllerAccount.newHeadless(
appId: "my_app",
username: "player",
classHash: ControllerAccount.getControllerClassHash(.latest),
rpcUrl: "https://api.cartridge.gg/x/starknet/mainnet",
owner: owner,
chainId: "0x534e5f4d41494e"
)
// Execute transaction
let call = Call(
contractAddress: "0x...",
entrypoint: "transfer",
calldata: ["0x...", "0x100", "0x0"]
)
do {
let txHash = try await controller.execute([call])
print("Transaction: \(txHash)")
} catch let error as ControllerError {
print("Error: \(error.message)")
}Android (Kotlin)
Prerequisites: Rust toolchain for building native libraries
Uses UniFFI-generated Kotlin bindings.
import uniffi.controller.*
val owner = ownerInit("0x...")
val controller = controllerNewHeadless(
appId = "my_app",
username = "player",
classHash = getControllerClassHash(Version.LATEST),
rpcUrl = "https://api.cartridge.gg/x/starknet/mainnet",
owner = owner,
chainId = "0x534e5f4d41494e"
)
val call = Call(
contractAddress = "0x...",
entrypoint = "transfer",
calldata = listOf("0x...", "0x100", "0x0")
)
try {
val txHash = controller.execute(listOf(call))
println("Transaction: $txHash")
} catch (e: ControllerException) {
println("Error: ${e.message}")
}Capacitor (Web Wrapper)
iOS Configuration
// capacitor.config.ts
export default {
appId: "com.mygame.app",
plugins: {
App: {
launchUrl: "myapp://",
},
},
};Android Configuration
Add to AndroidManifest.xml:
<activity
android:name=".MainActivity"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" />
</intent-filter>
</activity>Handle Deep Links
import { App } from "@capacitor/app";
App.addListener("appUrlOpen", (event) => {
if (event.url.includes("auth-callback")) {
sessionConnector.handleCallback(event.url);
}
});Note: WebAuthn has limited support on Android webviews.
Passkey Configuration (AASA)
To enable passkey sign-in on native apps, configure Apple App Site Association in your preset.
Add to @cartridge/presets:
{
"apple-app-site-association": {
"webcredentials": {
"apps": ["TEAMID.com.mygame.app"]
}
}
}Replace TEAMID with your Apple Developer Team ID.
Platform Notes
- OAuth limitation: Social login may not work in webviews. Prefer passkeys for native.
- EVM wallets: Desktop only, auto-hidden on mobile browsers.
- Passkeys: Require AASA configuration for native iOS apps.
- Android WebAuthn: Limited support in Capacitor webviews.
Security
Warning: Never commit private keys. Use environment variables, secure storage APIs, or secret managers.