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

> ## Agent Instructions
> Before reading these docs, install the relevant Circle Skill to get pre-loaded context — skills are LLM-optimized and cover architecture decisions, correct flows, and common pitfalls. In Claude Code: run /plugin marketplace add circlefin/skills then /plugin install circle-skills@circle. With Vercel Skills CLI: run npx skills add circlefin/skills. Available skills: use-usdc https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-usdc/SKILL.md (balances, transfers, approvals on EVM and Solana); EURC has no skill — see https://developers.circle.com/stablecoins/what-is-eurc.md; bridge-stablecoin https://github.com/circlefin/skills/blob/master/plugins/circle/skills/bridge-stablecoin/SKILL.md (crosschain transfers via CCTP, USDC, EURC, and third-party assets); use-arc https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-arc/SKILL.md (Arc chain config, contract deployment, USDC bridging — Arc docs: https://docs.arc.io/llms.txt); use-circle-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-circle-wallets/SKILL.md (choose wallet type); use-developer-controlled-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-developer-controlled-wallets/SKILL.md (custodial wallets for payouts and treasury); use-user-controlled-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-user-controlled-wallets/SKILL.md (embedded wallets with Web2 login); use-modular-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-modular-wallets/SKILL.md (smart contract wallets with passkeys and ERC-4337); use-gateway https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-gateway/SKILL.md (unified USDC balance, nanopayments); use-smart-contract-platform https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-smart-contract-platform/SKILL.md (deploy, interact, monitor contracts). Pair any skill with the Circle MCP server for live SDK signatures, contract addresses, and chain IDs: {"mcpServers":{"circle":{"url":"https://api.circle.com/v1/codegen/mcp"}}}.
> Read the product overview first. Each product has an overview explaining when and why to use it. Read it before quickstarts.
> Pick the right wallet type. Developer-controlled (you hold keys), user-controlled (end user holds keys via PIN/social login), or modular (smart contract wallets with passkeys). See https://developers.circle.com/wallets.md.
> Use CCTP V2 for crosschain transfers. CCTP natively transfers USDC and EURC via burn-and-mint, and supports permissionless wrapping of third-party assets via lock-and-mint. V1 is legacy; always use V2 unless V1 is specified or the chain requires it (Noble, Sui, and Aptos still require V1). Migration guide: https://developers.circle.com/cctp/migration-from-v1-to-v2.md.
> Use Bridge Kit for frontend bridging. Wraps CCTP with a simpler SDK for user-facing apps. Use CCTP directly for backend transfers.
> Gas Station vs Paymaster. Gas Station sponsors gas for Circle Wallet transactions. Paymaster lets users pay gas in USDC. Different use cases — read both overviews.
> Gateway Nanopayments for sub-cent payments. Gasless USDC micropayments down to $0.000001 via x402 and batched settlement. For pay-per-request APIs, AI agent payments, streaming.
> Look up USDC addresses per chain. Never hardcode — use https://developers.circle.com/stablecoins/usdc-contract-addresses.md.
> Prefer SDKs over raw API calls. Node.js and Python SDKs handle auth, retries, and errors.
> API key required. Bearer token in Authorization header. Testnet and mainnet use separate keys and may use different base URLs depending on the product.
> Set up webhooks when available. Most operations are async. Webhooks deliver transaction confirmations and state changes.
> When calling list endpoints, paginate using pageSize and pageAfter until no nextPageAfter cursor is returned—stopping at the first page silently misses records.
> Building an AI agent? Start with the Agent Stack—Circle CLI, agent wallets, and nanopayments built for autonomous use cases: https://developers.circle.com/agent-stack.md.

# Digital Asset Accounts Android SDK

> Reference for the Android SDK that runs passkey approvals in a native application and holds the authorization key for the React Native SDK

> [!NOTE]
>
> The Android SDK is available in sandbox only. Production support is on the
> roadmap.

The Android SDK runs in your application on the end customer's Android-powered
device. It never holds your API key and makes no calls to the Circle API itself.
Your backend does that and passes your application only the short-lived token
for one ceremony.

## What you can do

* **Approve operations with a passkey**: run passkey enrollment and approval for
  [Strong Customer Authentication (SCA)](https://developers.circle.com/digital-asset-accounts/howtos/strong-customer-authentication.md)
  in a Kotlin application.
* **Hold the authorization key for non-custodial accounts**: the
  [React Native SDK](https://developers.circle.com/sdks/daa/react-native-sdk.md) calls this SDK to create the
  end customer's authorization key in hardware-backed key storage and sign with
  it. You don't call these methods yourself. To build a
  [non-custodial account](https://developers.circle.com/digital-asset-accounts/concepts/non-custodial-accounts.md)
  integration, use the React Native SDK.

## Requirements

| Requirement | Minimum |
| - | - |
| `minSdk` | 23 |
| `compileSdk` | 36 |
| Android Gradle Plugin | 8.9.1 |
| Passkeys | Android 9 with a screen lock |

The SDK depends on `androidx.browser` 1.9.0, which requires compile SDK 36 and
Android Gradle Plugin 8.9.1. This doesn't change your `minSdk` or `targetSdk`.

## Install

The SDK is distributed from GitHub Packages, which needs a GitHub account to
read even public packages. Read the values from environment variables so that no
credentials are committed:

```kotlin theme={null}
// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven(System.getenv("DAA_ANDROID_MAVEN_URL")) {
            credentials {
                username = System.getenv("DAA_ANDROID_MAVEN_USERNAME")
                password = System.getenv("DAA_ANDROID_MAVEN_PASSWORD")
            }
        }
    }
}
```

| Variable | Value |
| - | - |
| `DAA_ANDROID_MAVEN_URL` | `https://maven.pkg.github.com/circlefin/daa-android-sdk` |
| `DAA_ANDROID_MAVEN_USERNAME` | Your GitHub username |
| `DAA_ANDROID_MAVEN_PASSWORD` | A GitHub personal access token with `read:packages` scope |

Then add the dependency to your application module:

```kotlin theme={null}
// app/build.gradle.kts
dependencies {
    implementation("com.circle.daa:daa-android-sdk:0.6.0")
}
```

## Set up the callback

When a ceremony finishes, Circle's page returns the result to your application.
In an Auth Tab, which Chrome 137 and later supports, the result returns to the
SDK directly. In a Custom Tab, it returns through a callback activity that you
declare. Set up both, because the end customer's browser decides which one runs.

1. Choose an `https` callback URL on a domain that you control, such as
   `https://app.example.com/sca/callback`. The SDK refuses a URL that includes a
   port or an IP address, and can be up to 2,048 characters. Circle doesn't
   accept custom schemes.

2. Register every exact callback URL that you use in Circle Hub, under
   **Settings > Digital Asset Accounts > SCA**. Register separately in
   [sandbox](https://hub-sandbox.circle.com) and
   [production](https://hub.circle.com).

3. Declare the SDK's callback activity in your `AndroidManifest.xml`, with an
   intent filter for your callback URL:

   ```xml theme={null}
   <activity
       android:name="com.circle.daa.sdk.sca.DaaScaCallbackActivity"
       android:exported="true"
       android:excludeFromRecents="true"
       android:taskAffinity=""
       android:theme="@android:style/Theme.Translucent.NoTitleBar">
       <intent-filter android:autoVerify="true">
           <action android:name="android.intent.action.VIEW" />
           <category android:name="android.intent.category.DEFAULT" />
           <category android:name="android.intent.category.BROWSABLE" />
           <data
               android:scheme="https"
               android:host="app.example.com"
               android:path="/sca/callback" />
       </intent-filter>
   </activity>
   ```

4. Serve `https://<host>/.well-known/assetlinks.json` with the
   `delegate_permission/common.handle_all_urls` relation. List the SHA-256
   fingerprint of every key that signs your application, including the Play App
   Signing key and your debug key. Serve it over HTTPS, without redirects, with
   `Content-Type: application/json`.

Your backend sends the same callback URL as `redirectUri` when it opens each
passkey registration or challenge. It replaces `embedOrigin`, which applies only
to web pages. If the value doesn't exactly match a registered URL, Circle
returns error code 420068.

The ceremony runs only in a supported browser, never in a `WebView`: Chrome,
Samsung Internet, Firefox, Firefox Focus, Firefox Klar, Edge, Brave, DuckDuckGo,
Opera, Opera Mini, Vivaldi, or Seznam.

## Initialize

Create a `DaaScaConfig`:

```kotlin theme={null}
import com.circle.daa.sdk.DaaEnvironment
import com.circle.daa.sdk.sca.DaaScaConfig

val scaConfig = DaaScaConfig(
    environment = DaaEnvironment.Sandbox,
    redirectUri = "https://app.example.com/sca/callback",
)
```

| Parameter | Required | Description |
| - | - | - |
| `environment` | Yes | `DaaEnvironment.Sandbox` or `DaaEnvironment.Production` |
| `redirectUri` | Yes | Your registered callback URL, exactly as registered |
| `approveTimeoutMillis` | No | How long an approval can take, in milliseconds. Defaults to 300000. Must be from 30000 to 3600000 |
| `enrollTimeoutMillis` | No | How long an enrollment can take, in milliseconds. Defaults to 600000. Must be from 30000 to 3600000 |

An invalid value throws `IllegalArgumentException`.

## Methods

The SDK runs each ceremony as an activity result contract. Register it with
`registerForActivityResult`, and launch it with the frame token from your
backend.

| API | Input | Returns | Description |
| - | - | - | - |
| `DaaScaEnrollContract(config)` | `frameToken` | `DaaScaResult` | Runs passkey enrollment. On success, send `response` to your backend unchanged |
| `DaaScaApproveContract(config)` | `frameToken` | `DaaScaResult` | Runs an approval. On success, your backend sends `response`, unchanged, in the `X-Sca-Assertion` header |
| `DaaSca.capabilities(context, config)` | None | `DaaScaCapabilities` | Reports whether this device can run a ceremony, such as whether the callback is registered and verified |

`DaaScaResult` is one of these:

| Result | Meaning |
| - | - |
| `Completed(response)` | The ceremony succeeded. Send `response` exactly as returned. Don't parse or re-encode it |
| `Cancelled` | The end customer closed the ceremony. Let them try again |
| `Failed(error, message, unsupportedReason)` | The ceremony failed. See [Errors](#errors) |

This example approves a transfer. Your backend opens the challenge, returns only
the frame token, and later sends the transfer with the assertion:

```kotlin theme={null}
class TransferActivity : ComponentActivity() {
    private val scaConfig = DaaScaConfig(
        DaaEnvironment.Sandbox,
        "https://app.example.com/sca/callback",
    )
    private var transferId: String? = null

    private val approve =
        registerForActivityResult(DaaScaApproveContract(scaConfig)) { result ->
            val transfer = transferId ?: return@registerForActivityResult
            transferId = null
            when (result) {
                // Send the response exactly as returned.
                is DaaScaResult.Completed -> backend.confirmTransfer(transfer, result.response)
                DaaScaResult.Cancelled -> showMessage("Transfer not approved. You can try again.")
                is DaaScaResult.Failed -> showScaFailure(result)
            }
        }

    fun onSendClicked(transfer: String) {
        backend.startTransfer(transfer) { frameToken ->
            transferId = transfer
            approve.launch(frameToken)
        }
    }
}
```

Save `transferId` in `onSaveInstanceState`. Android can recreate your activity
while the browser is open.

For the backend steps, see
[How-to: Implement Strong Customer Authentication](https://developers.circle.com/digital-asset-accounts/howtos/strong-customer-authentication.md).

## Errors

`DaaScaResult.Failed` carries a `DaaScaError`:

| `error` | Cause | What to do |
| - | - | - |
| `Expired` | The frame token or the ceremony timed out | Ask your backend for a new frame token |
| `AlreadyEnrolled` | The end customer already has a passkey on this device. Enrollment only | Skip enrollment |
| `Unsupported` | The device can't run the ceremony. Read `unsupportedReason` | See the following table |
| `Transport` | The browser couldn't verify your App Link in time, usually because the device is offline | Retry once with a new frame token. If it keeps failing, ask the end customer to check their connection |
| `Protocol` | Circle's page and this SDK version didn't understand each other | Update the SDK. Don't offer a different authentication method instead |

| `unsupportedReason` | Cause |
| - | - |
| `NoAllowedBrowser` | No supported browser is installed |
| `CallbackNotRouted` | The callback activity isn't declared for your callback URL |
| `AppLinkNotVerified` | Android hasn't verified your App Link. Check `assetlinks.json` |
| `InsecureRedirect` | Approval was launched with a custom scheme callback |
| `PasskeyUnavailable` | Passkeys aren't available on this device |

`message` is for your logs. Don't show it to the end customer.

## Versions

The SDK follows semantic versioning. Before version 1.0.0, a minor version can
include breaking changes, so pin an exact version. If you also use the React
Native SDK, use the Android SDK version that it pins.
