React Native SDK for Digital Asset Accounts (DAA) — attested authentication and self-custody DAA API access.
Login is a three-call ceremony — Prepare, Social Session, Device Registration
Complete. Tests can finish the ceremony with ./testing.
-
DaaClient+DaaSdkConfiguration, including the closedkeyPolicy(DEVICE_KEY_POLICY: hardware-backed, per-signature user verification). -
Injected
DaaTransportboundary (DaaHttpRequest/DaaHttpResponse) — the SDK never embeds a service credential. -
Deterministic exact-byte body serialization (serialize once, sign/send the same immutable bytes).
-
Registered-device authorization: compact ES256 JWS in
X-Device-Authorization(authorizeExactRequest/postSignedResource). Not on the public.entry. Tests use the./testingin-memory store; productionsignExactBytesreaches ExposignDeviceAuthorization. Account, transfer, and withdrawalcreateare wired through it. -
The DAA Owner-key interfaces (P-256 / ES256) on the public entry. No generic signing API. The concrete WebCrypto-backed in-memory test double is exported only from the
./testingsubpath and fails closed in a React Native release bundle. -
DaaSdkErrormodel. -
Typed reads for accounts, account limits/usage, deposit addresses, deposits, transfers, withdrawals, and wire/ACH fiat accounts (plus their receiving instructions) behind
DaaUserClient, plus unsigned deposit-address create-or-resolve and the Owner-authorizedaccounts.create/transfers.create/withdrawals.create. All three signed creates take their request shape from the Owner-authorization registry, reject a property it does not name before anything is signed, and fail closed until the AuthKey carries Circle's registration acknowledgement. Account, deposit-address, deposit, transfer, and withdrawal listings return trusted pages: navigation accepts only same-origin links for the originating collection and preserves the API's opaque query exactly.accountLimits.get(accountId, query?)readsGET /v1/accounts/{accountId}/limits/usage; the public API reference (list-account-limits-usage) is its contract.usd_centsandcountvalues are returned unconverted:const usage = await daaUserClient.accountLimits.get('1000662322', { operation: 'withdrawal', window: 'P30D', }) for (const item of usage.items) { console.log(item.operation.kind, item.effectiveThreshold, item.usagePercent) }
Response models are exported as types only: every instance a caller holds came from a response the SDK decoded. Reached through
DaaClient.forUser(context), which opens only for anACTIVEDaaUserContextfrom Device Registration Complete. -
DaaUserClient.refreshContext()and.close()— the lifecycle methods.refreshContextis a signedget_device_contextread (GET /v1/accounts/auth/context), not an unsigned one: it carries a freshX-Device-Authorizationover the empty body byte array and decodes the sameDeviceContextshape Device Registration Complete returns. This SDK holds no retained context object and no cancellable in-flight work, soclose()refuses all further work through this instance:refreshContext, every signedcreateonaccounts/transfers/withdrawals, every unsigned read, the unsigneddepositAddresses.create, andnext()/previous()on a previously returnedPagefail closed withcontract_violationafterward. A request already sent completes. The registered AuthKey itself is untouched. -
AuthClient.prepareSocialLogin— the Prepare call. Mints a registration code verifier and an ephemeral P-256 registration-flow key, thenPOST /v1/accounts/auth/loginRequestswith the S256 challenge and the flow public key. It touches no AuthKey: AuthKey selection comes after the Social Session response, so nothing at Prepare reads the secure-key store. Returns aPreparedSocialLogin(loginRequestId,oidcNonce,oidcState,prepareReceiptJws,expiresAt). No Prepare-receipt trust anchor is pinned in this release, so the SDK ignores any receipt the service sends and login proceeds as if none arrived (prepareReceiptJwsis null).oidcNonceandoidcStateare therefore not authenticated by the SDK, and the receipt never signsoidcState: bind the provider redirect with your app's own OAuthstate, and carryoidcStateinside or next to it.completeSocialSessionpostsPOST /v1/accounts/auth/sessionsand selects the AuthKey only after that response. It takes the provideridTokenand theclientEntityIdof an end user the application's own backend has already onboarded: the service requires that field and Circle never creates the entity, so an application onboards first and passes the id here.PendingDeviceRegistration.completesends Device Registration Complete withX-Registration-Flow-AuthorizationandX-Device-Authorizationover the exact four-field body.
Prepare mints the verifier and flow key through the Expo DaaRegistration
module (DaaSecureRandom / DaaRegistrationFlowKey in the native SDKs), not
through DaaSecureKey and not through host WebCrypto. The same module checks
the receipt signature by delegating to the native SDKs'
DaaPrepareReceiptSignature. A React Native host missing that module fails
closed with contract_violation when minting, and a receipt it cannot verify
for want of the module is prepare_receipt_invalid.
Node tests inject a fake in jest.setup.js.
The Expo DaaSecureKey module (ios/, android/, expo-module.config.json)
delegates Owner AuthKey operations to the native SDKs, and
resolveNativeBridge() loads it via requireOptionalNativeModule — failing
closed in Node and Jest where no native module exists.
X-Device-Authorization is minted in TypeScript (authorizeExactRequest);
production signExactBytes reaches Expo signDeviceAuthorization.
yarn add @circle-fin/daa-react-native-sdk
This package bridges to two native SDKs published as coordinates rather than
vendoring them. The
tarball ships DaaReactNativeSdk.podspec and android/build.gradle, which only
declare the native dependencies — installing from npm does not fetch them. The
host app's own CocoaPods and Gradle resolve them at build time.
iOS. daa-ios-sdk is a CocoaPods spec source, not published to the public
trunk. Add it alongside the CDN in your Podfile, then pod install:
source 'https://github.com/circlefin/daa-ios-sdk.git'
source 'https://cdn.cocoapods.org/'Android. com.circle.daa:daa-android-sdk is published to GitHub Packages,
which needs a GitHub token even for public packages. Add a maven {} block to
your app's root build.gradle, sourced from the environment so nothing is
committed:
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 PAT with read:packages scope |
- The biometric prompt text belongs to the native SDKs. Face ID, Touch ID
and the Android BiometricPrompt show text that the native SDK sets from the
operation being signed, for example
Authorizing: transfer. The text is English only. Your app cannot set or change it. - iOS apps that share Keychain items may lose their AuthKey. The AuthKey now
lives in your app's own application-identifier Keychain group
(
<App ID prefix>.<bundle identifier>). Before, it lived in the first group of yourkeychain-access-groupsentitlement. If you use Keychain Sharing and a shared group is first in that list, the SDK does not find the AuthKey that an earlier version created.resolveOrCreatereturns"created", and the device must register again. Apps without Keychain Sharing, and apps that list their own group first, are not affected. - iOS fails closed when the AuthKey Keychain group cannot be resolved. If the SDK cannot resolve your app's own group, for example in a background launch before the first unlock after boot, the call throws instead of running an unscoped Keychain query. Nothing is cached, so a later call tries again.
- Android needs an Auth Tab browser for a custom-scheme enrollment
redirect. A custom-scheme
redirectUriforenrollnow runs only in an Auth Tab. On a device with no allowed browser that offers one,enrollrejects with aScaErrorwhosecodeisUnsupportedand whosereasonisInsecureRedirect, and nothing opens. AnhttpsApp Link is not affected, andapprovealready refuses a custom scheme.
src/
index.ts Public surface — `.` (types + client + serialization)
testing.ts `./testing` subpath — in-memory secure-key test double
DaaClient.ts Root client + configuration
api/ AuthClient, DaaUserClient + typed resource clients
auth/ Login-attempt material (registration-flow key, verifier, receipt)
models/ Typed account, page, address, deposit, transfer, withdrawal, fiat-account (wire/ACH), money, and prepared-login models
wire/ Response decoding: `data` envelope, payload mapping
transport/ Injected transport boundary
crypto/ Exact-byte serialization, Base64URL, SHA-256, JWK thumbprint, native-backed CSPRNG
securekey/ DAA Owner-key interfaces + in-memory & native-bridge stores
errors.ts DaaSdkError model + DaaError
DaaReactNativeSdk.podspec Declares the DaaSdk CocoaPods dependency
android/build.gradle Declares the com.circle.daa:daa-android-sdk dependency
ios/ Expo DaaSecureKey + DaaRegistration Swift modules
android/src/ Expo DaaSecureKey + DaaRegistration Kotlin modules
expo-module.config.json Expo autolinking
yarn install
yarn test # unit tests (jest + ts-jest)
yarn check:type # tsc --noEmit- Reporting a security vulnerability:
SECURITY.md - Contributing:
CONTRIBUTING.md