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

# Cross-chain permits

Cross-chain permits define the cross-chain bridging scope available to a session.

Set them at the session level via `crossChainPermits`, not per function. Each entry allows source and destination legs and defines the limits the session has to stay within.

<Note>
  A permit restricts bridging authority; it does not enable the session on
  another chain. For a transaction that uses the account on several chains, also
  configure [Multi-chain
  sessions](/wallets/session-keys/custom-setup/multi-chain-sessions).
</Note>

## Basic usage

Restrict a session to only bridge USDC from Base to Arbitrum:

Examples on this page use `@rhinestone/sdk`, where `rhinestone` is the `RhinestoneSDK` instance from [Create a session with a custom setup](/wallets/session-keys/custom-setup/create-a-session).

```ts {13-18} theme={null}
import { parseUnits } from "viem";
import { arbitrum, base } from "viem/chains";

const usdcOnBase = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
const usdcOnArbitrum = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831";

const session = await rhinestone.createSession({
  chain: base,
  owners: {
    type: "ecdsa",
    accounts: [sessionOwnerAccount],
  },
  crossChainPermits: [
    {
      from: [{ chain: base, token: usdcOnBase }],
      to: [{ chain: arbitrum, token: usdcOnArbitrum }],
    },
  ],
});
```

Pass each token's address on its corresponding chain. Pass a single leg or an array, and omit a side entirely to leave it unrestricted — the settlement-layer allowlist, deadlines, and recipient rules still apply.

| Field  | Description                                                                                        |
| ------ | -------------------------------------------------------------------------------------------------- |
| `from` | Source chain + token (+ optional `maxAmount` cap). Omit for no source-token restriction.           |
| `to`   | Destination chain + token (+ optional `recipient` pin). Omit for no destination-token restriction. |

## Guardrails

Tighten a permit with optional bounds:

```ts {11-17} theme={null}
const usdcOnBase = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
const usdcOnArbitrum = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831";

const session = await rhinestone.createSession({
  chain: base,
  owners: {
    type: "ecdsa",
    accounts: [sessionOwnerAccount],
  },
  crossChainPermits: [
    {
      from: [
        { chain: base, token: usdcOnBase, maxAmount: parseUnits("100", 6) },
      ],
      to: [{ chain: arbitrum, token: usdcOnArbitrum }],
      validAfter: new Date("2026-01-01"),
      validUntil: new Date("2027-01-01"),
      fillDeadline: [{ chain: arbitrum, max: new Date("2027-01-01") }],
    },
  ],
});
```

* `maxAmount` on a source leg caps how much of that token the session can pull (a spending limit).
* `validAfter` / `validUntil` bound the permit deadline. Both accept a `Date`.
* `fillDeadline` bounds the fill window per destination chain.

## Recipient safety

By default a cross-chain permit enforces **bridge-to-self** on-chain: the destination recipient must be the smart account itself. This stops a leaked session key from routing funds to an attacker-controlled address. Opt out explicitly only when you need to bridge to a different recipient:

```ts {13-14} theme={null}
const usdcOnBase = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
const usdcOnArbitrum = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831";

const session = await rhinestone.createSession({
  chain: base,
  owners: {
    type: "ecdsa",
    accounts: [sessionOwnerAccount],
  },
  crossChainPermits: [
    {
      from: [{ chain: base, token: usdcOnBase }],
      to: [
        { chain: arbitrum, token: usdcOnArbitrum, recipient: payoutAddress },
      ],
      allowRecipientNotAccount: true,
    },
  ],
});
```

## Settlement layers

A permit allows any supported settlement layer by default. Pass `settlementLayers` to narrow it to a subset:

```ts {14} theme={null}
const usdcOnBase = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
const usdcOnArbitrum = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831";

const session = await rhinestone.createSession({
  chain: base,
  owners: {
    type: "ecdsa",
    accounts: [sessionOwnerAccount],
  },
  crossChainPermits: [
    {
      from: [{ chain: base, token: usdcOnBase }],
      to: [{ chain: arbitrum, token: usdcOnArbitrum }],
      settlementLayers: ["ECO"],
    },
  ],
});
```

<Warning>
  In `@rhinestone/sdk` 2.16.1, `"ECO"` authorizes only the legacy Standard ECO
  arbiters. Eco solver-network routes use a different adapter and are
  intentionally blocked because the built-in claim policy cannot yet verify
  their encoded destination, token, recipient, and deadline. A quote that
  requires a solver-network ECO route cannot use this session; use another
  supported settlement layer or the account owner until route-aware
  authorization is available.
</Warning>
