crossx-20-ios
CROSSx iOS Skill
Documentation
Dependency
Swift Package Manager — add the CROSSx SDK package via Xcode or Package.swift. The product name is CROSSxCoreSDK.
Requires URL scheme registration in Info.plist for the SDK callback scheme (crossx-{projectId}).
Instructions
Identify the request category first, then make the smallest Swift changes that fit the existing iOS project structure.
Treat the CROSSx iOS documentation linked above as the primary source for SDK usage, integration flow, and sample patterns.
If the bundled iOS docs do not fully answer the question or the latest SDK behavior is unclear, prefer the official cross MCP server before unrelated documentation sources.
File Discovery
Before editing, find the existing integration points for dependency setup, URL scheme callback handling, SDK creation, authentication flow, and wallet-related actions.
Check these first when they exist:
Package.swiftor Xcode package dependency setupInfo.plistAppDelegate,SceneDelegate, or SwiftUI app entry- existing SDK initialization location
- existing auth, wallet, settings, or blockchain-related integration points
Prefer extending existing integration points over introducing new wrappers or architectural layers.
Request Classification
Classify each request into one or more of these categories before writing code:
- SDK setup: package dependency, project ID, app name, callback scheme, URL type registration, SDK initialization
- Authentication: login provider, sign-in flow (OAuth / JWT / signInWithCreate / signInAgain), URL callback handling, session restore, sign-out, login checks, token refresh
- Wallet: wallet state check, wallet creation, address lookup, wallet selection, wallet recovery
- Signing and transactions: sign message, sign typed data, sign transaction, send transaction, wait for receipt
- RPC and chain access: wallet RPC, balance lookup, token balance, nonce lookup, gas estimation, chain-specific operations
- Biometric: Face ID / Touch ID enablement, PIN protection
- Password and key management: password verification, password change, mnemonic export, private key export
- UI and theme integration: theme mode (SDKThemeMode), color tokens (SDKThemeTokens / SDKColorOverrides), runtime theme change
If a request spans multiple categories, handle setup and authentication prerequisites before wallet, signing, transaction, or RPC work.
Key Types Reference
SDKConfig
public struct SDKConfig: Sendable {
public init(
bundle: Bundle = .main,
projectId: String, // required
appName: String, // required, shown in signing dialogs
callbackScheme: String? = nil, // default: "crossx-{projectId}"
httpNetwork: HTTPNetworkConfig? = nil,
theme: SDKThemeMode = .system,
themeTokens: SDKThemeTokens? = nil,
debug: Bool = true // forced false in Release builds
)
}
public struct HTTPNetworkConfig: Sendable {
public init(timeout: TimeInterval = 30, maxRetries: Int = 3)
}AuthResult / UserInfo
public struct AuthResult: Sendable {
public let success: Bool
public let walletAddress: String?
public let user: UserInfo?
// cloudBackupStatus for migration detection
}
public struct UserInfo: Sendable {
public let id: String
public let email: String?
public let provider: String?
public let accessToken: String?
public let idToken: String?
}SDKSignInProvider
public enum SDKSignInProvider: Equatable, Sendable {
case all // show all login options
case google
case apple
}Theme Types
public enum SDKThemeMode: String, Sendable {
case light, dark, system
}
public struct SDKThemeTokens: Sendable {
public var light: SDKColorOverrides?
public var dark: SDKColorOverrides?
}
public struct SDKColorOverrides: Sendable {
public var primary: String? // button bg, accent (default: "#019D92")
public var secondary: String? // error highlight (default: "#E70077")
public var onPrimary: String? // button text (default: "#FFFFFF")
public var bg: String? // background
// ... additional color tokens
}Wallet Types
public enum WalletCheckStatus: String, Codable, Sendable {
case exists // wallet exists
case migrationRequired // legacy backup found, needs migration
case notFound // no wallet, create new
}
public struct CreateWalletResponse: Codable, Sendable {
public let address: String
public let addresses: [WalletAddressInfo]?
}
public struct GetAddressResponse: Codable, Sendable {
public let address: String
public let index: Int
}
public struct GetAddressesResponse: Codable, Sendable {
public let addresses: [WalletAddressInfo]
}
public struct WalletAddressInfo: Codable, Sendable {
public let address: String
public let index: Int
}Transaction Types
public struct UnsignedTransaction: Codable, Sendable {
public let chainId: String?
public let from: String?
public let to: String?
public let value: String?
public let data: String?
public let nonce: String?
public let gasLimit: String?
public let gasPrice: String?
public let maxFeePerGas: String?
public let maxPriorityFeePerGas: String?
}
public struct SignTransactionResponse: Codable, Sendable {
public let signedTx: String?
public let txHash: String?
}
public struct SendTransactionResponse: Codable, Sendable {
public let txHash: String?
}
public struct SignMessageResponse: Codable, Sendable {
public let signature: String?
}
public struct TransactionReceipt: Sendable, Decodable {
public let transactionHash: String
public let blockHash: String
public let blockNumber: String
public let from: String
public let to: String?
public let gasUsed: String
public let effectiveGasPrice: String
public let status: String // "0x1" = success, "0x0" = reverted
}
public struct PollingOptions: Sendable {
public let intervalMs: Int // default: 2000
public let timeoutMs: Int // default: 60_000
}
public struct JsonRpcRequest: Codable, Sendable {
public init(id: Int = 1, method: String, params: [String] = [])
}Key CROSSxError Cases
public enum CROSSxError: LocalizedError, Sendable {
// Session / Auth
case sessionExpired // requires sign-out and re-login
case accountMismatch // signInAgain() returned different account
case notAuthenticated
case notInitialized
case userRejected
// Wallet
case walletNotFound
case walletAlreadyExists
case migrationRequired
case walletInconsistentState
// Chain / Transaction
case unsupportedChain(String)
case invalidChainId(String)
case signFailed(String)
case transactionFailed(String)
case broadcastFailed(String)
// Password / Biometric
case passwordWrong
case passwordLocked(PinLockInfo)
case biometricFailed
case userReject // user cancelled PIN or biometric prompt
// Network
case networkError(Error)
}
// Check error.requiresSignOut to determine if sign-out recovery is needed
// Use error.errorCode (String) for stable error identificationChain ID Format
All chain ID parameters accept CAIP-2 eip155:<number> format (recommended), plain decimal strings, or 0x hex strings. They are internally normalized to eip155:<number>.
// All equivalent — normalized to "eip155:1"
try await sdk.getBalance(address: addr, chainId: "eip155:1")
try await sdk.getBalance(address: addr, chainId: "1")
try await sdk.getBalance(address: addr, chainId: "0x1")Core API Signatures
Initialization
// Constructor
public init(config: SDKConfig) throws
// Session restore — call on app launch
@discardableResult
public func initialize() async throws -> AuthResult?
// OAuth callback — call from URL handler
@discardableResult
public func handleURL(_ url: URL) -> BoolAuthentication
public func signIn(provider: SDKSignInProvider = .all) async throws -> AuthResult
public func signInWithCreate(provider: SDKSignInProvider = .all) async throws -> AuthResult
public func signInAgain() async throws -> AuthResult
public func signInWithJWT(accessToken: String, refreshToken: String? = nil) async throws -> AuthResult
public func signOut() async throws
public func isLoggedIn() -> Bool
public func ensureLoggedIn() async -> Bool
public func refreshToken() async throws -> String
public func getUserInfo() async throws -> SDKUserInfoWallet
public func checkWallet() async throws -> WalletCheckStatus
public func createWallet(migrateAutomatically: Bool = true) async throws -> CreateWalletResponse
public func getAddress(index: Int = 0) async throws -> GetAddressResponse
public func getAddresses() async throws -> GetAddressesResponse
public func selectWallet(currentAddress: String? = nil) async throws -> WalletAddressInfo?Signing
public func signMessage(_ message: String, chainId: String,
from: String? = nil, dappName: String? = nil,
accountName: String = "Account") async throws -> SignMessageResponse
public func signTypedData(_ typedDataJson: String, chainId: String,
from: String? = nil, dappName: String? = nil,
accountName: String = "Account") async throws -> SignTypedDataResponse
public func signTypedDataOffchain(_ typedDataJson: String,
from: String? = nil, dappName: String? = nil,
accountName: String = "Account") async throws -> SignTypedDataResponseTransactions
public func signTransaction(_ unsignedTx: UnsignedTransaction, chainId: String,
dappName: String? = nil, networkName: String? = nil,
estimatedFee: String? = nil, amount: String? = nil) async throws -> SignTransactionResponse
public func sendTransaction(_ unsignedTx: UnsignedTransaction, chainId: String,
dappName: String? = nil, networkName: String? = nil,
estimatedFee: String? = nil, amount: String? = nil) async throws -> SendTransactionResponse
public func sendTransactionAndWait(_ unsignedTx: UnsignedTransaction, chainId: String,
dappName: String? = nil, networkName: String? = nil,
estimatedFee: String? = nil, amount: String? = nil,
options: PollingOptions = PollingOptions()) async throws -> SendTransactionAndWaitResponse
public func sendTransactionWithWaitForReceipt(_ unsignedTx: UnsignedTransaction, chainId: String,
dappName: String? = nil, networkName: String? = nil,
estimatedFee: String? = nil, amount: String? = nil,
timeoutMs: Int = 30_000, pollIntervalMs: Int = 1_000) async throws -> TransactionReceiptRPC (read-only)
public func walletRpc(request: JsonRpcRequest, chainId: String) async throws -> String
public func getBalance(address: String, chainId: String, blockTag: String = "latest") async throws -> String
public func getTokenBalance(contractAddress: String, ownerAddress: String, chainId: String,
blockTag: String = "latest") async throws -> String
public func getNonce(address: String, chainId: String, blockTag: String = "pending") async throws -> String
public func getGasPrice(chainId: String) async throws -> String
public func estimateGas(_ unsignedTx: UnsignedTransaction, chainId: String) async throws -> String
public func getMaxPriorityFeePerGas(chainId: String) async throws -> String
public func getBaseFeePerGas(chainId: String) async throws -> String?
public func getTransactionReceipt(txHash: String, chainId: String) async throws -> TransactionReceipt?
public func waitForTransaction(txHash: String, chainId: String,
options: PollingOptions = PollingOptions()) async throws -> TransactionReceiptChain Info
public func getChains() async throws -> [ChainInfo]
public func getChain(chainId: String) async throws -> ChainInfo
public func getRpcUrl(for chainId: String) async throws -> StringTheme / Biometric
public func applyTheme(themeMode: SDKThemeMode? = nil, themeTokens: SDKThemeTokens? = nil)
public func canUseBiometric() -> Bool
public func isBiometricEnabled() -> Bool
public func setBiometricEnabled(_ enabled: Bool) async throwsPassword / Key Management
public func verifyPassword(_ password: String) async throws -> Bool
public func changePassword(password: String, newPassword: String) async throws -> ChangePasswordResponse
public func exportMnemonic(password: String) async throws -> GetMnemonicResponse
public func exportPrivateKey(password: String, index: Int = 0) async throws -> GetPrivateKeyResponseCode Examples
SDK Initialization
import CROSSxCoreSDK
let config = SDKConfig(
projectId: "YOUR_PROJECT_ID",
appName: "My App",
theme: .system,
themeTokens: SDKThemeTokens(
light: SDKColorOverrides(primary: "#FF6B35"),
dark: SDKColorOverrides(primary: "#FF6B35")
)
)
let sdk = try CROSSxSDK(config: config)
// Restore session on app launch
let restored = try await sdk.initialize()
if restored != nil {
// session restored, user is logged in
}URL Callback Handling (SwiftUI)
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
sdk.handleURL(url)
}
}
}
}Sign In and Wallet Setup
do {
// One-step: sign in + wallet creation
let result = try await sdk.signInWithCreate(provider: .google)
let address = result.walletAddress
// Or two-step: sign in, then check wallet
let authResult = try await sdk.signIn()
let status = try await sdk.checkWallet()
if status == .notFound || status == .migrationRequired {
let wallet = try await sdk.createWallet()
}
let addr = try await sdk.getAddress()
} catch CROSSxError.sessionExpired {
try await sdk.signOut()
// redirect to login
} catch CROSSxError.accountMismatch {
// user signed in with a different account
}JWT Authentication (Server-Side Auth)
let result = try await sdk.signInWithJWT(
accessToken: serverAccessToken,
refreshToken: serverRefreshToken
)Send Transaction
let tx = UnsignedTransaction(
from: senderAddress,
to: receiverAddress,
value: "0xDE0B6B3A7640000", // 1 ETH in wei (hex)
gasLimit: "0x5208" // 21000
)
do {
let receipt = try await sdk.sendTransactionWithWaitForReceipt(
tx,
chainId: "eip155:1"
)
// receipt.transactionHash, receipt.status
} catch let error as CROSSxError {
if error.requiresSignOut {
try await sdk.signOut()
}
}RPC Read
let balance = try await sdk.getBalance(
address: walletAddress,
chainId: "eip155:1"
)
let tokenBalance = try await sdk.getTokenBalance(
contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
ownerAddress: walletAddress,
chainId: "eip155:1"
)
let gasPrice = try await sdk.getGasPrice(chainId: "eip155:1")Preconditions
Confirm the required setup before implementing a feature.
- SDK setup requires a real
projectIdandappNameprovided by the user or already present in project configuration. - SDK setup requires URL scheme registration in
Info.plistfor the SDK callback scheme (crossx-{projectId}). initialize()must be called on app launch to restore a stored session before other SDK operations.- Authentication work requires callback URL forwarding into
sdk.handleURL(_:)and a valid session flow (for OAuth) or valid JWT tokens (forsignInWithJWT). - Use
signInAgain()to re-authenticate an existing session without going through provider selection. If the account differs from the stored session, it throwsCROSSxError.accountMismatch. - Wallet and RPC work require a valid login state and the correct chain ID (CAIP-2
eip155:<number>recommended). - After login, wallet-dependent flows may still require wallet creation. Call
checkWallet()to determine the current wallet state, then callcreateWallet()if no wallet exists or migration is required. Alternatively usesignInWithCreate()for one-step login + wallet setup. - Signing and transaction work require the correct chain-aware API and the SDK UI flow to be available from the current iOS app context.
- Password and key management methods (
verifyPassword,changePassword,exportMnemonic,exportPrivateKey) accept the password directly — no PIN modal is shown. The caller must collect the PIN from their own UI before calling.
Guardrails
- Do not assume authentication is complete unless URL callback handling and session flow are wired correctly.
- Do not use wallet or RPC features as the default path before confirming login state.
- Do not use
walletRpcfor transaction signing or sending flows — it is read-only (eth_call,eth_getBalance, etc.). - Do not introduce lower-level transaction payloads unless the request clearly requires them.
- Do not add new wrappers or architectural layers when the feature can fit existing integration points.
- Do not guess unclear SDK behavior when the latest documentation or MCP resources should be checked first.
- Do not generate fallback values such as empty strings,
crossx,my-app, bundle-derived sample IDs, or placeholder project IDs forprojectIdor callback scheme configuration. - If
projectIdorappNameis missing, ask the user for it before writing SDK setup changes. - Use the correct type names:
SDKThemeMode(notCROSSxTheme),SDKSignInProvider(notLoginProvider),SDKThemeTokens(notCROSSxThemeConfig),SDKColorOverrides(notCROSSxThemeTokens).
Implementation Workflow
- Identify the feature category and the affected iOS files.
- Inspect the existing project structure before making changes.
- Confirm the required setup before adding feature code.
- Apply the smallest code change that fits the existing architecture and style.
- Use the correct SDK APIs, chain-aware parameters, and callback handling requirements.
- Review the affected files for integration gaps before finalizing.
Validation Checklist
Before finalizing CROSSx iOS SDK changes, verify the following:
- The change matches the requested feature scope without unnecessary refactoring.
- Required SDK values (
projectId,appName) and callback setup are present where needed. Info.plistincludes the required URL scheme for the SDK callback when auth flows are used.initialize()is called on app launch before other SDK operations.- Authentication, wallet, and RPC flows satisfy login and session requirements.
- After login, confirm a wallet address exists for wallet-dependent flows; use
checkWallet()to inspect state, then create withcreateWallet()or usesignInWithCreate(). - Chain IDs use CAIP-2
eip155:<number>format (e.g.eip155:1,eip155:56). - RPC usage is limited to supported read or call scenarios.
- Theme configuration uses
SDKThemeMode,SDKThemeTokens, andSDKColorOverrides. - When catching errors, check
error.requiresSignOutfor session recovery; handleCROSSxError.sessionExpired(requires sign-out and re-login) andCROSSxError.accountMismatch(re-authentication returned a different account) separately from other auth errors.
Updated about 20 hours ago