controller-native

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Controller Native Integration

Controller 原生集成

Integrate Controller into native and mobile applications.
将Controller集成到原生及移动应用中。

Choosing an Approach

选择集成方案

ApproachUse WhenPlatforms
Native Bindings (Controller.c)Performance-critical, native appsiOS, Android, React Native, C/C++
Web Wrapper (Capacitor)Existing web app → app storeiOS, Android
方案使用场景支持平台
原生绑定(Controller.c)对性能要求高的原生应用iOS、Android、React Native、C/C++
Web包装器(Capacitor)现有Web应用转上架应用商店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应用的浏览器端认证
  • SessionAccount:使用Controller.c FFI绑定的原生应用
  • ControllerAccount:使用自定义签名密钥的无头/服务器端场景

SessionConnector (Web/Capacitor)

SessionConnector(Web/Capacitor)

For web apps wrapped with Capacitor:
typescript
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",
});
针对使用Capacitor包装的Web应用:
typescript
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
  1. 应用生成本地密钥对
  2. 用户在浏览器中完成认证(通过深层链接)
  3. 浏览器携带会话凭证重定向回应用
  4. 应用在本地对交易进行签名

React Native

React Native

Prerequisites: Node.js >= 20
Uses TurboModules with Controller.c bindings. The native module is generated locally:
bash
pnpm install
pnpm exec expo prebuild
typescript
import { 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);
前置要求:Node.js >= 20
使用带有Controller.c绑定的TurboModules,原生模块将在本地生成:
bash
pnpm install
pnpm exec expo prebuild
typescript
import { SessionAccount, type SessionPolicy, type Call } from "./modules/controller/src";

// 从订阅流程创建会话
const session = SessionAccount.createFromSubscribe(
  privateKey,
  sessionPolicies,
  "https://api.cartridge.gg/x/starknet/mainnet",
  "https://api.cartridge.gg"
);

// 访问会话元数据
const address = session.address();
const username = session.username();

// 执行交易
const calls: Call[] = [
  {
    contractAddress: "0x...",
    entrypoint: "transfer",
    calldata: ["0x...", "0x100", "0x0"],
  },
];
const txHash = session.executeFromOutside(calls);

iOS (Swift)

iOS(Swift)

Prerequisites: Xcode 15+
Uses UniFFI-generated Swift bindings.
swift
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)")
}
前置要求:Xcode 15+
使用UniFFI生成的Swift绑定。
swift
import ControllerAccount

// 从私钥创建所有者实例
let owner = try Owner.init(privateKey: "0x...")

// 创建无头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"
)

// 执行交易
let call = Call(
    contractAddress: "0x...",
    entrypoint: "transfer",
    calldata: ["0x...", "0x100", "0x0"]
)

do {
    let txHash = try await controller.execute([call])
    print("交易哈希: \(txHash)")
} catch let error as ControllerError {
    print("错误: \(error.message)")
}

Android (Kotlin)

Android(Kotlin)

Prerequisites: Rust toolchain for building native libraries
Uses UniFFI-generated Kotlin bindings.
kotlin
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}")
}
前置要求:用于构建原生库的Rust工具链
使用UniFFI生成的Kotlin绑定。
kotlin
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("交易哈希: $txHash")
} catch (e: ControllerException) {
    println("错误: ${e.message}")
}

Capacitor (Web Wrapper)

Capacitor(Web包装器)

iOS Configuration

iOS配置

typescript
// capacitor.config.ts
export default {
  appId: "com.mygame.app",
  plugins: {
    App: {
      launchUrl: "myapp://",
    },
  },
};
typescript
// capacitor.config.ts
export default {
  appId: "com.mygame.app",
  plugins: {
    App: {
      launchUrl: "myapp://",
    },
  },
};

Android Configuration

Android配置

Add to
AndroidManifest.xml
:
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>
添加到
AndroidManifest.xml
:
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

处理深层链接

typescript
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.
typescript
import { App } from "@capacitor/app";

App.addListener("appUrlOpen", (event) => {
  if (event.url.includes("auth-callback")) {
    sessionConnector.handleCallback(event.url);
  }
});
注意:WebAuthn在Android WebView中支持有限。

Passkey Configuration (AASA)

Passkey配置(AASA)

To enable passkey sign-in on native apps, configure Apple App Site Association in your preset.
Add to
@cartridge/presets
:
json
{
  "apple-app-site-association": {
    "webcredentials": {
      "apps": ["TEAMID.com.mygame.app"]
    }
  }
}
Replace
TEAMID
with your Apple Developer Team ID.
要在原生应用中启用Passkey登录,需在预设中配置Apple App Site Association。
添加到
@cartridge/presets
:
json
{
  "apple-app-site-association": {
    "webcredentials": {
      "apps": ["TEAMID.com.mygame.app"]
    }
  }
}
TEAMID
替换为你的Apple开发者团队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.
  • OAuth限制:社交登录在WebView中可能无法正常工作,原生应用优先使用Passkey。
  • EVM钱包:仅支持桌面端,在移动浏览器中会自动隐藏。
  • Passkey:原生iOS应用需要配置AASA。
  • Android WebAuthn:在Capacitor WebView中支持有限。

Security

安全提示

Warning: Never commit private keys. Use environment variables, secure storage APIs, or secret managers.
警告:切勿提交私钥。请使用环境变量、安全存储API或密钥管理器。