# Using session keys

> Using session keys with your Modular Account V2

> For the complete documentation index, see [llms.txt](/docs/llms.txt).

<Tip title="Most projects should use @alchemy/wallet-apis">
  [`@alchemy/wallet-apis`](/docs/wallets/quickstart) supports session keys through [`wallet_createSession`](/docs/wallets/reference/wallet-apis-session-keys/api) and the [`grantPermissions`](/docs/wallets/reference/wallet-apis-session-keys/sdk) SDK flow. This page covers the lower-level path: using Modular Account V2 session-key validations directly with `@alchemy/smart-accounts` and viem's bundler client. Use it when you need lower-level control for custom onchain validation and permission wiring.
</Tip>

Once a session key is installed, you can use it by creating a second `ModularAccountV2` whose **owner** is the session key signer and whose `accountAddress` points to the original account. Pass the session key's `entityId` and global-validation flag via `signerEntity` — these were set when you called [`encodeInstallValidation`](/docs/wallets/smart-contracts/modular-account-v2/session-keys/adding-session-keys).

```ts twoslash
import { createBundlerClient } from "viem/account-abstraction";
import { createClient, parseEther } from "viem";
import { sepolia } from "viem/chains";
import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
import { alchemyTransport } from "@alchemy/common";
import { toModularAccountV2 } from "@alchemy/smart-accounts";
import { estimateFeesPerGas } from "@alchemy/aa-infra";

const transport = alchemyTransport({ apiKey: "your-api-key" });
const rpcClient = createClient({ chain: sepolia, transport });

// Owner-side account (already created and used to install the session key).
const ownerAccount = await toModularAccountV2({
  client: rpcClient,
  owner: privateKeyToAccount(generatePrivateKey()),
});

// Session-key signer + the validation entity id you installed it under.
const sessionKeySigner = privateKeyToAccount(generatePrivateKey());
const sessionKeyEntityId = 1;

// Reconnect to the SAME account address, but with the session-key signer as owner
// and the session key's signerEntity. If the account isn't deployed yet, also pass
// factoryArgs from `ownerAccount.getFactoryArgs()` so the UO can deploy it.
const sessionKeyAccount = await toModularAccountV2({
  client: rpcClient,
  owner: sessionKeySigner,
  accountAddress: ownerAccount.address,
  signerEntity: {
    entityId: sessionKeyEntityId,
    isGlobalValidation: true,
  },
});

const sessionKeyClient = createBundlerClient({
  account: sessionKeyAccount,
  client: rpcClient,
  chain: sepolia,
  transport,
  userOperation: { estimateFeesPerGas },
});

await sessionKeyClient.sendUserOperation({
  calls: [
    {
      to: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
      data: "0x",
      value: parseEther("1"),
    },
  ],
});
```

You must pass `accountAddress` — without it, `toModularAccountV2` would derive a counterfactual address from the session-key signer instead of pointing at the original account.