Skip to content

tkhq/kotlin-sdk

Turnkey Kotlin SDK

License: Apache 2.0 Static Badge Static Badge

The Turnkey Kotlin/Android SDKs provide everything you need to build a fully working Android app powered by Turnkey: typed HTTP access, auth flows (OAuth / Passkeys / OTP), session + key management, and wallet utilities.

Packages

Package Description Path
sdk-kotlin High‑level Android/Kotlin SDK: init/config, session lifecycle (persist/select/auto‑refresh), OAuth/Passkey/OTP helpers, wallet CRUD, and signing helpers. packages/sdk-kotlin/
http Lower‑level, fully typed HTTP client generated from OpenAPI (OkHttp + coroutines + kotlinx.serialization). packages/http/
crypto P‑256 key utilities and Turnkey bundle (en/de)cryption helpers packages/crypto/
encoding Fast hex <> bytes, Base64url helpers, and secure random. packages/encoding/
passkey Simple passkey registration/assertion wrappers over Credential Manager for Android. packages/passkey/
stamper Produces HTTP request stamps from API keys or passkeys (header pair), consumed by http and usable standalone. packages/stamper/
types Single‑file model set (Models.kt) for all Turnkey request/response types (public + auth‑proxy), generated from OpenAPI. packages/types/
tools Internal codegen utilities used by types and http (generators, helpers). packages/tools/

See each package’s README for usage details and API docs.

Quick start (Android)

1) Add dependencies:

// app/build.gradle.kts
dependencies {
  implementation("com.turnkey:sdk-kotlin:<version>")
  // (transitive) http, types, encoding, crypto, stamper, passkey
}

2) Initialize in your Application class:

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

    val createSubOrgParams = CreateSubOrgParams(
        customWallet = CustomWallet(
            walletName = "Wallet 1",
            walletAccounts = listOf(
                V1WalletAccountParams(
                    addressFormat = V1AddressFormat.ADDRESS_FORMAT_ETHEREUM,
                    curve = V1Curve.CURVE_SECP256K1,
                    path = "m/44'/60'/0'/0/0",
                    pathFormat = V1PathFormat.PATH_FORMAT_BIP32
                ),
                V1WalletAccountParams(
                    addressFormat = V1AddressFormat.ADDRESS_FORMAT_SOLANA,
                    curve = V1Curve.CURVE_ED25519,
                    path = "m/44'/501'/0'/0'",
                    pathFormat = V1PathFormat.PATH_FORMAT_BIP32
                )
            )
        )
    )
      
    TurnkeyContext.init(
      this,
      TurnkeyConfig(
        apiBaseUrl = "https://api.turnkey.com",
        authProxyBaseUrl = "https://authproxy.turnkey.com",
        authProxyConfigId = "<your-auth-proxy-config-id>", 
        organizationId = "<your-organization-id>",
        appScheme = "<your-app-scheme>", // for OAuth deep link
        authConfig = AuthConfig(
          rpId = "<your-rp-id>",
          createSubOrgParams = MethodCreateSubOrgParams(
              emailOtpAuth = createSubOrgParams,
              smsOtpAuth = createSubOrgParams,
              passkeyAuth = createSubOrgParams,
              oAuth = createSubOrgParams
          ),
        )
      )
    )
  }
}

3) Log in (examples)

// OTP
val init = TurnkeyContext.initOtp(
    otpType = OtpType.OTP_TYPE_EMAIL,
    contact = "[email protected]"
)

val session = TurnkeyContext.loginOrSignUpWithOtp(
    otpId = init.otpId,
    otpCode = "123ABC",
    contact = "[email protected]",
    otpType = OtpType.OTP_TYPE_EMAIL
)

// Passkey
val passkey = TurnkeyContext.loginWithPasskey(activity = requireActivity())

// OAuth (Google)
TurnkeyContext.handleGoogleOAuth(activity = requireActivity())

4) Use the client

lifecycleScope.launch {
    val signature = TurnkeyContext.signMessage(
        signWith = "0x...address...",
        addressFormat = V1AddressFormat.ADDRESS_FORMAT_ETHEREUM,
        message = "Hello Turnkey!"
    )
    println("${signature.r}${signature.s}${signature.v}")
}

Verify our artifacts!

At Turnkey, we take security very seriously. We sign every published artifact so you can verify that what you consume is exactly what we released.

1) Download the artifact + its detached signature:

Replace ARTIFACT_ID with the module you’re verifying (e.g. sdk-kotlin, http, types) and VERSION with the version.

ARTIFACT_ID=sdk-kotlin
VERSION=<PACKAGE_VERSION>

# JAR and its .asc (detached signature)
curl -O https://repo1.maven.org/maven2/com/turnkey/${ARTIFACT_ID}/${VERSION}/${ARTIFACT_ID}-${VERSION}-sources.jar
curl -O https://repo1.maven.org/maven2/com/turnkey/${ARTIFACT_ID}/${VERSION}/${ARTIFACT_ID}-${VERSION}-sources.jar.asc

# (Recommended) also verify the POM:
curl -O https://repo1.maven.org/maven2/com/turnkey/${ARTIFACT_ID}/${VERSION}/${ARTIFACT_ID}-${VERSION}.pom
curl -O https://repo1.maven.org/maven2/com/turnkey/${ARTIFACT_ID}/${VERSION}/${ARTIFACT_ID}-${VERSION}.pom.asc

2) Import our public key (one-time)

You can import by fingerprint:

# Replace the fingerprint with the one shown on keys.openpgp.org for [email protected]
gpg --keyserver hkps://keys.openpgp.org --recv-keys PUB_KEY_FINGERPRINT

You can confirm it’s in your keyring:

gpg --list-keys [email protected]

3) Verify signatures:

# Verify sources JAR
gpg --verify ${ARTIFACT_ID}-${VERSION}-sources.jar.asc ${ARTIFACT_ID}-${VERSION}-sources.jar

# Verify POM
gpg --verify ${ARTIFACT_ID}-${VERSION}.pom.asc ${ARTIFACT_ID}-${VERSION}.pom

If everything is good, you’ll see output like:

gpg: Signature made <SIGNATURE_TIMESTAMP>
gpg:                using RSA key <SIGNING_KEY_FINGERPRINT>
gpg: Good signature from "Turnkey Kotlin Publishers <[email protected]>"

Troubleshooting

  • “No public key”: Import the correct fingerprint (see step 2), or gpg --recv-keys again.
  • Key trust level: “Good signature” is what matters for authenticity. You can set ownertrust if you want, but it’s not required to validate the signature.
  • Wrong artifact path: Double-check group ID (com.turnkey vs your snapshot/group), module (ARTIFACT_ID), and VERSION.

Code generation

This repo uses a tools module to generate models and the typed HTTP client from OpenAPI specs.

Quick start

Generate both types and HTTP client:

./gradlew generate

Or run individually:

./gradlew :packages:types:regenerateModels
./gradlew :packages:http:regenerateHttpClient

Details

Artifact Generator Inputs Outputs
types TypesGenerator.kt openapi/public_api.swagger.json, openapi/auth_proxy.swagger.json packages/types/src/main/kotlin/com/turnkey/types/Models.kt
http ClientGenerator.kt openapi/public_api.swagger.json, openapi/auth_proxy.swagger.json packages/http/src/main/kotlin/com/turnkey/http/TurnkeyClient.kt

OpenAPI specs are stored in openapi/ at the repository root (single source of truth).

Generators live in packages/tools/src/main/kotlin/com/turnkey/tools/.

Exact task configuration lives in each module's build.gradle.kts and is documented in the package READMEs.

packages/
  encoding/
  crypto/
  passkey/
  stamper/
  types/
  http/
  sdk-kotlin/
  tools/
examples/
  kotlin-demo-wallet/

Which package should I use?

This SDK is designed in layers, allowing you to choose the right level of abstraction for your needs. Here's how to decide:

Use sdk-kotlin (Highest Level) if:

  • You want the fastest integration - Get up and running in minutes with a familiar API
  • You want everything handled for you - Automatic session management, key-pair storage, expiry timers, and state management via Kotlin Flow
  • You're building a standard mobile app - The singleton pattern and built-in storage work great for typical Android apps
  • You need OAuth, Passkey, or OTP authentication - High-level methods handle the complete flow for you

Trade-off: Less flexibility in architecture. Uses a global singleton pattern which may not fit all app structures.

Use http (Mid Level) if:

  • You need dependency injection - Works seamlessly with Hilt, Koin, or any other DI framework
  • You need fine-grained control over session management - Manage sessions, storage, and expiry logic yourself
  • You want to write unit tests with mocked clients - Easily inject test doubles and mock HTTP responses
  • You have custom storage requirements - Use your own database, encrypted preferences, or remote storage
  • You need to integrate with existing architecture - No singletons, just plain Kotlin classes you control

Trade-off: You'll need to implement session storage, key management, and state handling yourself.

Use crypto + stamper (Lowest Level) if:

  • You need signing primitives - Direct access to P-256 key generation, signing, and encryption utilities
  • You need the most fine-grained control - Build your own HTTP client, session management, and authentication flows
  • You're building non-standard integrations - Server-side Kotlin, custom protocols, or embedded systems
  • You want zero opinions - Just the cryptographic building blocks with no framework assumptions
  • You're integrating Turnkey into existing infrastructure - Use only the pieces you need

Trade-off: You'll build everything from scratch. Only use this if you have specific requirements that the higher-level packages can't meet.

Note

Recommendation: Start with sdk-kotlin for the fastest path to production. If you hit architectural constraints, drop down to http. Only use crypto + stamper if you have specialized needs. You can also mix and match packages to fit your specific requirements!

Development

Requirements

  • JDK/Toolchain: Kotlin JVM toolchain 24 (see module kotlin { jvmToolchain(24) })
  • Android: compileSdk 36 (for Android modules), minSdk varies per package (e.g., passkey/stamper minSdk 28)

Build & test

./gradlew build
./gradlew test

Releasing

This repo uses the Vanniktech Maven Publish plugin.

  1. Ensure changesets are present in the /.changesets directory (CI will check for present changesets and exit if none are present)
  2. Push a release tag that follows the format: v<year>.<month>.<release_no> e.g: v2025.11.1
  3. CI will automatically pick up the tag and kick off the publish flow
  4. Await approval on the publish step and the rest is covered!

Note

CI usually publishes in dependency order. Only modules with version changes will be released. Use prerelease tags like 0.1.0‑beta.1 when needed. Bump precedence for versioning goes as follows: beta > major > minor > patch (meaning if a package has both a major and a patch changeset, the major bump will take precedence e.g. 0.1.0 (major + patch bump) -> 1.0.0)

Important

beta takes precedence over ALL, meaning if a package has a major changeset + a beta changeset, the final version will look like 0.1.0-beta.1 -> 0.1.0-beta.2

Links

Security

  • Private keys and mnemonics never leave the client; keep logging redacted.
  • For passkeys, configure Digital Asset Links so your domain (RP ID) is associated with your app.
  • For dev loopback, prefer 10.0.2.2 on Android emulators; allow cleartext only in debug builds.

License

Apache-2.0

Contributing

  1. Open an issue or draft PR for discussion.
  2. Keep changes small and well‑scoped.
  3. Add tests for bugfixes and new features.
  4. Add a changeset for your changes using the tooling found in the repo root build.gradle.kts (here). More information below.

Changeset Tooling

This SDK uses a custom changeset tooling to handle changelogs & versioning.

Adding a changeset

  1. Go to the repo's root build.gradle.kts (here) and run the createChangeset task. create-changeset.png
  2. Choose which packages to write changesets for select-packages.png
  3. Select the bump type per package (major, minor, patch, or beta) select-bump-type.png
  4. Add a title and a note (end the note with a "." on its own line) changeset-title-note.png

And that's it! Commit your changeset and the CI release tooling will cover the rest (changelogs + versioning)! If done properly, you should see your new changeset in the .changeset directory.

About

Kotlin sdk for android development using Turnkey 🔑

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •  

Languages