controller-native
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseController Native Integration
Controller 原生集成
Integrate Controller into native and mobile applications.
将Controller集成到原生及移动应用中。
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 |
| 方案 | 使用场景 | 支持平台 |
|---|---|---|
| 原生绑定(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
认证流程
- App generates local keypair
- User authenticates in browser (deep link)
- Browser redirects back with session credentials
- App signs transactions locally
- 应用生成本地密钥对
- 用户在浏览器中完成认证(通过深层链接)
- 浏览器携带会话凭证重定向回应用
- 应用在本地对交易进行签名
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 prebuildtypescript
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 prebuildtypescript
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.xmlxml
<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.xmlxml
<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/presetsjson
{
"apple-app-site-association": {
"webcredentials": {
"apps": ["TEAMID.com.mygame.app"]
}
}
}Replace with your Apple Developer Team ID.
TEAMID要在原生应用中启用Passkey登录,需在预设中配置Apple App Site Association。
添加到:
@cartridge/presetsjson
{
"apple-app-site-association": {
"webcredentials": {
"apps": ["TEAMID.com.mygame.app"]
}
}
}将替换为你的Apple开发者团队ID。
TEAMIDPlatform 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或密钥管理器。