{"id":51485851,"url":"https://github.com/0xsequence/kotlin-sdk","last_synced_at":"2026-07-07T06:30:44.133Z","repository":{"id":362317974,"uuid":"1202661808","full_name":"0xsequence/kotlin-sdk","owner":"0xsequence","description":"Kotlin SDK for Embedded Wallets","archived":false,"fork":false,"pushed_at":"2026-07-02T14:17:10.000Z","size":678,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-07-02T16:08:01.320Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/0xsequence.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-06T09:08:13.000Z","updated_at":"2026-06-30T11:00:32.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/0xsequence/kotlin-sdk","commit_stats":null,"previous_names":["0xsequence/kotlin-sdk"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/0xsequence/kotlin-sdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xsequence%2Fkotlin-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xsequence%2Fkotlin-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xsequence%2Fkotlin-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xsequence%2Fkotlin-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/0xsequence","download_url":"https://codeload.github.com/0xsequence/kotlin-sdk/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xsequence%2Fkotlin-sdk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35218117,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-07T02:00:07.222Z","response_time":90,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-07T06:30:43.374Z","updated_at":"2026-07-07T06:30:44.120Z","avatar_url":"https://github.com/0xsequence.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# OMS Client Kotlin SDK\n\nAndroid and Kotlin SDK for wallet, auth, signing, and API/indexer integrations.\n\n## Installation\n\nMaven Central:\n\n```kotlin\nimplementation(\"io.github.0xsequence:oms-client-kotlin-sdk:0.1.0-alpha.4\")\n```\n\nThis is the only artifact consumers add. The generated WaaS client is packaged\ninside the AAR as an internal implementation detail; consumers should use the\nSDK APIs documented below instead of importing generated classes.\n\n## What It Provides\n\n- email sign-in flow against the wallet API\n- OIDC ID-token sign-in flow against the wallet API\n- non-extractable Android Keystore request credential for wallet API signing\n- persisted wallet session metadata\n- wallet selection and wallet creation flows\n- message signing\n- typed-data signing\n- transaction sending and contract calls\n- transaction status lookup\n- wallet access listing and revocation\n- message and typed-data signature verification through WaaS\n- native and token balance lookups plus transaction history through the indexer\n  service, including optional token contract info and token metadata\n- unit formatting and parsing helpers for raw token amounts\n\n## Requirements\n\n- Android 10 / API 29 or newer\n- Android `compileSdk 34` or newer\n- Java 17 Android compile options\n- Kotlin/Android app using the Android library module\n- a valid `publishableKey`\n\nThe SDK does not require consumer apps to enable core library desugaring.\n\nThe published artifact declares `minSdk 24` so apps with lower manifest floors,\nincluding Expo/React Native apps, can include the dependency. This is a packaging\ncompatibility floor; the SDK requires Android 10 / API 29 or newer at runtime\nbecause the service endpoints require TLS 1.3.\n\nThe sample app in this repository uses additional Google Sign-In / AndroidX\nCredential Manager dependencies and therefore compiles with SDK 35. That sample\napp requirement does not raise the published SDK artifact's consumer\n`compileSdk` floor.\n\n## Quick Start\n\nCreate the SDK with the Android-friendly constructor:\n\n```kotlin\nval client = OMSClient(\n    context = context,\n    publishableKey = \"YOUR_PUBLISHABLE_KEY\",\n)\n```\n\nThe SDK derives Wallet API and IndexerGateway routing from the publishable key.\nSession restore persists completed wallet-session metadata only; it does not\nstore private signing material.\n\nPending email OTP state is kept in memory. OIDC redirect state is stored only to\ncomplete the browser redirect flow and is cleared when the flow completes, fails,\nor is replaced.\n\nExpired sessions are made inactive before protected wallet operations and throw\n`OmsSessionException` with `code = OmsSdkErrorCode.SessionExpired`. The SDK\nclears the active signer/session state, but keeps expired completed-session\nmetadata in storage until the app starts a new auth flow or calls `signOut()`.\nSubscribe with `client.wallet.onSessionExpired { event -\u003e ... }` to route users\nback to sign-in while preserving the expired session snapshot for reauth.\nListeners are delivered on the Android main thread.\n\n## Example Flow\n\n`OMSClient` restores a persisted session automatically when it is created. Apps\ncan hide sign-in controls while a wallet is selected, but starting a new auth\nflow intentionally replaces any existing wallet session so users can re-auth or\nswitch accounts:\n\nBy default email OTP and OIDC ID-token auth completion use\n`WalletSelectionBehavior.Automatic`. They select a wallet for the requested\nwallet type, create one when none exists, and return\n`CompleteAuthResult.WalletSelected`. If more than one matching wallet exists,\nautomatic mode selects the first matching wallet returned by WaaS. Use manual\nmode for apps that need to let users choose between multiple wallets.\n\nCompleted auth requests ask WaaS for a one-week session lifetime by default\n(`WalletClient.DEFAULT_SESSION_LIFETIME_SECONDS`, `604_800` seconds).\nPass `sessionLifetimeSeconds` to `completeEmailAuth`, `signInWithOidcIdToken`,\nor `handleOidcRedirectCallback` to request a different positive whole-number\nlifetime in seconds. Invalid lifetimes are reported as\n`OmsSdkErrorCode.ValidationError`.\n\n```kotlin\nif (client.wallet.walletAddress == null) {\n    client.wallet.startEmailAuth(\"user@example.com\")\n    // A one-time code is sent to the user's email inbox.\n    val result = client.wallet.completeEmailAuth(\"123456\")\n    check(result is CompleteAuthResult.WalletSelected)\n    showWallet(result.wallet)\n}\n```\n\nFor OIDC ID-token flows such as Google Sign-In with Credential Manager:\n\n```kotlin\nval result =\n    client.wallet.signInWithOidcIdToken(\n        idToken = googleIdToken,\n        issuer = \"https://accounts.google.com\",\n        audience = \"YOUR_WEB_CLIENT_ID\",\n    )\ncheck(result is CompleteAuthResult.WalletSelected)\nshowWallet(result.wallet)\n```\n\nFor OIDC authorization-code PKCE redirect flows, start the redirect, open the\nreturned URL with your browser or Custom Tabs, then safely handle incoming app\nlinks from `onCreate` / `onNewIntent`:\n\n```kotlin\nval started = client.wallet.startOidcRedirectAuth(\n    provider = OidcProviders.google(),\n    redirectUri = \"yourapp://auth/callback\",\n)\n\n// Open started.authorizationUrl.\n\nwhen (val result = client.wallet.handleOidcRedirectCallback(intent.data?.toString())) {\n    is OidcRedirectAuthResult.Completed -\u003e showWallet(result.wallet)\n    OidcRedirectAuthResult.NotOidcRedirectCallback -\u003e Unit\n    OidcRedirectAuthResult.NoPendingAuth -\u003e Unit\n    is OidcRedirectAuthResult.Failed -\u003e showRestartSignIn(result.error)\n}\n```\n\nUse a redirect URI that matches a deep link registered by your app, such as\n`yourapp://auth/callback`. If your Google OAuth setup uses a custom web client\nID, pass it with `OidcProviders.google(clientId = \"YOUR_WEB_CLIENT_ID\")`.\nPass `loginHint` only when you want to prefill or select a specific Google\naccount, such as during session-expiry reauth. When omitted, the SDK falls back\nto the previous active session email when one exists before redirect auth\nstarts. Pass an empty string to force no `login_hint` for a call. Non-Google\nproviders do not receive `login_hint`.\n\nWith the default automatic behavior, a successful redirect callback returns\n`OidcRedirectAuthResult.Completed`; `WalletSelection` is only a successful branch\nwhen the callback is handled with manual wallet selection.\n\nTo use your own wallet-selection UI, pass\n`walletSelection = WalletSelectionBehavior.Manual` when completing auth:\n\n```kotlin\nval result =\n    client.wallet.completeEmailAuth(\n        code = \"123456\",\n        walletSelection = WalletSelectionBehavior.Manual,\n    )\ncheck(result is CompleteAuthResult.WalletSelection)\n\nval selected = selectOrCreateWallet(result.pendingSelection)\nshowWallet(selected.wallet)\n```\n\nManual mode completes auth but does not select or create a wallet until the app\ncalls `pendingSelection.selectWallet(...)` or\n`pendingSelection.createAndSelectWallet(...)`. `pendingSelection.wallets` is\nalready filtered to the requested wallet type, so the app picker can show those\nwallets plus a \"Create New Wallet\" action:\n\n```kotlin\nprivate suspend fun selectOrCreateWallet(\n    pendingSelection: PendingWalletSelection,\n): WalletSelectionResult {\n    val choice =\n        showWalletPickerAndWaitForChoice(\n            wallets = pendingSelection.wallets,\n            includeCreateNewWallet = true,\n        )\n\n    return when (choice) {\n        WalletPickerChoice.CreateNew -\u003e\n            pendingSelection.createAndSelectWallet()\n        is WalletPickerChoice.Existing -\u003e\n            pendingSelection.selectWallet(choice.wallet.id)\n    }\n}\n```\n\n`WalletPickerChoice` is app UI state in this example. Both SDK calls return the\nselected wallet and persist it as the active wallet session.\n\nFor OIDC redirect auth, pass the same behavior when handling the callback:\n\n```kotlin\nwhen (\n    val result =\n        client.wallet.handleOidcRedirectCallback(\n            callbackUrl = intent.data?.toString(),\n            walletSelection = WalletSelectionBehavior.Manual,\n        )\n) {\n    is OidcRedirectAuthResult.WalletSelection -\u003e {\n        val selected = selectOrCreateWallet(result.pendingSelection)\n        showWallet(selected.wallet)\n    }\n    OidcRedirectAuthResult.NotOidcRedirectCallback -\u003e Unit\n    OidcRedirectAuthResult.NoPendingAuth -\u003e Unit\n    is OidcRedirectAuthResult.Failed -\u003e showRestartSignIn(result.error)\n    is OidcRedirectAuthResult.Completed -\u003e error(\"Expected manual wallet selection\")\n}\n```\n\nUseful state checks:\n\n```kotlin\nval walletAddress = client.session.walletAddress\nval expiresAt = client.session.expiresAt\nval loginType = client.session.loginType\nval sessionEmail = client.session.sessionEmail\n```\n\n`expiresAt` is an ISO-8601 timestamp string returned by the wallet API.\n\n`client.session` only reports completed wallet-session state. It does not\ninclude pending auth progress. Show OTP or redirect waiting UI from the method\nresult that started the flow, not from session state. Always pass incoming app\nlinks to `handleOidcRedirectCallback`;\nif it returns `NoPendingAuth`, show sign-in UI and let the user start again. A\nfresh SDK instance restores completed wallet sessions, including the session\nexpiry, login type, and email returned by the wallet API, but not email OTP\npending state. Completed auth requests ask the wallet API for a one-week session\nlifetime by default; pass `sessionLifetimeSeconds` to request a different\npositive whole-number lifetime in seconds. Auth completion loads all wallet\npages before selecting or creating a wallet. If auth completes but wallet\nselection, wallet creation, or session persistence fails, the SDK clears the\nin-memory auth session instead of retaining unrecoverable transient state.\n\nUse the selected wallet:\n\n```kotlin\nval network = Network.AMOY\nval typedDataJson =\n    buildJsonObject {\n        putJsonObject(\"types\") {\n            putJsonArray(\"EIP712Domain\") {\n                add(buildJsonObject {\n                    put(\"name\", \"name\")\n                    put(\"type\", \"string\")\n                })\n                add(buildJsonObject {\n                    put(\"name\", \"version\")\n                    put(\"type\", \"string\")\n                })\n                add(buildJsonObject {\n                    put(\"name\", \"chainId\")\n                    put(\"type\", \"uint256\")\n                })\n            }\n            putJsonArray(\"Message\") {\n                add(buildJsonObject {\n                    put(\"name\", \"contents\")\n                    put(\"type\", \"string\")\n                })\n            }\n        }\n        put(\"primaryType\", \"Message\")\n        putJsonObject(\"domain\") {\n            put(\"name\", \"OMS Client\")\n            put(\"version\", \"1\")\n            put(\"chainId\", JsonPrimitive(network.id.toLong()))\n        }\n        putJsonObject(\"message\") {\n            put(\"contents\", \"hello from android\")\n        }\n    }\n\nval signResult = client.wallet.signMessage(\n    network = network,\n    message = \"hello from android\",\n)\n\nval verifyResult = client.wallet.isValidMessageSignature(\n    network = network,\n    message = \"hello from android\",\n    signature = signResult,\n)\n\nval typedSignature = client.wallet.signTypedData(\n    network = network,\n    typedData = typedDataJson,\n)\n\nval txResult = client.wallet.sendTransaction(\n    network = network,\n    to = \"0xE5E8B483FfC05967FcFed58cc98D053265af6D99\",\n    value = parseUnits(\"0.01\", 18),\n)\n```\n\n`sendTransaction` prepares and executes the transaction, then polls the WaaS\nstatus endpoint briefly for an executed status or transaction hash. If the\ntransaction is still pending when polling times out, the response keeps the\n`txnId` with `status = TransactionStatus.Pending` and `txnHash = null`.\nTransaction values are raw base-unit integers. Use `parseUnits` to convert\nhuman-entered decimal values before sending. Import the helpers from\n`com.omsclient.kotlin_sdk.utils`.\n\n## Errors\n\nPublic SDK APIs throw `OmsSdkException` subclasses with stable fields such as\n`code`, `operation`, `status`, nullable `retryable`, and `txnId`. When a failure comes\nfrom a remote OMS service response or transport failure, the error also includes\n`upstreamError` with normalized WaaS or indexer details for logging and\nservice-specific troubleshooting. Application logic should usually branch on the\nSDK-level `code`.\n\nFor transaction writes, `TransactionExecutionUnconfirmed` means the SDK has a\n`txnId` from preparation, but the execute request failed before the SDK could\nconfirm whether the transaction was submitted; do not blindly resend the same\nwrite. `TransactionStatusLookupFailed` means the transaction was submitted but\nstatus polling failed, so retry status lookup with the returned `txnId`.\n`retryable` describes the failed SDK operation, not the whole user intent.\n\n```kotlin\ntry {\n    client.wallet.startEmailAuth(\"user@example.com\")\n} catch (error: OmsSdkException) {\n    println(\"${error.code} ${error.operation?.id} ${error.upstreamError}\")\n}\n```\n\nFor raw token amount formatting and parsing:\n\n```kotlin\nval rawAmount = parseUnits(\"1.5\", 18)\nval displayAmount = formatUnits(rawAmount, 18)\n```\n\nFor indexer balance lookups:\n\n```kotlin\nval walletAddress = requireNotNull(client.wallet.walletAddress)\n\nval tokenBalances = client.indexer.getBalances(\n    walletAddress = walletAddress,\n    networks = listOf(network),\n    contractAddresses = listOf(\"0xTokenContract\"),\n    includeMetadata = true,\n)\n\ntokenBalances.nativeBalances.forEach { balance -\u003e\n    println(\"${balance.symbol.orEmpty()} ${balance.balance.orEmpty()}\")\n}\n\ntokenBalances.balances.forEach { balance -\u003e\n    println(\"${balance.contractInfo?.symbol.orEmpty()} ${balance.contractInfo?.decimals ?: 0}\")\n}\n```\n\nPass `includeMetadata = true` when you need token contract details or NFT/token\nmetadata from `balance.contractInfo` and `balance.tokenMetadata`.\n\nFor transaction history:\n\n```kotlin\nval history = client.indexer.getTransactionHistory(\n    walletAddress = walletAddress,\n    networks = listOf(network),\n)\n```\n\nFor raw calldata or transaction parameters beyond `to` and `value`, use the request overload:\n\n```kotlin\nval network = Network.AMOY\n\nval txResult = client.wallet.sendTransaction(\n    network = network,\n    request = SendTransactionRequest(\n        to = \"0xContractAddress\",\n        value = parseUnits(\"0\", 18),\n        data = \"0x1234\",\n        mode = TransactionMode.Native,\n    ),\n)\n```\n\nFor WaaS ABI-style contract calls, use `callContract`:\n\n```kotlin\nval txResult = client.wallet.callContract(\n    network = network,\n    contract = \"0xContractAddress\",\n    method = \"transfer(address,uint256)\",\n    args =\n        listOf(\n            AbiArg(type = \"address\", value = JsonPrimitive(\"0xRecipient\")),\n            AbiArg(type = \"uint256\", value = JsonPrimitive(\"1000000000000000000\")),\n        ),\n)\n```\n\nTo pick the first fee option the selected wallet can afford, pass the built-in\nselector:\n\n```kotlin\nval txResult = client.wallet.sendTransaction(\n    network = network,\n    request = SendTransactionRequest(\n        to = \"0xContractAddress\",\n        value = parseUnits(\"0\", 18),\n        data = \"0x1234\",\n        mode = TransactionMode.Native,\n    ),\n    selectFeeOption = FeeOptionSelector.firstAvailable,\n)\n```\n\nFor a custom fee picker, return the selected option's `selection`:\n\n```kotlin\nval txResult = client.wallet.sendTransaction(\n    network = network,\n    request = SendTransactionRequest(\n        to = \"0xContractAddress\",\n        value = parseUnits(\"0\", 18),\n        data = \"0x1234\",\n        mode = TransactionMode.Native,\n    ),\n) { feeOptions -\u003e\n    val selected = showFeePickerAndWaitForChoice(feeOptions)\n    selected.selection\n}\n```\n\nThe selector receives `FeeOptionWithBalance` values. `balance` is the selected\nwallet's raw indexer balance for that fee token when available. `available` is\nformatted with the token decimals, while `availableRaw` keeps the raw integer\nvalue. `decimals` is exposed as a regular `Int`. `selection` preserves the\nAPI-provided `tokenID` when present and falls back to the token symbol. Sponsored\ntransactions skip fee selection; unsponsored transactions fail before execute\nwhen no fee option can be selected.\n\nTo refresh a transaction later or manage active wallet credentials:\n\n```kotlin\nval status = client.wallet.getTransactionStatus(txnId = txResult.txnId)\nval idToken = client.wallet.getIdToken(ttlSeconds = 300u)\nval credentials = client.wallet.listAccess(pageSize = 25u)\nclient.wallet.listAccessPages(pageSize = 25u).collect { page -\u003e\n    renderCredentials(page.credentials)\n}\n\ncredentials\n    .firstOrNull { !it.isCaller }\n    ?.let { client.wallet.revokeAccess(targetCredentialId = it.credentialId) }\n```\n\n## API Reference\n\nThe full public API surface is documented in [docs/api.md](docs/api.md).\n\n## Sample App\n\nThis repository includes an Android sample app in [`app/`](app/) that demonstrates:\n\n- Google sign-in with Android Credential Manager\n- Google OIDC redirect sign-in\n- email sign-in\n- custom session lifetime input for expiry testing\n- expired-session reauth UI\n- wallet selection after sign-in\n- message signing and verification\n- transaction sending\n\n## Build From Source\n\nTo enable the local pre-push Kotlin style gate for this checkout:\n\n```sh\ntools/install-git-hooks.sh\n```\n\nThe hook runs `./gradlew ktlintCheck` before push. This is intentionally local\nand is not wired into GitHub CI.\n\n```sh\n./gradlew :oms-client-kotlin-sdk:testDebugUnitTest\n./gradlew ktlintCheck\n./gradlew :oms-client-kotlin-sdk:lintDebug\n./gradlew :app:lintDebug\n./gradlew :app:assembleDebug\n```\n\n## Publishing\n\nSee [publishing.md](publishing.md) for release PR and Maven Central publishing\nsteps.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0xsequence%2Fkotlin-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F0xsequence%2Fkotlin-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0xsequence%2Fkotlin-sdk/lists"}