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

# Kotlin SDK sessions and access

> Restore completed sessions, create or import wallets, issue backend ID tokens, and manage direct or remote access.

A completed session combines an authenticated credential with one selected wallet. The Android-backed `OMSWallet` constructor restores that session automatically when its metadata is valid, unexpired, and still matches the Android Keystore credential.

Protected methods on `omsWallet.wallet` require this active session. Read-only calls on `omsWallet.indexer` do not.

## Read the completed session

```kotlin theme={null}
val session = omsWallet.wallet.session

println("Wallet: ${session.walletAddress}")
println("Expires at: ${session.expiresAt}")
println("Authenticated email: ${session.auth?.email}")
```

`walletAddress` is non-null only after wallet activation. `expiresAt` is the ISO-8601 value returned by OMS. `auth` is `OMSWalletEmailSessionAuth` or `OMSWalletOidcSessionAuth`; OIDC metadata also records the `Redirect` or `IdToken` flow and issuer.

Pending email OTP and manual wallet-selection attempts are not persisted. Pending redirect state is stored separately and must be completed through `handleOidcRedirectCallback`. Do not use `session.walletAddress == null` to infer which pending flow, if any, is active.

## Handle expiration

An expired session becomes inactive before protected wallet work proceeds. The operation throws `OMSWalletSessionException` with `code = OMSWalletErrorCode.SessionExpired`.

Subscribe when the UI should react as the session expires:

```kotlin theme={null}
val unsubscribe = omsWallet.wallet.onSessionExpired { event ->
    println("Reauthenticate ${event.session.auth?.email.orEmpty()}")
}

// Call when the lifecycle owner no longer needs the callback.
unsubscribe()
```

Listeners run on the Android main thread. A new listener receives the latest expiration event until a new auth flow, a new completed session, or `signOut()` clears it. The event contains the expired snapshot, not a still-active session.

## List, switch, and create wallets

List every wallet available to the authenticated credential:

```kotlin theme={null}
val wallets = omsWallet.wallet.listWallets()

wallets.forEach { wallet ->
    println("${wallet.id}: ${wallet.address}")
}
```

Activate a different existing wallet by its OMS wallet ID:

```kotlin theme={null}
val selected = omsWallet.wallet.useWallet(walletId = wallets.last().id)
println("Active wallet: ${selected.walletAddress}")
```

Or create and activate a wallet:

```kotlin theme={null}
val created = omsWallet.wallet.createWallet(reference = "savings")
println("Created wallet: ${created.wallet.address}")
```

Both activation methods update the persisted completed session. `reference` is optional app-defined wallet metadata.

Pass `walletType = WalletType.Solana` to create a Solana wallet:

```kotlin theme={null}
val solana = omsWallet.wallet.createWallet(
    walletType = WalletType.Solana,
    reference = "solana-primary",
)
```

## Import and activate a wallet

```kotlin theme={null}
val imported = omsWallet.wallet.importWallet(
    privateKey = WalletImportPrivateKey.Ethereum("0x..."), // Supply the key from your secure migration flow.
    reference = "imported-wallet",
)

println(imported.wallet.keyOrigin == WalletKeyOrigin.Imported)
```

Ethereum imports accept 32 raw bytes or 64 hexadecimal digits with an optional `0x` prefix. Solana imports accept a 32-byte seed or 64-byte keypair as raw bytes, or the base58 encoding of either. The SDK encrypts the key locally for the attested import transport and does not persist the plaintext key. Wallets currently report `WalletKeyOrigin.Enclave` or `WalletKeyOrigin.Imported`.

Attestation failures throw an `OMSWalletException` with code `OMSWalletErrorCode.AttestationVerificationFailed`.

<Warning>
  Development uses Nitro debug-mode attestation. Use only disposable test keys in Development. Staging and Production verify against SDK-pinned enclave measurements.
</Warning>

## Keep the three credential concepts separate

The SDK uses three related values for different trust boundaries:

| Value                             | Purpose                                                                                                      |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| Completed wallet session          | Local SDK state containing the selected wallet, expiry, and auth metadata                                    |
| OMS Wallet ID token               | A token from `getIdToken()` that your app sends to its backend as proof for the active wallet                |
| Request-signing access credential | An SDK-managed P-256 credential that authorizes wallet API requests and appears in access-management results |

The provider ID token passed to `signInWithOidcIdToken` is a fourth value used only to authenticate the user's external identity. It is not the token returned by `getIdToken()`.

## Issue an ID token for your backend

```kotlin theme={null}
val idToken = omsWallet.wallet.getIdToken(
    ttlSeconds = 300u,
)
```

Send this token to your backend and verify it there as described in [Backend wallet verification](/wallets/sdk/guides/backend-wallet-verification). `customClaims` accepts `Map<String, JsonElement>` when you need app-provided context. Treat app-provided claims as untrusted unless your backend controls their values.

## Inspect request-signing access

`listAccess` follows all cursors and returns direct or remote grants for the selected wallet:

```kotlin theme={null}
val grants = omsWallet.wallet.listAccess(pageSize = 25u)

grants.forEach { grant ->
    when (grant) {
        is AccessGrant.Direct ->
            println("${grant.credential.credentialId} caller=${grant.credential.isCaller}")
        is AccessGrant.Remote ->
            println("${grant.sessionId} ${grant.metadata} ${grant.grants}")
    }
}
```

Each grant contains a `WalletCredential` with `credentialId`, ISO-8601 `expiresAt`, and `isCaller`. A remote grant also contains its session ID, display metadata, and bounded smart-session grants. For page-at-a-time UI, use `listAccessPage(pageSize, cursor, type)`. For a stream of pages, collect `listAccessPages(pageSize, type)`.

Revoke only a grant whose credential has `isCaller == false`:

```kotlin theme={null}
grants.firstOrNull { !it.credential.isCaller }?.let { grant ->
    when (grant) {
        is AccessGrant.Direct ->
            omsWallet.wallet.revokeAccess(
                credentialId = grant.credential.credentialId,
            )
        is AccessGrant.Remote ->
            omsWallet.wallet.revokeAccess(
                credentialId = grant.credential.credentialId,
                sessionId = grant.sessionId,
            )
    }
}
```

For a remote grant, `sessionId` is required and revokes exactly that session. Revocation changes server-side wallet access. Revoking the caller invalidates the credential making the current request and prevents subsequent protected operations from that session. It does not switch the selected wallet.

## Authorize remote access

Remote access grants are bounded EVM smart sessions. Inspect the remote credential and show its returned metadata to the wallet owner before requesting approval:

This example uses Polygon Amoy. Grant limits are raw EVM base-unit amounts. A native-transfer limit is cumulative, so `1000000000000000` wei authorizes up to `0.001 POL` across the session.

```kotlin theme={null}
val credentialId = "REMOTE_CREDENTIAL_ID"
val metadata = omsWallet.wallet.inspectRemoteCredential(credentialId)

// Render the returned metadata in your consent UI and continue only after approval.
println("${metadata.appName} ${metadata.appUrl}")

val session = omsWallet.wallet.authorizeRemoteAccess(
    credentialId = credentialId,
    network = Network.AMOY,
    grants = listOf(
        SmartSessionGrant.NativeTransfer(
            to = "0x1111111111111111111111111111111111111111",
            // Cumulative 0.001 POL limit in wei.
            limit = java.math.BigInteger("1000000000000000"),
        ),
    ),
    expiresAt = java.time.Instant.now().plusSeconds(3_600).toString(),
)

val details = omsWallet.wallet.getRemoteAccessSession(session.sessionId)
val usage = omsWallet.wallet.getRemoteAccessSessionUsage(
    session.sessionId,
    Network.AMOY,
)
```

WaaS caps the requested session expiry at the remote credential's expiry. Backend credential registration and remote execution are outside the Kotlin SDK; implement them with the TypeScript SDK's [backend smart sessions guide](/wallets/sdk/guides/backend-smart-sessions).

## Sign out

```kotlin theme={null}
omsWallet.wallet.signOut()
```

`signOut()` synchronously clears in-memory state and attempts to remove completed-session metadata, pending redirect state, and the local request-signing credential. If persistent cleanup fails, it throws `OMSWalletStorageException` after the in-memory session has already been cleared.
