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

# Create a session with a custom setup

> Install the session-key validator and define its permissions.

<Note>
  This Smart Sessions setup is EVM-only. `createSession`, its validator and
  policies, and session signer selection do not apply to managed Solana
  accounts. Swig's `allButManageAuthority` permits spending and instructions;
  it is not a restricted session with token budgets, call allowlists, or expiry.
  See [Solana support](/wallets/custom-signer/solana-support).
</Note>

## Installing the validation

You can install the validator during account deployment:

Examples on this page use `@rhinestone/sdk`, where `rhinestone` is the `RhinestoneSDK` instance from the [custom signer quickstart](/wallets/custom-signer/quickstart).

```ts {7-9} theme={null}
const rhinestoneAccount = await rhinestone.createAccount({
  evm: {
    owners: {
      type: "ecdsa",
      accounts: [ownerAccount],
    },
    sessions: {
      enabled: true,
    },
  },
});
```

You can also install it when the account is already deployed:

```ts theme={null}
import { enable } from "@rhinestone/sdk/actions/smart-sessions";

const transaction = await rhinestoneAccount.prepareTransaction({
  destination: { chain: base, calls: [enable()] },
  sponsored: true,
});
```

To uninstall the validator:

```ts theme={null}
import { disable } from "@rhinestone/sdk/actions/smart-sessions";

const transaction = await rhinestoneAccount.prepareTransaction({
  destination: { chain: base, calls: [disable()] },
  sponsored: true,
});
```

These no-delivery examples require fee and gas sponsorship. Sign and submit the prepared transaction with the account owner. Without sponsorship, select a funded fee token in `source: { chain: base, token: feeTokenAddress }`.

## Creating sessions

To create a session, use `RhinestoneSDK.createSession`:

```ts theme={null}
const session = await rhinestone.createSession({
  chain: base,
  owners: {
    type: "ecdsa",
    accounts: [sessionOwnerAccount],
  },
});
```

To limit the session to specific calls, list the permissions and set `restrictToActions`:

```ts {7-15} theme={null}
const session = await rhinestone.createSession({
  chain: base,
  owners: {
    type: "ecdsa",
    accounts: [sessionOwnerAccount],
  },
  permissions: [
    {
      abi: erc20Abi,
      address: usdcAddress,
      functions: { transfer: {} },
    },
  ],
  restrictToActions: true,
  saltMode: "strict",
});
```

Permissions alone do not make a restrictive allowlist. Without `restrictToActions`, the session retains the wildcard intent-execution fallback for calls approved by the Orchestrator's intent target allowlist. See [Restrict a session](./restrict-a-session) for the behavior and tradeoffs.

Keep `saltMode: "strict"` when creating a new restricted session. The SDK default, `"none"`, uses a zero salt, so sessions with the same signer reuse a permission ID even when their actions differ. Enabling another definition under that ID adds its policies to the on-chain sets instead of replacing earlier grants. Strict mode gives different restricted definitions distinct permission IDs, but it does not invalidate any permission ID that the signer already controls. Revoke the previous broader session before relying on the signer to have narrower authority.

Permissions govern account calls, while [cross-chain permits](./policies/cross-chain) scope the session to the settlement routes of cross-chain intents. A session holding a permit is already scoped, and you can combine it with `restrictToActions`, `permissions`, and `actions`.

Finally, you can constrain function parameters. The SDK derives the selector and parameter offsets from the ABI, so you reference parameters by name:

```ts {11-21} theme={null}
const session = await rhinestone.createSession({
  chain: base,
  owners: {
    type: "ecdsa",
    accounts: [sessionOwnerAccount],
  },
  permissions: [
    {
      abi: erc20Abi,
      address: usdcAddress,
      functions: {
        transfer: {
          params: {
            recipient: {
              condition: "equal",
              value: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
            },
          },
        },
      },
    },
  ],
  restrictToActions: true,
  saltMode: "strict",
});
```

See the [Call policy](./policies/call) for the full set of parameter conditions, and [Restrict a session](./restrict-a-session) to make the listed permissions the only calls the session can run.

## Session access

Every created session reports whether its key is held to its own actions in `session.access.kind`:

| Definition | Access |
| - | - |
| No `permissions`, `actions`, `crossChainPermits`, or `swap` | `"open"`: wildcard [sudo](./policies/sudo) |
| `permissions` or `actions` without `restrictToActions` | `"open"`: wildcard intent-execution fallback |
| `restrictToActions`, a `swap` scope, or a `crossChainPermits` entry without `fallback` | `"scoped"` |
| A Permit2-layer permit with [`fallback`](./policies/cross-chain#open-fallback) | `"open"` |

Branch on `kind`. `session.access.reason` is for people to read, and its wording may change.

## Validate a definition

`rhinestone.validateSession` runs the same checks as `createSession` without creating, enabling, or signing anything. It lists every refusal instead of throwing the first one:

```ts theme={null}
const { refusals, warnings, access, settlementCoverage } =
  await rhinestone.validateSession({
    chain: base,
    owners: {
      type: "ecdsa",
      accounts: [sessionOwnerAccount],
    },
    account: rhinestoneAccount.getAddress("evm"),
    crossChainPermits: [
      {
        from: { chain: base, token: usdcAddress, maxAmount: parseUnits("100", 6) },
        to: { chain: arbitrum, token: usdcOnArbitrum },
      },
    ],
  });

for (const refusal of refusals) {
  console.error(refusal.code, refusal.message);
}
for (const warning of warnings ?? []) {
  console.warn(warning.code, warning.message);
}
if (refusals.length === 0) {
  console.log(access?.kind, settlementCoverage?.dropped);
}
```

* `refusals` is empty exactly when `createSession` succeeds. The first refusal is the error `createSession` throws.
* `warnings` lists what the session accepts but does not enforce the way the definition may suggest.
* `access` and `settlementCoverage` describe the session `createSession` would return.

Each refusal and warning has a stable `code`, listed in `SESSION_REFUSAL_CODES` and `SESSION_WARNING_CODES` from `@rhinestone/sdk/smart-sessions`. Branch on codes, not messages. Refusals can also carry `permitIndex`, `layer`, `chainId`, and `leg`.

`validateSession` makes the same network reads as `createSession`, and network failures still throw. `@rhinestone/sdk/smart-sessions` also exports an offline `validateSession` and `toSession`. They make no network reads, so routes that depend on the served settlement catalog need it passed as `settlement`. Prefer the online methods.

## Store the created session

`createSession` resolves the definition into a `Session`: its `permissionId`, `salt`, `actions`, `erc7739Policies`, compiled `claimPolicies`, and metadata. Persist that object with serialization that preserves `bigint` values, and pass the restored `Session` to `getSessionDetails`, `signEnableSession`, `enableSession`, `disableSession`, and session signers.

Never send a stored `Session` back through `createSession`, `toSession`, or `validateSession`. Rebuilding a session from its definition under a newer served catalog, clock, or SDK default can produce a different `permissionId` and enable digest. A new identity does not upgrade an installed grant: the old grant keeps its installed policies until you revoke it, so revoke or authorize a replacement deliberately.

`Session.claimPolicies` is compiled signing metadata; keep it. `access` and `settlementCoverage` can be absent on sessions stored by older SDK versions.

## Next steps

<CardGroup cols={2}>
  <Card title="Use a session" href="/wallets/session-keys/custom-setup/use-a-session">
    Enable the session on-chain and transact with it.
  </Card>

  <Card title="Session signing" href="/wallets/session-keys/custom-setup/session-signing">
    Decide what the session key may sign off-chain.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.