iOS Integration
Cartridge Controller provides native iOS support through UniFFI-generated Swift bindings. This enables seamless integration with iOS applications while maintaining full access to Controller functionality.
Prerequisites
- Xcode 15+
- iOS 17+ deployment target
- Swift 5.5+
- Rust toolchain (for building the library)
Installation
Build the Swift bindings from the Controller.c repository:
cd controller.c
cargo build --release
./scripts/build_swift.shThis generates:
libcontroller_uniffi.dylib- The native librarycontroller_uniffi.swift- Swift wrappercontroller_uniffiFFI.h- C headers for bridging
Add these files to your Xcode project and configure:
- Bridging Header: Include
controller_uniffiFFI.h - Other Linker Flags:
-lcontroller_uniffi - Library Search Paths: Path to
target/release/
Core Types
FieldElement
Blockchain field elements are represented as strings:
typealias ControllerFieldElement = StringCall
Represents a contract call:
struct Call {
var contractAddress: ControllerFieldElement
var entrypoint: String
var calldata: [ControllerFieldElement]
}Session Policies
Define permissions for session accounts:
struct SessionPolicy {
var contractAddress: ControllerFieldElement
var entrypoint: String
}
struct SessionPolicies {
var policies: [SessionPolicy]
var maxFee: ControllerFieldElement
}Creating a ControllerAccount
Initialize a headless ControllerAccount with your own signing key:
import Foundation
let owner = try Owner(privateKey: "0x...")
let classHash = try getControllerClassHash(version: .latest)
let controller = try ControllerAccount.newHeadless(
appId: "my_app",
username: "player123",
classHash: classHash,
rpcUrl: "https://api.cartridge.gg/x/starknet/sepolia",
owner: owner,
chainId: "0x534e5f5345504f4c4941" // SEPOLIA
)
// Access controller properties
let address = try controller.address()
let username = try controller.username()Creating a SessionAccount
Generate a keypair and create a session via browser authorization:
// Generate random private key
var bytes = [UInt8](repeating: 0, count: 32)
SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)
let privateKey = "0x" + bytes.map { String(format: "%02x", $0) }.joined()
let publicKey = try getPublicKey(privateKey: privateKey)
// Define policies
let policies = SessionPolicies(
policies: [
SessionPolicy(
contractAddress: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
entrypoint: "transfer"
)
],
maxFee: "0x2386f26fc10000"
)
// Create session from API subscription
let session = try SessionAccount.createFromSubscribe(
privateKey: privateKey,
policies: policies,
rpcUrl: "https://api.cartridge.gg/x/starknet/sepolia",
cartridgeApiUrl: "https://api.cartridge.gg"
)
// Check session metadata
let address = session.address()
let username = session.username()
let expiresAt = session.expiresAt()
let isExpired = session.isExpired()Executing Transactions
Execute transactions through the Controller or SessionAccount:
let call = Call(
contractAddress: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
entrypoint: "transfer",
calldata: ["0x1234...", "0x1000", "0x0"]
)
// Via Controller
let txHash = try controller.execute(calls: [call])
// Via SessionAccount (no signature required)
let txHash = try session.executeFromOutside(calls: [call])Error Handling
The bindings define ControllerError for error handling:
do {
try controller.execute(calls: [call])
} catch let error as ControllerError {
switch error {
case .InitializationError(let message):
print("Init failed: \(message)")
case .SignupError(let message):
print("Signup failed: \(message)")
case .ExecutionError(let message):
print("Execution failed: \(message)")
case .NetworkError(let message):
print("Network error: \(message)")
case .StorageError(let message):
print("Storage error: \(message)")
case .InvalidInput(let message):
print("Invalid input: \(message)")
case .DisconnectError(let message):
print("Disconnect failed: \(message)")
}
}Utility Functions
// Derive public key from private key
let publicKey = try getPublicKey(privateKey: privateKey)
// Convert signer to GUID
let guid = try signerToGuid(privateKey: privateKey)
// Get Controller class hash
let classHash = try getControllerClassHash(version: .latest)
// Validate felt format
let isValid = try validateFelt(felt: "0x123...")
// Check if storage exists for app
let hasStorage = try controllerHasStorage(appId: "my_app")Example Projects
Complete working examples are available in the Controller.c repository: