crossx-20-android

CROSSx Android Skill

Documentation

Dependency

// build.gradle.kts (app module)
implementation("io.crosstoken.cross:crossx-sdk-core:<version>")

Requires <uses-permission android:name="android.permission.INTERNET" /> in AndroidManifest.xml.

Instructions

Identify the request category first, then make the smallest Kotlin changes that fit the existing Android project structure.

Treat the CROSSx Android documentation linked above as the primary source for SDK usage, integration flow, and sample patterns.

If the bundled Android 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, manifest deep links, SDK creation, authentication flow, and wallet-related actions.

Check these first when they exist:

  • app module Gradle file
  • AndroidManifest.xml
  • deep link entry Activity
  • 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: dependency setup, Gradle config, project and app IDs, callback scheme, manifest placeholders, SDK initialization
  • Authentication: login provider, sign-in flow, deep link callback, session restore, sign-out, sign-in-again, login checks
  • Wallet: wallet state check, wallet creation, address lookup, wallet selection
  • Signing and transactions: sign message, sign typed data, sign transaction, send transaction, wait for receipt
  • RPC and chain access: wallet RPC, balance lookup, nonce lookup, chain-specific read operations
  • UI and theme integration: theme mode, theme tokens, SDK UI integration points

If a request spans multiple categories, handle setup and authentication prerequisites before wallet, signing, transaction, or RPC work.

Key Types Reference

SDKConfig

// Convenience constructor (recommended)
SDKConfig(
    context: Context,          // appId auto-derived from packageName
    projectId: String,         // required, non-blank
    appName: String,           // required, non-blank, shown in signing dialogs
    callbackScheme: String = SDKConfig.defaultScheme(projectId),
    theme: SDKThemeMode = SDKThemeMode.Light,
    themeTokens: SDKThemeTokens = SDKThemeTokens(),
    logger: LoggerPort? = null
)

// Primary constructor (explicit appId)
SDKConfig(
    projectId: String,
    appId: String,             // e.g. context.applicationContext.packageName
    appName: String,
    callbackScheme: String = SDKConfig.defaultScheme(projectId),
    theme: SDKThemeMode = SDKThemeMode.Light,
    themeTokens: SDKThemeTokens = SDKThemeTokens(),
    logger: LoggerPort? = null
)

AuthResult / UserInfo

data class AuthResult(
    val success: Boolean,
    val walletAddress: String? = null,
    val user: UserInfo? = null,
    val needsMigration: Boolean? = null
)

data class UserInfo(
    val id: String,
    val email: String? = null,
    val provider: String? = null,
    val accessToken: String? = null,
    val idToken: String? = null,
    val sub: String = id
)

SDKSignInProvider

enum class SDKSignInProvider { All, Google, Apple }

Wallet Types

data class WalletCheckResp(val result: WalletCheckResult? = null)
enum class WalletCheckResult { Exists, MigrationRequired, NotFound }

data class CreateWalletResp(val address: String? = null)
data class GetAddressResp(val address: String? = null, val index: Int? = null)
data class GetAddressesResp(val addresses: List<AddressInfo>? = null)
data class SelectedWallet(val address: String, val index: Int)

Transaction Types

data class UnsignedTx(
    val chainId: String? = null,
    val data: String? = null,
    val from: String,
    val gasLimit: String? = null,
    val gasPrice: String? = null,
    val to: String? = null,
    val value: String? = null,
    val nonce: String? = null,
    val maxFeePerGas: String? = null,
    val maxPriorityFeePerGas: String? = null
)

data class SendTxResp(val txHash: String? = null)
data class SignMessageResp(val signature: String? = null)
data class SignTypedDataResp(val signature: String? = null)

data class TransactionReceipt(
    val transactionHash: String? = null,
    val blockHash: String? = null,
    val blockNumber: String? = null,
    val from: String? = null,
    val to: String? = null,
    val gasUsed: String? = null,
    val status: String? = null,
    ...
)

data class JsonRpcRequest(
    val id: String = "1",
    val jsonrpc: String = "2.0",
    val method: String,
    val params: String = "[]"
)

Key CROSSxError Cases

sealed class CROSSxError : RuntimeException {
    // Session / Auth
    object SessionExpired    // requires sign-out and re-login
    object AccountMismatch   // signInAgain() returned different account
    object NotAuthenticated  // not logged in
    object NotInitialized    // initialize() not called
    object UserRejected      // user cancelled

    // Wallet
    object WalletNotFound
    object WalletAlreadyExists
    object MigrationRequired
    object WalletInconsistentState

    // Chain / Transaction
    class UnsupportedChain(details: String)
    class InvalidChainId(details: String)
    class SignFailed(details: String)
    class TransactionFailed(details: String)
    class BroadcastFailed(details: String)

    // Network
    class NetworkError(cause: Throwable)
    class Timeout(details: String)
}
// Check error.requiresSignOut to determine if sign-out recovery is needed

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"
sdk.getBalance(address, chainId = "eip155:1")
sdk.getBalance(address, chainId = "1")
sdk.getBalance(address, chainId = "0x1")

Core API Signatures

Initialization

// Constructor
CROSSxSDK(application: Application, config: SDKConfig)

// Session restore — call on app start
suspend fun initialize(): AuthResult?

// OAuth callback — call from deep link Activity
fun handleUrl(url: String): Boolean

Authentication

suspend fun signIn(provider: SDKSignInProvider = SDKSignInProvider.All): AuthResult
suspend fun signInWithCreate(provider: SDKSignInProvider = SDKSignInProvider.All): AuthResult
suspend fun signInAgain(): AuthResult
suspend fun signOut()
fun isLoggedIn(): Boolean
suspend fun ensureLoggedIn(): Boolean
suspend fun refreshToken(): String
suspend fun getUserInfo(): SDKUserInfo

Wallet

suspend fun checkWallet(): WalletCheckResp
suspend fun createWallet(migrateAutomatically: Boolean = true): CreateWalletResp
suspend fun getAddress(index: Int? = null): GetAddressResp
suspend fun getAddresses(): GetAddressesResp
suspend fun selectWallet(activity: Activity, currentAddress: String? = null): SelectedWallet

Signing (requires Activity for SDK UI)

suspend fun signMessage(activity: Activity, message: String, chainId: String,
    from: String? = null, dappName: String = "", accountName: String = "Account"): SignMessageResp

suspend fun signTypedData(activity: Activity, typedDataJson: String, chainId: String,
    from: String? = null, dappName: String = "", accountName: String = "Account"): SignTypedDataResp

suspend fun signTypedDataOffchain(activity: Activity, typedDataJson: String,
    from: String? = null, dappName: String = "", accountName: String = "Account"): SignTypedDataResp

Transactions (requires Activity for SDK UI)

suspend fun signTransaction(activity: Activity, unsignedTx: UnsignedTx, chainId: String,
    dappName: String = "", networkName: String? = null,
    estimatedFee: String? = null, amount: String? = null): SignTxResp

suspend fun sendTransaction(activity: Activity, unsignedTx: UnsignedTx, chainId: String,
    dappName: String = "", networkName: String? = null,
    estimatedFee: String? = null, amount: String? = null): SendTxResp

suspend fun sendTransactionWithWaitForReceipt(activity: Activity, unsignedTx: UnsignedTx, chainId: String,
    dappName: String = "", networkName: String? = null,
    estimatedFee: String? = null, amount: String? = null,
    timeoutMs: Long = 30_000, pollIntervalMs: Long = 1_000): TransactionReceipt

RPC (read-only)

suspend fun walletRpc(request: JsonRpcRequest, chainId: String): String
suspend fun getBalance(address: String, chainId: String, blockTag: String = "latest"): String
suspend fun getNonce(address: String, chainId: String, blockTag: String = "pending"): String
suspend fun waitForTxAndGetReceipt(txHash: String, chainId: String,
    timeoutMs: Long = 30_000, pollIntervalMs: Long = 1_000): TransactionReceipt

Theme / Biometric

fun applyTheme(themeMode: SDKThemeMode = config.theme, themeTokens: SDKThemeTokens = config.themeTokens)
fun canUseBiometric(): Boolean
fun isBiometricEnabled(): Boolean
suspend fun setBiometricEnabled(enabled: Boolean)

Code Examples

SDK Initialization

class MyApplication : Application() {
    lateinit var crossxSdk: CROSSxSDK

    override fun onCreate() {
        super.onCreate()

        val config = SDKConfig(
            context = this,
            projectId = CROSSX_PROJECT_ID,
            appName = "My App"
        )
        crossxSdk = CROSSxSDK(application = this, config = config)
    }
}

// In Activity or ViewModel — restore session on app start
lifecycleScope.launch {
    val restored = crossxSdk.initialize()
    if (restored != null) {
        // session restored, user is logged in
    }
}

Sign In and Wallet Setup

lifecycleScope.launch {
    try {
        // One-step: sign in + wallet creation
        val result = crossxSdk.signInWithCreate()
        val address = result.walletAddress

        // Or two-step: sign in, then check wallet
        val authResult = crossxSdk.signIn(SDKSignInProvider.Google)
        if (authResult.needsMigration == true) {
            crossxSdk.createWallet()
        }
        val addr = crossxSdk.getAddress()
    } catch (e: CROSSxError.SessionExpired) {
        crossxSdk.signOut()
        // redirect to login
    } catch (e: CROSSxError.AccountMismatch) {
        // user signed in with a different account
    }
}

Deep Link Callback (Activity)

// In your callback Activity
override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    intent?.data?.toString()?.let { url ->
        crossxSdk.handleUrl(url)
    }
}

Send Transaction

lifecycleScope.launch {
    val tx = UnsignedTx(
        from = senderAddress,
        to = receiverAddress,
        value = "0xDE0B6B3A7640000", // 1 ETH in wei (hex)
        gasLimit = "0x5208"           // 21000
    )
    try {
        val receipt = crossxSdk.sendTransactionWithWaitForReceipt(
            activity = this@MainActivity,
            unsignedTx = tx,
            chainId = "eip155:1"
        )
        // receipt.transactionHash, receipt.status
    } catch (e: CROSSxError) {
        // handle error
    }
}

RPC Read

val balance = crossxSdk.getBalance(
    address = walletAddress,
    chainId = "eip155:1"
)
val nonce = crossxSdk.getNonce(
    address = walletAddress,
    chainId = "eip155:1"
)

Preconditions

Confirm the required setup before implementing a feature.

  • SDK setup requires a real CROSSX_PROJECT_ID provided by the user or already present in project configuration.
  • SDKConfig must include non-blank projectId and appName. appId is auto-derived from context.applicationContext.packageName when using the convenience constructor; supply it explicitly only when using the primary constructor.
  • SDK setup requires <uses-permission android:name="android.permission.INTERNET" /> in AndroidManifest.xml.
  • initialize() must be called on app start to restore a stored session before other SDK operations.
  • Authentication work requires callback scheme setup, deep link callback handling via handleUrl(), and a valid session flow.
  • 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.
  • Signing and transaction methods (signMessage, signTypedData, signTransaction, sendTransaction, selectWallet) require a valid Activity parameter for the SDK UI flow.

Guardrails

  • Do not assume authentication is complete unless 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, package-name-derived values, or sample IDs for CROSSX_PROJECT_ID or callback scheme configuration.
  • If CROSSX_PROJECT_ID is missing, ask the user for it before writing Gradle or manifest configuration.

Implementation Workflow

  1. Identify the feature category and the affected Android 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 Activity requirements.
  6. Review the affected files for integration gaps before finalizing.

Validation Checklist

Before finalizing CROSSx Android SDK changes, verify the following:

  • The change matches the requested feature scope without unnecessary refactoring.
  • Required SDK values and callback setup are present where needed.
  • AndroidManifest.xml includes the required INTERNET permission when SDK network flows are used.
  • initialize() is called on app start 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() if needed.
  • Chain IDs use CAIP-2 eip155:<number> format (e.g. eip155:1, eip155:56).
  • Signing and transaction methods pass a valid Activity instance.
  • RPC usage is limited to supported read or call scenarios.
  • 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 errors.

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