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 neededChain 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): BooleanAuthentication
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(): SDKUserInfoWallet
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): SelectedWalletSigning (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"): SignTypedDataRespTransactions (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): TransactionReceiptRPC (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): TransactionReceiptTheme / 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_IDprovided by the user or already present in project configuration. SDKConfigmust include non-blankprojectIdandappName.appIdis auto-derived fromcontext.applicationContext.packageNamewhen using the convenience constructor; supply it explicitly only when using the primary constructor.- SDK setup requires
<uses-permission android:name="android.permission.INTERNET" />inAndroidManifest.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 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. - Signing and transaction methods (
signMessage,signTypedData,signTransaction,sendTransaction,selectWallet) require a validActivityparameter 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
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, package-name-derived values, or sample IDs forCROSSX_PROJECT_IDor callback scheme configuration. - If
CROSSX_PROJECT_IDis missing, ask the user for it before writing Gradle or manifest configuration.
Implementation Workflow
- Identify the feature category and the affected Android 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 Activity requirements.
- 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.xmlincludes the requiredINTERNETpermission 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 withcreateWallet()if needed. - Chain IDs use CAIP-2
eip155:<number>format (e.g.eip155:1,eip155:56). - Signing and transaction methods pass a valid
Activityinstance. - RPC usage is limited to supported read or call scenarios.
- 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 errors.
Updated about 19 hours ago