> ## 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 React Native SDK

> Reference for the React Native SDK that signs non-custodial account requests with an authorization key and runs passkey approvals

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

The React Native SDK runs in your application on the end customer's iPhone or
Android-powered device. It never holds your API key: every request goes through
your backend.

## What you can do

* **Create and use a non-custodial account**: sign the end customer in, register
  their authorization key, create a
  [non-custodial account](https://developers.circle.com/digital-asset-accounts/concepts/non-custodial-accounts.md),
  and sign transfers and withdrawals from it. See
  [Create a non-custodial account](https://developers.circle.com/digital-asset-accounts/quickstarts/create-non-custodial-account.md).
* **Read accounts and activity**: read accounts, limits, deposit addresses,
  deposits, transactions, transfers, withdrawals, and linked bank accounts.
* **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).

The SDK creates accounts with a `custodyType` of `self` only. Its read methods
also return other account types.

## Requirements

| Requirement | Minimum |
| - | - |
| React Native | 0.81 |
| React | 18 |
| Expo SDK | 54, if your application uses Expo |
| iOS | 15.0 deployment target |
| Android | API level 23 (`minSdk`), compile SDK 36, Android Gradle Plugin 8.9.1 |
| Node.js (builds) | 22 |
| Passkey approvals | iOS 17.4, or Android 9 with a screen lock |

## Install

The SDK has two parts: the `@circle-fin/daa-react-native-sdk` package, and
Circle's [iOS](https://developers.circle.com/sdks/daa/ios-sdk.md) and [Android](https://developers.circle.com/sdks/daa/android-sdk.md) SDKs,
which it calls for authorization keys, signatures, and passkey ceremonies.
Installing the package doesn't download the native SDKs. Your application's
CocoaPods and Gradle builds resolve them.

### Packages

| Package | Purpose | Install |
| - | - | - |
| `@circle-fin/daa-react-native-sdk` | The SDK | `npm install @circle-fin/daa-react-native-sdk` |
| Expo modules | The SDK's native modules are Expo modules. Needed only in a bare React Native application without Expo | `npx install-expo-modules@latest` |
| `expo-crypto` | Generates UUID v4 idempotency keys. The React Native runtime doesn't provide a `crypto` global | `npx expo install expo-crypto` |

`npx expo install` picks the `expo-crypto` version that matches your
application's Expo SDK.

### iOS native SDK

The iOS native SDK is distributed from a CocoaPods spec repository. Your
`Podfile` lists it before the CocoaPods CDN source, and `pod install` resolves
it:

```ruby theme={null}
source "https://github.com/circlefin/daa-ios-sdk.git"
source "https://cdn.cocoapods.org/"
```

Put both `source` lines at the top of the `Podfile`, before any `target` block.

### Android native SDK

The Android native SDK is distributed from GitHub Packages, which needs a GitHub
account to read even public packages. Your application's root `build.gradle`
lists it in the `repositories` block. Read the values from environment variables
so that no credentials are committed:

```gradle theme={null}
allprojects {
    repositories {
        // Your existing repositories
        maven {
            url 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 |

The Android native SDK needs `compileSdkVersion` 36 or later.

### Builds and devices

The SDK's native modules are linked when your application's native projects
build, for example with `npx expo run:ios` and `npx expo run:android`, or
`npx react-native run-ios` and `npx react-native run-android` in a bare React
Native application. Run your application on a physical iPhone or Android-powered
device. The authorization key is hardware-backed, so simulators and emulators
without secure key hardware can't create one.

## Initialize

The SDK has two clients. Create the one for each job that your application does.

### Non-custodial accounts

`DaaClient` takes four required fields:

| Field | Description |
| - | - |
| `appId` | A stable identifier that you choose, 1 to 41 characters from `A-Za-z0-9._-`. It names your application's authorization keys on the device and isn't sent to Circle |
| `environment` | `sandbox` or `production` |
| `transport` | Your `DaaTransport` implementation. It sends each request to your backend relay. The SDK never holds an API key |
| `keyPolicy` | Must be `DEVICE_KEY_POLICY`. The SDK rejects any other value |

```typescript theme={null}
import { DaaClient, DEVICE_KEY_POLICY } from "@circle-fin/daa-react-native-sdk";

const daa: DaaClient = new DaaClient({
  appId: "YOUR_APP_ID",
  environment: "sandbox",
  transport: backendTransport,
  keyPolicy: DEVICE_KEY_POLICY,
});
```

For a `DaaTransport` that sends requests to your relay, see
[Connect your application](https://developers.circle.com/digital-asset-accounts/quickstarts/create-non-custodial-account.md#step-2-connect-your-application).

### Passkey approvals

`createScaClient` returns a client that runs Circle's passkey ceremony in a
system browser sheet: `ASWebAuthenticationSession` on iOS, and an Auth Tab or
Custom Tab on Android.

```typescript theme={null}
import {
  createScaClient,
  type ScaClient,
} from "@circle-fin/daa-react-native-sdk";

const sca: ScaClient = createScaClient({
  environment: "sandbox",
  redirectUri: "https://app.example.com/sca/callback",
});
```

| Field | Required | Description |
| - | - | - |
| `environment` | Yes | `sandbox` or `production` |
| `redirectUri` | Yes | Your registered `https` app callback URI, exactly as registered |
| `approveTimeoutMs` | No | How long an approval can take, in milliseconds. Defaults to 300000. Must be from 30000 to 3600000 |
| `enrollTimeoutMs` | No | How long an enrollment can take, in milliseconds. Defaults to 600000. Must be from 30000 to 3600000 |

Before the first ceremony, set up the callback on each platform. See
[Set up the callback](https://developers.circle.com/sdks/daa/ios-sdk.md#set-up-the-callback) in the iOS SDK
reference and [Set up the callback](https://developers.circle.com/sdks/daa/android-sdk.md#set-up-the-callback)
in the Android SDK reference. The React Native SDK uses the same setup.

## Methods

Each table lists a client's methods, the API operation that each one calls, and
whether the end customer's authorization key signs it. Your backend relay must
forward each operation that your application uses. See
[Set up a backend relay](https://developers.circle.com/digital-asset-accounts/quickstarts/create-non-custodial-account.md#step-1-set-up-a-backend-relay).

> [!WARNING]
>
> Your relay adds your API key, which can act for all of your end customers, to
> every request. It must authenticate the end customer and forward only the SDK's
> routes. It must forward account, transfer, and withdrawal requests only for the
> signed-in end customer's own non-custodial account. Circle checks the end
> customer's signature only on non-custodial accounts, and your API key alone can
> debit a custodial account. The relay must also check that every unsigned
> request, such as a read, belongs to the signed-in end customer's
> `clientEntityId`.

### Sign-in

Sign-in is a chain: each call returns the object that you call next.
`DaaClient.auth` is an `AuthClient`.

| Method | Returns | Endpoint | End customer signature |
| - | - | - | - |
| `AuthClient.prepareSocialLogin({ provider })` | `PreparedSocialLogin` | [`POST /v1/accounts/auth/loginRequests`](https://developers.circle.com/api-reference/digital-asset-accounts/all/prepare-social-login) | No |
| `PreparedSocialLogin.completeSocialSession({ idToken, clientEntityId })` | `PendingDeviceRegistration` | [`POST /v1/accounts/auth/sessions`](https://developers.circle.com/api-reference/digital-asset-accounts/all/complete-social-session) | No |
| `PreparedSocialLogin.abandon()` | Nothing | None. Discards the prepared login | No |
| `PendingDeviceRegistration.complete()` | `DeviceRegistrationResult` | [`POST /v1/accounts/auth/deviceKeyRegistration`](https://developers.circle.com/api-reference/digital-asset-accounts/all/complete-device-registration) | Yes |
| `DaaClient.forUser(context)` | `DaaUserClient` | None. Runs on the device | No |

You can use each `PreparedSocialLogin` and `PendingDeviceRegistration` once.
When `complete()` returns a result whose `status` is `ACTIVE`, pass its
`context` to `forUser`.

### Signed-in end customer

`DaaUserClient` holds the clients in the rest of this section.

| Method | Returns | Endpoint | End customer signature |
| - | - | - | - |
| `DaaUserClient.refreshContext()` | `DeviceContext` | [`GET /v1/accounts/auth/context`](https://developers.circle.com/api-reference/digital-asset-accounts/all/get-device-context) | Yes |
| `DaaUserClient.close()` | Nothing | None. Every later call on this client fails | No |

### Accounts

| Method | Returns | Endpoint | End customer signature |
| - | - | - | - |
| `accounts.create(request)` | `Account` | [`POST /v1/accounts`](https://developers.circle.com/api-reference/digital-asset-accounts/all/create-account) | Yes |
| `accounts.list(query?)` | `Page<Account>` | [`GET /v1/accounts`](https://developers.circle.com/api-reference/digital-asset-accounts/all/list-accounts) | No |
| `accounts.get(accountId)` | `Account` | [`GET /v1/accounts/{id}`](https://developers.circle.com/api-reference/digital-asset-accounts/all/get-account) | No |
| `accountLimits.get(accountId, query?)` | `AccountLimitsUsage` | [`GET /v1/accounts/{id}/limits/usage`](https://developers.circle.com/api-reference/digital-asset-accounts/all/list-account-limits-usage) | No |
| `depositAddresses.create(request)` | `AddressObject` | [`POST /v1/accounts/addresses/deposit`](https://developers.circle.com/api-reference/digital-asset-accounts/all/create-account-deposit-address) | No |
| `depositAddresses.list(query?)` | `Page<AddressObject>` | [`GET /v1/accounts/addresses/deposit`](https://developers.circle.com/api-reference/digital-asset-accounts/all/list-account-deposit-addresses) | No |
| `depositAddresses.get(depositAddressId)` | `AddressObject` | [`GET /v1/accounts/addresses/deposit/{id}`](https://developers.circle.com/api-reference/digital-asset-accounts/all/get-account-deposit-address) | No |

### Money movement

| Method | Returns | Endpoint | End customer signature |
| - | - | - | - |
| `transfers.create(request)` | `Transfer` | [`POST /v1/accounts/transfers`](https://developers.circle.com/api-reference/digital-asset-accounts/all/create-account-transfer) | Yes |
| `transfers.list(query?)` | `Page<Transfer>` | [`GET /v1/accounts/transfers`](https://developers.circle.com/api-reference/digital-asset-accounts/all/list-account-transfers) | No |
| `transfers.get(transferId)` | `Transfer` | [`GET /v1/accounts/transfers/{id}`](https://developers.circle.com/api-reference/digital-asset-accounts/all/get-account-transfer) | No |
| `withdrawals.create(request)` | `WithdrawalDetail` | [`POST /v1/accounts/withdrawals`](https://developers.circle.com/api-reference/digital-asset-accounts/all/create-account-withdrawal) | Yes |
| `withdrawals.list(query?)` | `Page<WithdrawalDetail>` | [`GET /v1/accounts/withdrawals`](https://developers.circle.com/api-reference/digital-asset-accounts/all/list-account-withdrawals) | No |
| `withdrawals.get(withdrawalId)` | `WithdrawalDetail` | [`GET /v1/accounts/withdrawals/{id}`](https://developers.circle.com/api-reference/digital-asset-accounts/all/get-account-withdrawal) | No |
| `deposits.list(query?)` | `Page<DepositSummary>` | [`GET /v1/accounts/deposits`](https://developers.circle.com/api-reference/digital-asset-accounts/all/list-account-deposits) | No |
| `deposits.get(depositId)` | `Deposit` | [`GET /v1/accounts/deposits/{id}`](https://developers.circle.com/api-reference/digital-asset-accounts/all/get-account-deposit) | No |
| `transactions.list(query?)` | `Page<Transaction>` | [`GET /v1/accounts/transactions`](https://developers.circle.com/api-reference/digital-asset-accounts/all/list-account-transactions) | No |

### Bank accounts

| Method | Returns | Endpoint | End customer signature |
| - | - | - | - |
| `fiatAccounts.wires.list(query?)` | `WireSummary[]` | [`GET /v1/banks/wires`](https://developers.circle.com/api-reference/digital-asset-accounts/all/list-wire-accounts) | No |
| `fiatAccounts.wires.get(fiatAccountId)` | `Wire` | [`GET /v1/banks/wires/{id}`](https://developers.circle.com/api-reference/digital-asset-accounts/all/get-wire-account) | No |
| `fiatAccounts.wires.getInstructions(fiatAccountId, query?)` | `WireInstruction` | [`GET /v1/banks/wires/{id}/instructions`](https://developers.circle.com/api-reference/digital-asset-accounts/all/get-wire-account-instructions) | No |
| `fiatAccounts.ach.list({ clientEntityId })` | `ACH[]` | [`GET /v1/banks/ach`](https://developers.circle.com/api-reference/digital-asset-accounts/all/list-achaccounts) | No |
| `fiatAccounts.ach.get(fiatAccountId)` | `ACH` | [`GET /v1/banks/ach/{id}`](https://developers.circle.com/api-reference/digital-asset-accounts/all/get-achaccount) | No |
| `fiatAccounts.ach.getInstructions(fiatAccountId)` | `ACHInstruction` | [`GET /v1/banks/ach/{id}/instructions`](https://developers.circle.com/api-reference/digital-asset-accounts/all/get-achaccount-instructions) | No |

### Passkey approvals

| Method | Returns | Description |
| - | - | - |
| `ScaClient.enroll(frameToken)` | `string` | Runs passkey enrollment. Send the returned registration response to your backend unchanged |
| `ScaClient.approve(frameToken)` | `string` | Runs an approval. Your backend sends the returned value, unchanged, in the `X-Sca-Assertion` header |
| `ScaClient.capabilities()` | `ScaCapabilities` | Reports whether this device can run a ceremony, such as whether the callback is set up and platform passkeys are available |

Your backend gets each `frameToken` from Circle. One ceremony runs at a time.

### Pages

List methods return a `Page`. `items` holds the results. `next()` and
`previous()` return the adjacent page, or `null` when there isn't one.

```typescript theme={null}
import type { Page, Transfer } from "@circle-fin/daa-react-native-sdk";

let page: Page<Transfer> | null = await user.transfers.list();
while (page !== null) {
  for (const transfer of page.items) {
    console.log(transfer.id);
  }
  page = await page.next();
}
```

## Limitations

* **Recipient addresses, account update, and account archive**: not in the SDK.
  Call these from your backend.

For limitations of non-custodial accounts themselves, see
[Limitations in the sandbox preview](https://developers.circle.com/digital-asset-accounts/concepts/non-custodial-accounts.md#limitations-in-the-sandbox-preview).

## Errors

The non-custodial clients throw `DaaError`. The passkey client throws
`ScaError`.

### `DaaError`

Check an error with `isDaaError`, then read `err.detail.kind`:

```typescript theme={null}
import { isDaaError } from "@circle-fin/daa-react-native-sdk";

try {
  await user.transfers.create(transferRequest);
} catch (err: unknown) {
  if (isDaaError(err) && err.detail.kind === "transport") {
    // You don't know whether Circle received the transfer. Retry with the same
    // body and idempotency key, or read the original transfer.
  }
}
```

| `kind` | Cause | What to do |
| - | - | - |
| `api` | Circle returned an error status. Read `httpStatus` and `daaStatusCode` | Retry only when `retryable` is `true`, with the same body and key |
| `idempotency_conflict` | Circle already has a transfer or withdrawal with this key. Account creation never throws it | Find the original with a `list` call. Use a new key only if it's `failed` |
| `transport` | Your `DaaTransport` threw before a response arrived, so you don't know whether Circle received the request. `err.cause` holds your error | Retry with the same body and the `idempotencyKey` on the error, or read the original resource |
| `login_request_expired` | The prepared login expired | Start again with a new `prepareSocialLogin` call |
| `local_user_authentication_required` | The end customer didn't confirm with biometrics or, on an iPhone, the screen lock | Ask the end customer to try again |
| `device_not_initialized` | The authorization key isn't available on this device | Sign the end customer in again |
| `contract_violation` | A request or response didn't match the expected format, a key operation on the device failed, or the registered authorization key is no longer active | An integration bug, such as a changed body in your relay. Don't retry automatically |

During `completeSocialSession`, an `api` error with `httpStatus` `403` means
Circle didn't accept the sign-in. For example:

* A different Google Account is already linked to the `clientEntityId` that you
  sent.
* The ID token is invalid or expired.
* The ID token's nonce doesn't match the prepared login.
* The end customer is blocked.
* In production, the ID token's `aud` isn't an approved client ID. See
  [Register your Google client IDs](https://developers.circle.com/digital-asset-accounts/references/going-to-production.md#register-your-google-client-ids).

Start again with a new `prepareSocialLogin` call. Don't retry with a different
`clientEntityId`.

During sign-in (`prepareSocialLogin`, `completeSocialSession`, and `complete`),
errors that your `DaaTransport` throws pass through unchanged and aren't
`DaaError`. Start again with a new `prepareSocialLogin` call.

Never log ID tokens, signatures, or key material.

### `ScaError`

Check an error with `isScaError`, then read `err.code`. `err.reason` narrows
some codes, such as `NoAllowedBrowser` or `CallbackNotRouted` for `Unsupported`,
and is `null` otherwise.

```typescript theme={null}
import { isScaError } from "@circle-fin/daa-react-native-sdk";

try {
  const assertion: string = await sca.approve(frameToken);
} catch (err: unknown) {
  if (isScaError(err) && err.code === "Cancelled") {
    // The end customer closed the sheet. Let them try again.
  }
}
```

| `code` | Cause | What to do |
| - | - | - |
| `Cancelled` | The end customer closed the ceremony | Let them try again |
| `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 | Skip enrollment |
| `Unsupported` | The device can't run the ceremony. Read `reason` | Check `capabilities()`, your callback setup, and the device's browser |
| `CeremonyInProgress` | Another ceremony is already open | Wait for it to finish |
| `Transport` | On Android, 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` | The ceremony returned an unexpected result, such as an invalid frame token | An integration bug. Don't offer a different authentication method instead |
| `InvalidConfiguration` | A `createScaClient` field is invalid | Fix the configuration |
| `NativeModuleUnavailable` | The native module isn't linked | Rebuild your application's native projects |

## Versions

The SDK follows semantic versioning. Before version 1.0.0, a minor version can
include breaking changes, so pin an exact version. Each version of the package
pins the iOS and Android SDK versions that it was tested with.
