Skip to main content
This guide walks you through a complete wallet integration in Kotlin, from initializing the SDK and authenticating a user to retrieving their wallet address and sending tokens on Base Sepolia.

Before you begin

Install the Rain SDK and configure a Base Sepolia RPC endpoint. All wallet operations in this quickstart run directly in your Android application. You do not need a Rain API key. Note: Network operations use Kotlin suspend functions and must be called from a coroutine.
1

Create the provider

RainProvider is the Rain wallet: it owns login, wallet creation, and sessions. The wallet backend’s identity is embedded in the SDK, so the default configuration is the product. It needs your Application for secure storage. Create one provider per app and keep it for the app’s lifetime.
To react to a session that dies and can’t be refreshed, or to enable passkeys, pass a RainWalletConfig. The hook is held for the provider’s whole life, so don’t capture an Activity or ViewModel in it.
2

Log in: restore a session or start a login

On launch, wait for the SDK to restore a persisted session. A returning user skips the code entirely. The first authentication call of a launch also runs the wallet backend’s one-time initialization.
wallet.authState is a Flow<RainWalletAuthState> (Loading, Authenticated, Unauthenticated) and wallet.currentAuthState() is its snapshot, so a Compose login screen can collect it instead of polling.
3

Send a one-time code

Send a login code to the user’s email address or phone number. The contact is the account’s identity: a first login with it signs the user up, and later logins with the same contact find the same account.
Calling sendLoginCode again for the same contact issues a new code and replaces the pending one, which is how you implement Resend code. A blank email or a phone number outside E.164 throws RainError.InvalidConfig before anything leaves the device.
4

Confirm the code and create the wallet

Pass the code the user typed. On a first login the SDK signs the user up and creates one wallet holding an Ethereum account and a Solana account, from a single seed, in the same request. On a returning login it finds the existing account.
A wrong code keeps the pending challenge, so the user can retype it without requesting a new one. Codes expire after 5 minutes and lock after 3 wrong attempts.
The user is logged in and their wallet exists. A successful login also ends the user’s sessions on other devices; the signed-out device’s onSessionExpired fires on its next call.
5

Build the SDK and resolve the wallet

Register the provider with the chains you want to use and build the SDK. Then resolve the wallet-bound RainClient. Resolution suspends once, while the SDK materializes the wallet, and is cached afterwards.
Resolving before a session is live throws RainError.TokenExpired (RAIN_201). Send the EVM address (and the Solana address, if you use Solana) to your backend: creating the user’s Rain application requires it. See Wallet creation.
6

Send a transaction

Fund the wallet with testnet USDC first (the Circle faucet supports Base Sepolia), then send some of it to another address you control. Amounts are BigDecimal in human units; the SDK resolves the token’s decimals and converts exactly.
Gas is sponsored by default, so the wallet doesn’t need ETH to send. Native sends use client.sendNative(chainId, to, amount).
You’ve logged in, created a wallet, and sent a token. Everything else on RainClient follows the same shape: balances and history, receiving funds, and card funding.

Log in with a passkey instead

Users can log in with an existing passkey or add a passkey to their account for future authentication. Configure passkeyDomain during SDK setup and host https://<domain>/.well-known/assetlinks.json for your app. Each passkey call takes the foreground Activity the system passkey sheet is presented from and suspends until the sheet closes, so run it in a scope that survives configuration changes, such as a ViewModel scope.
Use signUpWithPasskey(activity) only when creating a new account. Returning users should use loginWithPasskey(activity) to access their existing wallet. See Authentication options for the association file shape and recovery guidance.

Log out and clean up

Use logout() to end the user’s session and close() to release SDK resources.
Calling rain.close() permanently closes the SDK instance. Create a new RainSdk instance before the next login. Use rain.reset() instead if you only need to clear resolved clients while keeping the provider active.

Using a third-party wallet provider

If your application already uses Portal, Privy, or your own Turnkey organization, register the corresponding provider adapter instead of RainProvider. Authentication and session management remain with your existing provider, while subsequent wallet operations use the same RainClient interface.
For provider configuration, authentication requirements, and supported capabilities, see Third-party wallet providers

What’s next

Features

What each platform and chain supports.

Testing

Testnets, test tokens, and the pre-go-live checklist.

Error reference

Every RAIN_* code and how to handle it.