API Reference

iOS SDK API Reference

This page documents the public API surface of CROSSxSDK for iOS.

Configuration Types

SDKConfig

public struct SDKConfig: Sendable {
    public let bundle: Bundle
    public let projectId: String
    public let appName: String
    public let callbackScheme: String
    public let httpNetwork: HTTPNetworkConfig?
    public let theme: SDKThemeMode
    public let themeTokens: SDKThemeTokens?
    public let debug: Bool

    public init(
        bundle: Bundle = .main,
        projectId: String,
        appName: String,
        callbackScheme: String? = nil,
        httpNetwork: HTTPNetworkConfig? = nil,
        theme: SDKThemeMode = .system,
        themeTokens: SDKThemeTokens? = nil,
        debug: Bool = true
    )

    public static func fromInfoPlist(
        projectId: String,
        appName: String,
        bundle: Bundle = .main,
        httpNetwork: HTTPNetworkConfig? = nil
    ) throws -> SDKConfig

    public static func defaultScheme(for projectId: String) -> String
}
public enum SDKSignInProvider: Equatable, Sendable {
    case all    // shows provider selection sheet
    case google
    case apple
}

Choose the provider at runtime in signIn(provider:) / signInWithCreate(provider:). .all shows the provider selection sheet.

Theme

public enum SDKThemeMode: String, Sendable {
    case light
    case dark
    case system
}

public struct SDKThemeTokens: Sendable {
    public var light: SDKColorOverrides?
    public var dark: SDKColorOverrides?
}

SDKColorOverrides

public struct SDKColorOverrides: Sendable {
    public var primary: String?
    public var secondary: String?
    public var onPrimary: String?
    public var borderDefault: String?
    public var borderSubtle: String?
    public var textPrimary: String?
    public var textSecondary: String?
    public var textTertiary: String?
    public var surfaceDefault: String?
    public var surfaceSubtle: String?
    public var background: String?
}

Color format: hex string (e.g. "#FF5733", "#12121280" for alpha).

HTTPNetworkConfig

public struct HTTPNetworkConfig: Sendable {
    public let timeout: TimeInterval   // default 30
    public let maxRetries: Int         // default 3
}

Core API

  • static let version: String — current SDK version
  • func initialize() async throws -> AuthResult?
  • func applyTheme(themeMode: SDKThemeMode? = nil, themeTokens: SDKThemeTokens? = nil)
  • @discardableResult func handleURL(_ url: URL) -> Bool
  • func signIn(provider: SDKSignInProvider = .all) async throws -> AuthResult
  • func signInAgain() async throws -> AuthResult
  • func signInWithCreate(provider: SDKSignInProvider = .all) async throws -> AuthResult
  • func signInWithJWT(accessToken: String, refreshToken: String? = nil) async throws -> AuthResult
  • func signOut() async throws
  • func refreshToken() async throws -> String
  • func isLoggedIn() -> Bool
  • func ensureLoggedIn() async -> Bool
  • func getUserInfo() async throws -> SDKUserInfo

Behavior summary

  • initialize()AuthResult? (includes cloudBackupStatus if session restored; nil if no restorable session)
  • signIn()AuthResult (includes cloudBackupStatus and needsMigration after login)
  • signInAgain() — Re-authenticates using the stored provider. If the account differs from the stored session, throws CROSSxError.accountMismatch without overwriting the session.
  • signInWithCreate() — Calls signIn() then createWallet() automatically. Returns with walletAddress set.
  • signInWithJWT() — Injects an externally issued access token directly into the SDK session (no OAuth flow).
  • refreshToken() — Returns a valid access token; refreshes via stored refresh token if needed. Throws CROSSxError.sessionExpired if refresh fails.
  • getUserInfo()SDKUserInfo

Wallet password and biometrics

The SDK may prompt for a PIN (wallet password) before signing and sending. The PIN is cached after first entry and reused.

  • func canUseBiometric() -> Bool
  • func isBiometricEnabled() -> Bool
  • func setBiometricEnabled(_ enabled: Bool) async throws

Notes

  • canUseBiometric() checks device hardware and enrollment status.
  • isBiometricEnabled() checks if the stored PIN is protected by Face ID / Touch ID.
  • setBiometricEnabled(_:) re-saves the PIN with or without biometric protection. Shows Enter PIN modal if no PIN is cached.
  • Throws CROSSxError.biometricFailed on recognition failure and CROSSxError.userReject on cancellation.

Address / Wallet

  • func checkWallet() async throws -> WalletCheckStatus
  • func createWallet(migrateAutomatically: Bool = true) async throws -> CreateWalletResponse
  • func getAddress(index: Int = 0) async throws -> GetAddressResponse
  • func getAddresses() async throws -> GetAddressesResponse
  • func selectWallet(currentAddress: String? = nil) async throws -> WalletAddressInfo?
  • func recoverWallet(shareC: String) async throws -> RecoverWalletResponse

createWallet() branches

  • .exists — Prompts for the existing PIN if not cached; returns the current address.
  • .migrationRequired — If migrateAutomatically == true, shows migration UI (Wallet Found → PIN). If false, throws CROSSxError.migrationRequired.
  • .notFound — Creates a new wallet and prompts for a new PIN.

Return notes

  • checkWallet()WalletCheckStatus (.exists | .migrationRequired | .notFound)
  • getAddress()GetAddressResponse (address, index)
  • getAddresses()GetAddressesResponse (addresses: [WalletAddressInfo])
  • createWallet()CreateWalletResponse (address)
  • selectWallet()WalletAddressInfo? (nil if user dismisses modal)
  • recoverWallet()RecoverWalletResponse (address)

Notes

  • checkWallet() does not require authentication but needs a valid access token.
  • selectWallet() shows an HD wallet selector modal. Passing currentAddress highlights the active wallet with a "selected" badge.

Wallet password management

  • func verifyPassword(_ password: String) async throws -> Bool
  • func changePassword(password: String, newPassword: String) async throws -> ChangePasswordResponse
  • func exportMnemonic(password: String) async throws -> GetMnemonicResponse
  • func exportPrivateKey(password: String, index: Int = 0) async throws -> GetPrivateKeyResponse

These methods accept the password directly — no PIN modal is shown. Collect the PIN from your own UI before calling. Throws CROSSxError.passwordWrong on mismatch.

Chain Management

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

Return notes

  • getChains()[ChainInfo] (all chains registered for the project)
  • getChain(chainId:)ChainInfo (chainId, rpcUrl)
  • getRpcUrl(for:)String (RPC URL for the given chainId)

Chain management APIs do not require authentication. They only check the project whitelist.

Signing / Sending (UI confirm flow)

All methods require chainId: String. Use CAIP-2 eip155:<number> format for chainId (for example eip155:1, eip155:612044).

  • signMessage(...)
    • message: String
    • params: from, dappName, accountName
  • signTypedData(...)
    • Variant: JSON string input (typedDataJson: String)
    • Variant: JSON Data input (typedDataJSON: Data)
    • params: from, dappName, accountName
  • signTypedDataOffchain(...)
    • Variant: JSON string input (typedDataJson: String)
    • Variant: JSON Data input (typedDataJSON: Data)
    • params: from, dappName, accountName
    • No chainId parameter — signs off-chain (chainId is set to "0" internally)
  • signTransaction(...)
    • unsignedTx: UnsignedTransaction
    • params: dappName, networkName, estimatedFee, amount
  • sendTransaction(...)
    • unsignedTx: UnsignedTransaction
    • params: dappName, networkName, estimatedFee, amount
  • sendTransactionAndWait(...)
    • unsignedTx: UnsignedTransaction
    • params: dappName, networkName, estimatedFee, amount
    • polling controls: options: PollingOptions
  • sendTransactionWithWaitForReceipt(...) — Android-compatible overload of sendTransactionAndWait. Returns TransactionReceipt directly. Polling controls: timeoutMs: Int = 30_000, pollIntervalMs: Int = 1_000

Return notes

  • signMessage()SignMessageResponse (signature: String?)
  • signTypedData()SignTypedDataResponse (signature: String?)
  • signTypedDataOffchain()SignTypedDataResponse (signature: String?)
  • signTransaction()SignTransactionResponse (signedTx: String?, txHash: String?)
  • sendTransaction()SendTransactionResponse (txHash: String?)
  • sendTransactionAndWait()SendTransactionAndWaitResponse (txHash: String, receipt: TransactionReceipt)
  • sendTransactionWithWaitForReceipt()TransactionReceipt

signTypedData notes

  • Routes to POST /mnemonic/sign-typed-data/:chainId.
  • For on-chain typed-data signing: use chainId = "eip155:<number>" and ensure typedData.domain.chainId exists and matches the numeric part.
  • For off-chain signing (no domain chainId): use signTypedDataOffchain() instead.
  • Server mismatch errors may return code -10026 (domain chainId mismatch).

Auto gas resolution for sendTransaction / sendTransactionAndWait

If nonce, gasLimit, or gas price fields are omitted from UnsignedTransaction, the SDK automatically resolves them via RPC before broadcasting:

  • nonceeth_getTransactionCount (pending)
  • gasLimiteth_estimateGas
  • Gas price → EIP-1559 (baseFeePerGas + 1 Gwei) if supported, otherwise legacy 2 Gwei fallback

RPC / Helpers

Direct RPC calls route through the chain's RPC URL (fetched via getChain). Use CAIP-2 eip155:<number> format for chainId (for example eip155:1).

  • func walletRpc(request: JsonRpcRequest, chainId: String) async throws -> String
  • func getBalance(address: String, chainId: String, blockTag: String = "latest") async throws -> String
  • func getTokenBalance(contractAddress: String, ownerAddress: String, chainId: String, blockTag: String = "latest") async throws -> String
  • func getNonce(address: String, chainId: String, blockTag: String = "pending") async throws -> String
  • func getGasPrice(chainId: String) async throws -> String
  • func getMaxPriorityFeePerGas(chainId: String) async throws -> String
  • func getBaseFeePerGas(chainId: String) async throws -> String?
  • func estimateGas(_ unsignedTx: UnsignedTransaction, chainId: String) async throws -> String
  • func getTransactionReceipt(txHash: String, chainId: String) async throws -> TransactionReceipt?
  • func waitForTransaction(txHash: String, chainId: String, options: PollingOptions = PollingOptions()) async throws -> TransactionReceipt
  • func waitForTxAndGetReceipt(txHash: String, chainId: String, timeoutMs: Int = 30_000, pollIntervalMs: Int = 1_000) async throws -> TransactionReceipt — Android-compatible overload of waitForTransaction

Return notes

  • walletRpc()String (JSON-RPC result as string; use for reads and calls only)
  • getBalance()String (hex wei, e.g. "0xde0b6b3a7640000")
  • getTokenBalance()String (hex token units)
  • getNonce()String (hex nonce, e.g. "0x5")
  • getGasPrice()String (hex wei, e.g. "0x3B9ACA00")
  • getMaxPriorityFeePerGas()String (hex wei)
  • getBaseFeePerGas()String? (hex wei, nil for legacy chains)
  • estimateGas()String (hex gas limit, e.g. "0x5208")
  • getTransactionReceipt()TransactionReceipt? (nil if transaction is still pending)
  • waitForTransaction() / waitForTxAndGetReceipt()TransactionReceipt

Key Object Details

UnsignedTransaction

  • chainId: String?
  • from: String?
  • to: String?
  • value: String?
  • data: String?
  • nonce: String?
  • gasLimit: String?
  • gasPrice: String?
  • maxFeePerGas: String?
  • maxPriorityFeePerGas: String?

PollingOptions

  • intervalMs: Int (default 2000)
  • timeoutMs: Int (default 60_000)

JsonRpcRequest

public struct JsonRpcRequest: Codable, Sendable {
    public let jsonrpc: String  // always "2.0"
    public let id: Int          // default 1
    public let method: String
    public let params: [JsonRpcParam]

    // Convenience init for simple string params
    public init(id: Int = 1, method: String, params: [String] = [])

    // Mixed params init (for eth_call etc.)
    public init(id: Int = 1, method: String, params: [JsonRpcParam])
}

public enum JsonRpcParam: Sendable, Codable {
    case string(String)
    case bool(Bool)
    case object([String: String])
}

JsonRpcResponse

public struct JsonRpcResponse: Sendable, Codable {
    public let jsonrpc: String
    public let id: Int
    public let error: JsonRpcError?
    public let resultData: Data?        // raw JSON bytes of result field

    public var result: String?          // result as String (e.g. hex values)
    public var isResultNull: Bool       // true if result is null (pending receipt etc.)
    public func decodeResult<T: Decodable>(_ type: T.Type) throws -> T?
}

JsonRpcError

  • code: Int
  • message: String

TransactionReceipt

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?              // nil for contract creation
    public let gasUsed: String
    public let effectiveGasPrice: String
    public let status: String           // "0x1" = success, "0x0" = reverted
    public let transactionIndex: String
    public let type: String
}

AuthResult

public struct AuthResult: Sendable {
    public let success: Bool
    public let walletAddress: String?
    public let user: UserInfo?
    public let cloudBackupStatus: CloudBackupStatus?
    public let needsMigration: Bool?
}

CloudBackupStatus

public struct CloudBackupStatus: Sendable {
    public let hasBackup: Bool          // true if a legacy native app backup exists
    public let primaryData: PrimaryStoreData?
    public let nonce: Int?
}

If hasBackup is true, the user has a legacy wallet backup that can be recovered via createWallet() migration flow.

WalletCheckStatus

public enum WalletCheckStatus: String, Codable, Sendable {
    case exists = "exists"
    case migrationRequired = "migration_required"
    case notFound = "not_found"
}

UserInfo

  • id: String
  • email: String?

SDKUserInfo

public struct SDKUserInfo: Sendable {
    public let status: String           // "success"
    public let data: SDKUserInfoData
    public let loginType: String?       // "google" or "apple"
    public let addresses: [WalletAddressInfo]

    // Convenience accessors
    public var id: String               // data.sub
    public var email: String?           // data.email
    public var provider: String?        // data.provider
    public var accessToken: String      // data.accessToken
    public var idToken: String?         // data.idToken
    public var sub: String              // data.sub
    public var providerSub: String?     // data.providerSub
}

SDKUserInfoData

public struct SDKUserInfoData: Sendable {
    public let provider: String?        // "google" or "apple"
    public let accessToken: String
    public let idToken: String?
    public let email: String?
    public let sub: String              // JWT subject (user ID)
    public let providerSub: String?     // OAuth provider's original sub
}

WalletAddressInfo

  • address: String
  • index: Int

ChainInfo

public struct ChainInfo: Codable, Sendable, Equatable {
    public let chainId: String   // CAIP-2 format, e.g. "eip155:612044"
    public let rpcUrl: String
}

RecoverWalletResponse

  • address: String

SignMessageResponse

  • signature: String?

SignTypedDataResponse

  • signature: String?

SignTransactionResponse

  • signedTx: String?
  • txHash: String?

SendTransactionResponse

  • txHash: String?

SendTransactionAndWaitResponse

  • txHash: String
  • receipt: TransactionReceipt

Errors

Common CROSSxError cases:

  • Auth: notAuthenticated, authFailed, tokenExpired, sessionExpired, accountMismatch
  • User cancel: userRejected, userReject
  • Wallet: migrationRequired, migrationPinLocked, walletInconsistentState, passwordWrong, passwordLocked, biometricFailed
  • Sign / send: signFailed, transactionFailed
  • Chain: unsupportedChain, invalidChainId
  • Other: networkError, invalidConfig, configurationMissing, unknown

Notes:

  • sessionExpired — access token unusable and refresh failed. Requires sign-out.
  • accountMismatchsignInAgain() obtained a different account than the stored session.
  • userRejected — user dismissed a confirmation or wallet modal.
  • userReject — user cancelled a biometric or PIN prompt.
  • migrationPinLocked — too many wrong migration PINs; check associated PinLockInfo.
  • passwordLocked — too many wrong wallet passwords; check associated PinLockInfo.

Message strings may vary with SDK resources and locale.


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