Skip to main content
A swap scope narrows a session to one swap: it may spend one token, receive one token, deliver to one recipient, and route only through the venues you name. Every other operation reverts on-chain. This is what makes an automated conversion session — a backend that turns deposits into a stablecoin, say — grantable without handing the session key authority over the account. These examples target SDK 3.0.0 and EVM sessions.

Declaring a swap scope

Examples on this page use @rhinestone/sdk, where rhinestone is the RhinestoneSDK instance from Create a session with a custom setup.
A swap scope is single-chain: it applies on the session’s chain.

The implied restriction

Declaring swap implies restrictToActions. The session’s only operations are to approve the sell token to a listed venue’s spender, and to call that venue’s swap entrypoint with the sell token, buy token, and recipient pinned. Two consequences:
  • The ERC-1271 signing surface defaults to { mode: 'disabled' }, so a swap-scoped key cannot sign an off-chain approval. Pass signing explicitly to opt back in.
  • A swap scope can sit beside crossChainPermits on other tokens; same-chain intents are then signed against the swap scope. It cannot be combined with fallback, and stableFloor refuses permits.

Naming venues

Venues are named, not addressed — router addresses, selectors, and calldata offsets stay inside the SDK, so you never encode a swap yourself and the SDK can follow a router deployment without a breaking change. The builders are published exports:
Each builder also accepts an optional maxSpend — a venue-level cumulative cap that overrides sell.maxTotal for that venue. Naming an aggregator authorizes every call shape its swaps can arrive in: both a direct router call by the account and the Swapper-wrapped one. That cannot be narrowed, because which shape the orchestrator emits depends on the swap’s direction and the winning quoter — both decided after the session is signed. Authorizing a single shape is how a session ends up rejecting the swap it was created for. Venue availability is per chain, and the SDK rejects a venue that isn’t available on the session’s chain.

Pinning 0x’s Settler

0x redeploys its Settler every few weeks and moves the registry token, so the SDK bundles no default address. Resolve it and pin the result:
Resolve once, at session-enable time — do not re-resolve per swap. A session that looked the address up at use time would follow 0x’s registry wherever it points, so a compromise of 0x’s upgrade key could redirect every live session’s approved funds. Pinned, the worst case of a rotation is that the session stops working until it is re-enabled.
zeroEx({ anySettler: true, maxSpend }) trades the pin for longevity, and it is an explicit security downgrade. With the target unpinned, a compromised session key can redirect the AllowanceHolder pull to its own contract, and maxSpend becomes the only bound on what it takes — which is why the SDK makes it mandatory in that form.

Swap window

Set validAfter and validUntil on the scope to limit when the session can swap:
The window applies to the scope’s approve and swap calls only, not to other actions in the session. An allowance granted inside the window stays. A windowed scope gets its own salted permission ID. See Timeframe for the shared window rules.

Stable-to-stable rate floor

The Rhinestone Swapper takes its output bound from the session signer, which can set it to zero. For a swap between two USD stablecoins, stableFloor adds an absolute floor:
Every Swapper call must then deliver at least maxTotal less the slippage, while selling at most maxTotal. true means 100 bps.
  • It requires one sell token, sell.maxTotal, both tokens among the USD stablecoins the Orchestrator serves for the chain, and the Rhinestone Swapper as the only venue. Create the session with rhinestone.createSession.
  • It refuses signing, crossChainPermits, and any other action on the sell token, Permit2, or the Swapper.
  • The floor is absolute, not proportional, so a swap much smaller than maxTotal cannot meet it. In practice the session is single-use.
  • An allowance to the Swapper proxy that existed before the session is not counted against maxTotal.

What the scope binds

Using the session

A same-chain swap intent, signed by the session key. Fund the account with the selected sell token, USDT0, and enable the session before using it:
Sponsor the intent. An unsponsored intent adds the orchestrator’s fee and gas-refund operations, which are authorized by the wildcard intent-execution fallback that a swap scope drops. Without sponsored: true the intent is rejected on-chain, and the error surfaces as a signature failure rather than an authorization one.
You don’t restate the venue at transaction time. When the session names specific aggregators, the SDK derives the matching quoters pin from swap.via and sends it with the quote request; across a per-chain session set the pin is the intersection of every session’s venues. rhinestoneSwap() pins nothing, since any quoter may legitimately fill it.
Venue pinning constrains the route rather than selecting it. If no permitted venue can serve the swap, the request fails instead of falling back to a venue the session does not allow — an availability consideration, not a security one.

Pinning venues without a session

quoters is also available on any transaction, as an include or exclude filter over the orchestrator’s swap venues:
An explicit filter can only narrow a session-derived pin, never widen it. And an empty filter is not the same as omitting the field: { include: [] } means no venue may serve the swap, so the request fails at quote time rather than falling back to an unconstrained route.