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.swift or Xcode package dependency setup
  • Info.plist
  • AppDelegate, 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 identification

Chain 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) -> Bool

Authentication

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 -> SDKUserInfo

Wallet

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 -> SignTypedDataResponse

Transactions

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 -> TransactionReceipt

RPC (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 -> TransactionReceipt

Chain Info

public func getChains() async throws -> [ChainInfo]
public func getChain(chainId: String) async throws -> ChainInfo
public func getRpcUrl(for chainId: String) async throws -> String

Theme / Biometric

public func applyTheme(themeMode: SDKThemeMode? = nil, themeTokens: SDKThemeTokens? = nil)
public func canUseBiometric() -> Bool
public func isBiometricEnabled() -> Bool
public func setBiometricEnabled(_ enabled: Bool) async throws

Password / 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 -> GetPrivateKeyResponse

Code 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 projectId and appName provided by the user or already present in project configuration.
  • SDK setup requires URL scheme registration in Info.plist for 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 (for signInWithJWT).
  • Use signInAgain() to re-authenticate an existing session without going through provider selection. If the account differs from the stored session, it throws CROSSxError.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 call createWallet() if no wallet exists or migration is required. Alternatively use signInWithCreate() 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 walletRpc for 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 for projectId or callback scheme configuration.
  • If projectId or appName is missing, ask the user for it before writing SDK setup changes.
  • Use the correct type names: SDKThemeMode (not CROSSxTheme), SDKSignInProvider (not LoginProvider), SDKThemeTokens (not CROSSxThemeConfig), SDKColorOverrides (not CROSSxThemeTokens).

Implementation Workflow

  1. Identify the feature category and the affected iOS files.
  2. Inspect the existing project structure before making changes.
  3. Confirm the required setup before adding feature code.
  4. Apply the smallest code change that fits the existing architecture and style.
  5. Use the correct SDK APIs, chain-aware parameters, and callback handling requirements.
  6. 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.plist includes 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 with createWallet() or use signInWithCreate().
  • 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, and SDKColorOverrides.
  • When catching errors, check error.requiresSignOut for session recovery; handle CROSSxError.sessionExpired (requires sign-out and re-login) and CROSSxError.accountMismatch (re-authentication returned a different account) separately from other auth errors.

© 2025 NEXUS Co., Ltd. All Rights Reserved.