> ## Documentation Index
> Fetch the complete documentation index at: https://docs.crossmint.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Embedded Credit

> Offer Tala credit lines with your Crossmint wallet on Android

This guide explains how to embed <a href="https://docs.tala.co/" target="_blank">Tala</a> Credit-as-a-Service into an Android wallet built with Crossmint. You will learn how to authenticate a user, create or load a Crossmint wallet, launch Tala's lending flow, and handle the result.

## Prerequisites

To use Tala with Crossmint wallets, you need:

* **Crossmint wallet:** a [wallet](/wallets/guides/create-wallet) on Base Sepolia
* **API key:** a **staging** **Client API Key** with the scopes `users.create`, `users.read`, `wallets.create`, `wallets.read`, `wallets:transactions.create`, `wallets:transactions.sign`, `wallets:balance.read`, and `wallets.fund` (create in the <a href="https://console.crossmint.com/" target="_blank">Crossmint Console</a>). In staging, all scopes are included by default.
* **Android project:** an app with the Crossmint Kotlin SDK and the Tala Lending SDK AARs. This guide uses the sandbox module that Tala shares for evaluation.

This guide uses Base Sepolia and the Tala sandbox as examples. To follow along, you will also need:

* **Tala AARs:** the Tala SDK AAR (`lending-sdk-<VERSION>.aar`) and the sandbox AAR (`lending-sdk-sandbox-<VERSION>.aar`), which Tala shares with partners for evaluation. The sandbox AAR is a thin add-on and requires the main SDK AAR alongside it.
* **Test tokens:** test USDC or USDXM in the wallet (use [wallet.fund](/wallets/guides/fund-staging-wallet) or the <a href="https://faucet.circle.com/" target="_blank">Circle USDC Faucet</a>)
* **Gas:** ETH on Base Sepolia for gas fees (not required if [gas sponsorship](/wallets/guides/gas-sponsorship) is enabled)

In production, you need a Tala partnership and a production API key from the Tala team. Contact <a href="https://www.crossmint.com/contact/sales" target="_blank">Crossmint Sales</a> to get connected.

## What You Will Build

High-level steps:

1. Configure the Crossmint Kotlin SDK and the Tala sandbox SDK.
2. Authenticate the user with Crossmint Auth email OTP.
3. Create or load a Crossmint smart wallet on Base Sepolia.
4. Launch the Tala lending flow.
5. Handle the Tala result.

<Note>
  Tala's sandbox module is for UI/UX evaluation only. It does not call the Tala backend or move real funds.
  Production integration requires Tala partnership credentials.
</Note>

## Add the SDKs

<Steps>
  <Step title="Drop the Tala AARs into app/libs/">
    Tala provides the AARs directly to partners out-of-band. The main SDK artifact is `lending-sdk-<VERSION>.aar`; the sandbox artifact is `lending-sdk-sandbox-<VERSION>.aar`. For sandbox evaluation, drop both files into `app/libs/` (the sandbox module depends on the main SDK). In production, only the main SDK AAR is required.

    ```text theme={null}
    app/
    └── libs/
        ├── lending-sdk-<VERSION>.aar           ← from Tala
        └── lending-sdk-sandbox-<VERSION>.aar   ← from Tala (sandbox only)
    ```
  </Step>

  <Step title="Add repositories and dependencies">
    ```kotlin settings.gradle.kts theme={null}
    dependencyResolutionManagement {
        repositories {
            google()
            mavenCentral()
        }
    }
    ```

    ```kotlin app/build.gradle.kts theme={null}
    plugins {
        id("com.android.application")
        id("org.jetbrains.kotlin.android")
        id("org.jetbrains.kotlin.plugin.compose")
    }

    android {
        namespace = "com.example.app" // Replace with your package
        compileSdk = 35

        defaultConfig {
            applicationId = "com.example.app" // Replace with your app ID
            minSdk = 24
            targetSdk = 35
        }

        buildFeatures {
            compose = true
        }

        compileOptions {
            isCoreLibraryDesugaringEnabled = true
            sourceCompatibility = JavaVersion.VERSION_17
            targetCompatibility = JavaVersion.VERSION_17
        }

        kotlin {
            compilerOptions {
                jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
            }
        }
    }

    dependencies {
        // Crossmint Kotlin SDK
        implementation("com.crossmint:crossmint-sdk:1.1.1")
        implementation("com.crossmint:crossmint-compose:1.1.1")

        // Tala AARs — replace <VERSION> with the files Tala sent you
        implementation(files("libs/lending-sdk-<VERSION>.aar"))
        implementation(files("libs/lending-sdk-sandbox-<VERSION>.aar")) // sandbox only

        // Compose
        implementation("androidx.activity:activity-compose:1.9.3")
        implementation(platform("androidx.compose:compose-bom:2025.01.00"))
        implementation("androidx.compose.ui:ui")
        implementation("androidx.compose.material3:material3")

        // AARs referenced with files() do not carry transitive dependencies, so list the runtime deps explicitly
        implementation("androidx.core:core-ktx:1.13.1")
        implementation("androidx.appcompat:appcompat:1.7.0")
        implementation("androidx.cardview:cardview:1.0.0")
        implementation("androidx.recyclerview:recyclerview:1.3.2")
        implementation("androidx.databinding:viewbinding:8.7.3")
        implementation("androidx.fragment:fragment-ktx:1.8.5")
        implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
        implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7")
        implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")

        coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
    }
    ```
  </Step>

  <Step title="Initialize Crossmint and Tala">
    Initialize the Crossmint SDK in your `Application` class. For sandbox, call `TalaSandbox.init` instead of `TalaSDK.init` — it wires the SDK to bundled fixtures so no Tala backend or session token is needed.

    ```kotlin MyApplication.kt theme={null}
    import android.app.Application
    import com.crossmint.kotlin.CrossmintSDK
    import com.crossmint.kotlin.configure
    import com.crossmint.kotlin.core.LogLevel
    import com.tala.sdk.ConsentMode
    import com.tala.sdk.RepaymentConfig
    import com.tala.sdk.TalaConfig
    import com.tala.sdk.sandbox.TalaSandbox

    class MyApplication : Application() {
        override fun onCreate() {
            super.onCreate()

            CrossmintSDK.configure(
                apiKey = "YOUR_CROSSMINT_CLIENT_API_KEY",
                appContext = this,
                logLevel = LogLevel.DEBUG,
            )

            // Sandbox only — uses bundled fixtures, no Tala backend needed
            TalaSandbox.init(
                this,
                TalaConfig(
                    partnerName = "YOUR_PARTNER_NAME",
                    consentMode = ConsentMode.EXPLICIT,
                    privacyPolicyUrl = "YOUR_PRIVACY_POLICY_URL",
                    termsUrl = "YOUR_TERMS_URL",
                    repaymentConfig = RepaymentConfig.PartnerManaged(exitDeepLink = "YOUR_DEEP_LINK"),
                ),
            )
        }
    }
    ```

    <Note>
      `CrossmintSDK.configure` must be called on the main thread before any other Crossmint SDK call. The environment
      (staging vs production) is derived from the key prefix: staging keys start with `ck_staging_` and production keys
      with `ck_production_`.
    </Note>
  </Step>
</Steps>

## Authenticate and Create a Wallet

Wrap your Compose UI with `CrossmintNonCustodialSignerProvider` so the SDK can show the email OTP signer UI. Send an OTP, verify it, and create the wallet.

```kotlin MainActivity.kt theme={null}
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.crossmint.kotlin.CrossmintSDK
import com.crossmint.kotlin.auth.CrossmintAuthManager
import com.crossmint.kotlin.auth.models.OTPAuthenticationStatus
import com.crossmint.kotlin.compose.CrossmintNonCustodialSignerProvider
import com.crossmint.kotlin.compose.LocalCrossmintSDK
import com.crossmint.kotlin.signers.SignerType
import com.crossmint.kotlin.types.EVMChain
import com.crossmint.kotlin.types.Result
import com.tala.sdk.TalaResult
import com.tala.sdk.TalaSDK
import com.tala.sdk.launch
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MaterialTheme {
                CrossmintNonCustodialSignerProvider {
                    TalaWalletApp()
                }
            }
        }
    }
}

@Composable
fun TalaWalletApp() {
    val sdk: CrossmintSDK = LocalCrossmintSDK.current
    val authManager = sdk.authManager as CrossmintAuthManager
    val scope = rememberCoroutineScope()

    var email by remember { mutableStateOf("") }
    var otp by remember { mutableStateOf("") }
    var status by remember { mutableStateOf("Ready") }
    var otpSent by remember { mutableStateOf(false) }

    val talaLauncher = rememberLauncherForActivityResult(
        contract = TalaSDK.Contract(),
    ) { result: TalaResult ->
        when (result) {
            is TalaResult.LoanAccepted -> { /* loan details arrive via the loan verification callback */ }
            is TalaResult.Abandoned -> { /* user dismissed the flow */ }
            is TalaResult.ConsentDeclined -> { /* user declined the consent screen */ }
            is TalaResult.NoLoanOffer -> { /* no offer available for this user */ }
            is TalaResult.Error -> { /* inspect the subtype and result.requestId */ }
        }
    }

    fun sendOtp() {
        scope.launch {
            when (authManager.sendOtp(email)) {
                is Result.Success -> {
                    otpSent = true
                    status = "OTP sent to $email"
                }
                is Result.Failure -> {
                    status = "Failed to send OTP"
                }
            }
        }
    }

    fun verifyOtpAndLaunchTala() {
        scope.launch {
            when (val verifyResult = authManager.verifyOtp(email, otp)) {
                is Result.Success -> {
                    if (verifyResult.value != OTPAuthenticationStatus.AUTHENTICATED) {
                        status = "Invalid OTP"
                        return@launch
                    }
                }
                is Result.Failure -> {
                    status = "OTP verification failed"
                    return@launch
                }
            }

            when (val walletResult = sdk.wallets.createWallet(
                chain = EVMChain.BaseSepolia,
                recovery = SignerType.Email(email),
                deviceSigner = true,
            )) {
                is Result.Success -> {
                    val wallet = walletResult.value
                    // In production, register wallet.address with your backend before launching
                    // so the session token it mints is bound to this user.
                    talaLauncher.launch()
                }
                is Result.Failure -> {
                    status = "Wallet creation failed"
                }
            }
        }
    }

    Column(
        modifier = Modifier.fillMaxSize().padding(24.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterVertically),
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        Text("Tala Embedded Credit Demo", style = MaterialTheme.typography.headlineSmall)

        TextField(
            value = email,
            onValueChange = { email = it },
            label = { Text("Email") },
        )

        Button(onClick = { sendOtp() }) {
            Text("Send OTP")
        }

        if (otpSent) {
            TextField(
                value = otp,
                onValueChange = { otp = it },
                label = { Text("OTP") },
            )

            Button(onClick = { verifyOtpAndLaunchTala() }) {
                Text("Verify and launch Tala")
            }
        }

        Text(status)
    }
}
```

This example sends the OTP first and only shows the OTP field after the email is sent. In a production app, split the OTP collection and wallet creation steps into separate screens.

## Launch the Tala Lending Flow

Register `TalaSDK.Contract` with `rememberLauncherForActivityResult` inside your composable (as shown in `MainActivity` above) and launch it with the `launch()` extension from `com.tala.sdk.launch`. The contract takes no input: the user identity is bound to the session token that the SDK fetches through `TalaSDK.setSessionTokenProvider`. In sandbox, `TalaSandbox.init` installs a test token provider for you.

```kotlin theme={null}
talaLauncher.launch()
```

The contract returns a `TalaResult`:

* `TalaResult.LoanAccepted` — the user accepted a loan. The loan details (reference ID, amount, currency) are delivered through `TalaSDK.setLoanVerificationCallback`, not on the result.
* `TalaResult.Abandoned` — the user dismissed the flow.
* `TalaResult.ConsentDeclined` — the user declined the consent screen.
* `TalaResult.NoLoanOffer` — Tala has no offer for this user.
* `TalaResult.Error` — a sealed hierarchy of typed errors, each carrying a `requestId` for support: `NetworkUnavailable`, `ServerError`, `SessionExpired`, `TokenProviderError`, `KycCallbackMissing`, and `Unknown`.

<Note>
  In production, the session token must come from your backend and is supplied through
  `TalaSDK.setSessionTokenProvider`. The Tala team provides the production setup details when you partner with them.
</Note>

## Sandbox Scenarios

`TalaSandbox.init` accepts an optional `scenario` argument so you can test each major flow without editing fixtures:

| `TalaSandbox.Scenario` | What the SDK shows                                                                                 |
| ---------------------- | -------------------------------------------------------------------------------------------------- |
| `HAPPY_PATH`           | First-time user: Consent → OptIn → Decisioning → Offer → Borrowing Summary → Processing → Success. |
| `NEW_USER`             | A brand-new user entering the onboarding flow.                                                     |
| `APPROVED_OFFER`       | A user with an approved loan offer ready to accept.                                                |
| `LOAN_DECLINED`        | A user whose application was declined — routes to the **Declined** screen.                         |
| `NO_OFFER`             | A user with no loan offer available.                                                               |
| `OVERDUE`              | A returning user with an overdue loan.                                                             |
| `PAST_DUE`             | A returning user with a past-due loan.                                                             |

```kotlin theme={null}
TalaSandbox.init(this, config, scenario = TalaSandbox.Scenario.LOAN_DECLINED)
```

`init` is idempotent, so you can re-initialize with the picked scenario right before launching.

## Repay a Loan from the Wallet

After Tala returns a repayment address or contract call, send USDC from the Crossmint wallet. `wallet` is the `EVMWallet` created in [Authenticate and Create a Wallet](#authenticate-and-create-a-wallet) (or loaded with `sdk.wallets.getWallet`):

```kotlin theme={null}
import com.crossmint.kotlin.types.Result

val repaymentAddress = "0x..." // provided by Tala
val amount = 10.0

when (val sendResult = wallet.send(
    recipient = repaymentAddress,
    tokenLocator = "base-sepolia:usdc",
    amount = amount,
)) {
    is Result.Success -> {
        val transaction = sendResult.value
        when (val approveResult = wallet.approve(transaction.id)) {
            is Result.Success -> { /* Repayment completed */ }
            is Result.Failure -> { /* Handle the error */ }
        }
    }
    is Result.Failure -> { /* Handle the error */ }
}
```

## Production

To enable Tala in production, you need a Tala partnership and a production API key provided by the Tala team. Contact <a href="https://www.crossmint.com/contact/sales" target="_blank">Crossmint Sales</a> to get connected.

## Customizing the Integration

Switch chains by changing the `chain` argument in `sdk.wallets.createWallet` — for example, `EVMChain.Base` in production or `EVMChain.BaseSepolia` for testing. To change Tala's consent mode, repayment handling, or legal links, update the `TalaConfig` you pass to `TalaSandbox.init`.

Tala supports specific emerging markets; confirm the country, currency, and disbursement token with the Tala team.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Tala SDK returns TalaResult.Error.SessionExpired or TokenProviderError in production">
    In production, the session token must come from your backend through `TalaSDK.setSessionTokenProvider`. In sandbox, `TalaSandbox.init` installs a test provider so no backend is needed. Contact <a href="https://www.crossmint.com/contact/sales" target="_blank">Crossmint Sales</a> to get connected with the Tala team and receive a production API key.
  </Accordion>

  <Accordion title="The Crossmint wallet fails to create with an API key error">
    Ensure you are using a client API key (`ck_...`) with the `wallets.create` scope. The SDK environment is derived
    from the key prefix: staging keys start with `ck_staging_` and production keys with `ck_production_`.
  </Accordion>

  <Accordion title="OTP dialog does not appear when signing a transaction">
    `CrossmintNonCustodialSignerProvider` must wrap your Compose content. If you are not using Compose, observe
    `CrossmintSDK.shared.isOTPRequired` and call `CrossmintSDK.shared.submit(otp)` from your own UI.
  </Accordion>

  <Accordion title="Tala sandbox UI shows the same scenario every time">
    `TalaSandbox.init` is idempotent. Call it again with the desired `TalaSandbox.Scenario` right before launching `TalaSDK.Contract`.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Check Wallet Balances" icon="wallet" href="/wallets/guides/check-balances">
    Query USDC and native token balances before and after a loan.
  </Card>

  <Card title="Send Custom EVM Transactions" icon="code" href="/wallets/guides/send-transaction-evm">
    Repay or disburse using raw contract calls when Tala provides the repayment contract.
  </Card>
</CardGroup>
