> ## 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.

# Manage smart sessions from a backend

> Register a backend remote access credential, reconcile owner-approved sessions, and execute bounded EVM transactions with the TypeScript SDK.

Smart sessions let an application backend submit transactions from a user's wallet without asking the owner to approve every transaction. The wallet owner approves an explicit set of EVM transfer grants and an expiry first. The wallet's Sessions Module enforces those limits on-chain.

The backend API is available through the TypeScript SDK's `RemoteAccessClient`. The owner can approve or revoke the session with any OMS Wallet SDK. In this guide, **client app** means any web or mobile frontend that integrates an OMS Wallet SDK to add wallet functionality.

<Note>
  Smart sessions currently support EVM wallets. Keep the remote access credential (RAC) private key on the backend. Any operator or application client should call your authenticated backend API and must never receive or use that key.
</Note>

## Divide responsibilities

| Component                           | Responsibility                                                                                                                                                                            |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Client app                          | Authenticates the owner, displays the RAC's public metadata and requested grants, and asks the owner to authorize or revoke a session.                                                    |
| Application backend                 | Owns the RAC signer, calls `RemoteAccessClient`, stores any application workflow records, and exposes authenticated endpoints when another client needs to request or inspect operations. |
| Operator or admin client (optional) | Displays backend state and requests actions through the application backend. It does not sign WaaS requests.                                                                              |
| WaaS and the Sessions Module        | Provide the authoritative session, grant, and usage state and enforce the approved grants on-chain.                                                                                       |

If your application uses approval links, request statuses, operator authentication, or local transaction history, those are application features. They are not OMS Wallet SDK APIs.

## Understand the lifecycle

<Steps>
  <Step title="Register the backend credential">
    The backend creates or loads a persistent signer and registers it with WaaS. Registration returns a `credentialId`.
  </Step>

  <Step title="Ask the owner to approve">
    Your application sends the owner the `credentialId` and the requested network, grants, and expiry through its own approval flow.
  </Step>

  <Step title="Authorize in the client app">
    The owner inspects the credential metadata and calls `authorizeRemoteAccess`. The SDK returns the authoritative `walletId` and `sessionId`.
  </Step>

  <Step title="Associate the session with the backend">
    Associate the returned `walletId` and `sessionId` through your application workflow. The client can send them through an authenticated endpoint, or the backend can reconcile its current sessions with `listSessions`. In either case, verify the authoritative session with `getSession` and store an application association only if your workflow needs one.
  </Step>

  <Step title="Reconcile and operate">
    The backend reads the session from WaaS, checks its current grants and usage, and prepares and executes transactions within those limits.
  </Step>
</Steps>

## Create a persistent backend signer

Load the RAC private key from backend-only secret storage and construct one `RemoteAccessClient`. This Node-style example reads environment variables; use your platform's secret bindings in a Worker or other serverless runtime.

```typescript theme={null}
import {
  EthereumPrivateKeyCredentialSigner,
  RemoteAccessClient,
} from '@polygonlabs/oms-wallet'
import { hexToBytes, type Hex } from 'viem'

const publishableKey = process.env.OMS_PUBLISHABLE_KEY
const privateKey = process.env.OMS_RAC_PRIVATE_KEY

if (!publishableKey) {
  throw new Error('OMS_PUBLISHABLE_KEY is required')
}
if (!privateKey || !/^0x[0-9a-fA-F]{64}$/.test(privateKey)) {
  throw new Error('OMS_RAC_PRIVATE_KEY must be a 0x-prefixed 32-byte private key')
}

const credentialSigner = new EthereumPrivateKeyCredentialSigner(
  hexToBytes(privateKey as Hex),
)
const remoteAccess = new RemoteAccessClient({
  publishableKey,
  credentialSigner,
})
```

The signer supplies a strictly increasing nonce for every signed WaaS request. The built-in signer uses a time-based in-memory nonce and is suitable when one long-running process issues requests sequentially. A distributed or restart-sensitive deployment should implement the public `CredentialSigner` interface with a shared atomic nonce store. Requests for one RAC must still reach WaaS in increasing nonce order, so serialize their dispatch even when allocation is atomic.

## Register the credential

Register the signer and persist the returned ID:

```typescript theme={null}
const lifetimeSeconds = 30 * 24 * 60 * 60

const { credentialId } = await remoteAccess.registerCredential({
  lifetimeSeconds,
  metadata: {
    appName: 'Example automation backend',
    appUrl: 'https://example.com',
    appLogoUrl: 'https://example.com/logo.png',
    custom: {},
  },
})
```

When first registering a newly generated signer, store its managed-key reference, `credentialId`, registration time, and requested lifetime. `registerCredential` returns only the credential ID. Registering the same active signer again is idempotent and does not extend its original lifetime, so do not treat a later call as a renewal. A session cannot expire after its RAC; persist the effective `approved.expiresAt` returned to the client app.

The metadata is public consent-screen information returned to the client app by `inspectRemoteCredential`. Do not put secrets in it.

## Collect owner approval

Your application decides how to transport the approval request to the client app. In the client app, inspect the credential before asking the owner to authorize it:

```typescript theme={null}
import { Networks } from '@polygonlabs/oms-wallet'

const metadata = await omsWallet.wallet.inspectRemoteCredential({ credentialId })

// Render this metadata and the exact requested grant in your consent UI.
console.log(metadata.appName, metadata.appUrl)

const approved = await omsWallet.wallet.authorizeRemoteAccess({
  credentialId,
  network: Networks.amoy,
  expiresAt: new Date(Date.now() + 60 * 60 * 1_000).toISOString(),
  grants: [
    {
      kind: 'nativeTransfer',
      to: '0x1111111111111111111111111111111111111111',
      limit: 1_000_000_000_000_000n,
    },
  ],
})

// Pass these values into your application's session-association workflow.
console.log(approved.walletId, approved.sessionId, approved.expiresAt)
```

The public SDK supports `nativeTransfer` and `erc20Transfer` grants. To replace the grants or expiry of a specific existing session without changing its signer, call `authorizeRemoteAccess` again and pass that session's `sessionId`.

The example uses Polygon Amoy. Grant limits and transaction values are raw base-unit amounts.

See the owner-side sessions guide for [TypeScript](/wallets/sdk/typescript/sessions-and-access#authorize-remote-access), [React Native](/wallets/sdk/react-native/sessions-and-access#authorize-remote-access), [Swift](/wallets/sdk/swift/sessions-and-access#authorize-remote-access), or [Kotlin](/wallets/sdk/kotlin/sessions-and-access#authorize-remote-access).

## Persist application state separately

Treat any session identifiers received from a client as untrusted application input. Before accepting an association, call `getSession` with the RAC and verify that the returned session ID, chain, grants, and effective expiry are consistent with the intended authorization.

Keep application workflow state separate from authoritative session state:

| Store in your application                                                             | Read from WaaS                                                             |
| ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| Optional approval-request token, requested grants, requester, and workflow status     | Active session ID, wallet ID, signer address, chain ID, grants, and expiry |
| Association between `credentialId`, `walletId`, `sessionId`, and your user or account | Current grant usage for a selected network                                 |
| Prepared transaction ID and your audit or display history                             | Current transaction status                                                 |

If the client app also sends a wallet address, use it only for display or Indexer queries. Use the `walletId` returned by `getSession` when preparing a remote transaction.

## Reconcile application state

`listSessions` follows all WaaS pages and returns the sessions currently available to this RAC. Read usage for the session's network when showing remaining allowances:

```typescript theme={null}
import { findNetworkById } from '@polygonlabs/oms-wallet'

const sessions = await remoteAccess.listSessions({ pageSize: 50 })

for (const session of sessions) {
  const network = findNetworkById(session.chainId)
  if (!network) {
    continue
  }

  const usage = await remoteAccess.getSessionUsage({
    sessionId: session.sessionId,
    network,
  })

  console.log(session.sessionId, session.grants, usage)
}
```

Keep these RAC-signed reads sequential for a given credential. Your database can retain expired or revoked associations as application history, but do not present them as usable merely because a local record still exists.

## Prepare and execute a transaction

Resolve the session immediately before preparing the transaction. This gives the backend its authoritative wallet ID and current grants:

```typescript theme={null}
import { findNetworkById } from '@polygonlabs/oms-wallet'

const session = await remoteAccess.getSession({ sessionId })
const network = findNetworkById(session.chainId)
if (!network) {
  throw new Error(`Unsupported session chain: ${session.chainId}`)
}

const prepared = await remoteAccess.prepareTransaction({
  walletId: session.walletId,
  sessionId: session.sessionId,
  network,
  to: '0x1111111111111111111111111111111111111111',
  value: 100_000_000_000_000n,
})

// Persist prepared.txnId before asking WaaS to execute it.
await remoteAccess.executeTransaction({
  txnId: prepared.txnId,
})

const latest = await remoteAccess.getTransactionStatus({
  txnId: prepared.txnId,
})
```

Smart-session transactions must be sponsored by the relayer. WaaS rejects preparation when the transaction is not sponsored, so remote execution does not select or submit a fee option.

Validate the requested operation against the current grants and usage before your backend accepts it for execution. This provides clear errors and defense in depth, but the on-chain Sessions Module remains the final authority. Persist `txnId` before execution so an uncertain response can be reconciled with `getTransactionStatus` instead of blindly preparing or executing another transaction.

## Revoke or rotate access

The wallet owner revokes one specific session:

```typescript theme={null}
await omsWallet.wallet.revokeAccess({
  credentialId,
  sessionId,
})
```

The backend can retire the RAC and every session authorized for it:

```typescript theme={null}
await remoteAccess.revokeCredential({ credentialId })
```

Credential rotation requires a new private key, a new `RemoteAccessClient`, and a new registration. Sessions authorized for the retired credential are not transferable to the replacement credential; owners must approve new sessions.

## Production checklist

* Encrypt the RAC private key at rest or keep it in a managed secret or signing service. Never expose it outside the secure backend.
* Authenticate and authorize every caller of backend session-management endpoints. Add rate limits and abuse controls to any public approval endpoints.
* If you use approval links, treat their tokens as bearer credentials. Store only hashes, or encrypt them and strictly limit access.
* Use a shared atomic nonce allocator and serialize WaaS requests when more than one process can use the same RAC.
* Schedule requested session expiries before the planned RAC rotation, persist each effective approval expiry, and rotate credentials before they expire.
* Re-read the authoritative session and usage before presenting or executing an action.
* Persist each prepared `txnId` before execution and reconcile its status before retrying.
* Use the session's authoritative `walletId` for remote transactions. A wallet address alone is not sufficient.

<Card title="Smart session example" href="https://github.com/0xPolygon/oms-wallet-typescript-sdk/tree/master/examples/smart-session">
  See a complete Worker, database, owner approval app, and admin dashboard that implement this lifecycle.
</Card>
