Skip to main content
Cross-chain permits define the cross-chain bridging scope available to a session. Set them at the session level via crossChainPermits, not per function. A session holding a permit is scoped: the session key can run only the calls its settlement routes need, funded only from the permit’s from tokens. It has no wildcard fallback unless you opt in with fallback.
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.

Basic usage

Restrict a session to bridging at most 100 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.
Pass each token’s address on its own chain. Without fallback, a Permit2-layer permit needs a from token on the session’s chain. Without account, a permit that delivers to the account and can settle through CCTP is refused with RECIPIENT_NEEDS_ACCOUNT. Create permit sessions with rhinestone.createSession. It reads the settlement addresses the Orchestrator serves, which the IntentExecutor layers pin. Run rhinestone.validateSession first to list every refusal and warning.

Settlement layers

A permit settles through one or more settlement layers: IntentExecutor layers pin their calls, including the recipient, in calldata. A session can hold one IntentExecutor-layer permit.
ECO, the retired Permit2 Eco arbiter, is refused with RETIRED_PERMIT2_LAYER. Use ECO_IE instead.

Default coverage

Omit settlementLayers, or pass [] or "all", to select ACROSS plus every IntentExecutor layer among CCTP, OFT, ECO_IE and LZ that can settle the permit on the session’s chain. The SDK leaves out the rest and records them in session.settlementCoverage.dropped with the reason. session.settlementLayers lists the layers it kept. For the basic example above, OFT and LZ are dropped because they require oneTimeUse, and ECO_IE because the permit sets no floor. Treat this as example output: coverage resolves against the served catalog and the clock when you create the session, so inspect settlementCoverage rather than assuming a route is available, and store the created session instead of rebuilding it from the definition. SAME_CHAIN_IE is never selected by default. If no layer qualifies, the session is refused with NO_LAYER_CAN_SETTLE.

Explicit layers

Name layers to make the list strict. Every named layer must be able to settle the permit, or the session is refused with that layer’s reason. The order does not matter:
Combination rules:
  • SAME_CHAIN_IE must be the only layer.
  • OFT and LZ cannot be combined, and each requires oneTimeUse (LAYER_REQUIRES_ONE_TIME_USE).
  • IntentExecutor layers need named to chains.
  • ACROSS is the only Permit2 layer you can name without fallback.
An intent’s own settlementLayers filter can only narrow the session’s layers.

Spending budgets

maxAmount on a from leg caps what the session can move from that token on its chain, across every route the permit settles through:
  • With oneTimeUse, it caps one settlement.
  • Without it, it is a lifetime total shared by every intent the session signs, not a per-intent cap.
maxAmount caps the approvals the session makes. It does not count allowances the account already holds, such as one the owner gave Permit2 or a bridge, or one another session left. Those remain spendable by the session’s routes, so revoke them first if maxAmount must be the most the session can move. allowFees calls have separate caps. Set maxAmount on each from leg on the session’s chain. When ACROSS sits beside IntentExecutor layers and a leg has no maxAmount, nothing caps what the ACROSS route moves for it, and validateSession warns with UNCAPPED_ACROSS_ROUTE. Re-enabling a session that is already enabled resets its lifetime budget, so getSessionDetails refuses it by default. See Re-enable a session with a lifetime budget. A reusable (no oneTimeUse) scoped ACROSS session needs the Permit2SenderPolicy on the session’s chain. On chains without it, such as Avalanche, the session is refused with PERMIT2_ROUTE_NEEDS_BOUND. Use oneTimeUse there.

Time windows

Bound the permit with validAfter and validUntil, and bound the fill per destination chain with fillDeadline:
  • validAfter / validUntil apply to every action the permit adds. On ACROSS they also bound the Permit2 deadline. validUntil must be a future Date.
  • fillDeadline applies to ACROSS only.
  • ECO_IE needs any validUntil at least 7 days ahead, and is usable only until 7 days before it.
See Timeframe for the window semantics shared by every session window.

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:
ACROSS enforces the recipient in its claim policy; IntentExecutor layers pin it in calldata.

Fees and delivery floors

By default a scoped permit session settles only sponsored intents without an app fee or a user-paid protocol fee taken from the from token. On IntentExecutor layers it also refuses unsponsored gas. These opt-ins widen or tighten that: An intent whose fee or gas ceiling exceeds the remaining allowFees cap fails closed. OFT and LZ quote with about 1% slippage, so set to.minAmount to at most about 99% of the expected send. Every layer in the permit must enforce to.minAmount. CCTP cannot, so it is refused when named and dropped by default coverage. ACROSS has no output floor. Fee and floor coverage applies only to the paths each policy enforces; it is not a universal spending bound.

Open fallback

Set fallback to give a permit session a wildcard action beside its scoped calls:
  • "intentExecution" also allows the calls Rhinestone’s intent-execution policy admits. Rhinestone sets that policy, and it may admit more over time.
  • "sudo" allows any call, with any arguments and native value.
Either value makes session.access.kind "open", and lets intents take routes other than ACROSS and fund them from any asset. With fallback:
  • Omitted layers mean ACROSS, and "all" is refused.
  • Only Permit2-layer permits (ACROSS, SAME_CHAIN) qualify. IntentExecutor layers, restrictToActions, and swap are refused with FALLBACK_NOT_APPLICABLE.
  • The recipient pin holds only for intents settled through ACROSS (warning FALLBACK_RECIPIENT_PIN_ACROSS_ONLY).
  • maxAmount requires oneTimeUse.
Do not add fallback just to make an incompatible scope compile. Fix the scope instead, or declare the extra actions you need.

Scoped session behavior

A scoped permit session settles cross-chain intents only, or with a swap scope, same-chain intents funded from its sell tokens. A destination call or leftover sweep on the destination chain needs the destination chain’s session to declare those actions, or to set fallback. You can combine a permit with restrictToActions, permissions, and actions to let the same key run other explicit calls.