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

# Migration guide

> Upgrade Rhinestone SDK integrations from v2 to v3, or from v1 to v2.

Choose your starting version: [SDK v2 to v3](#sdk-v2-to-v3) or [SDK v1 to v2](#sdk-v1-to-v2). If you use v1, complete both migrations in order.

## SDK v2 to v3

SDK v3 uses API version `2026-09.caucasus`. EVM-only integrations also need the changes below; adding a Solana account is optional. For HTTP request bodies, estimates and wire-level signing, see the [Blanc to Caucasus API migration](/api-reference/api-versioning#migrating-from-blanc).

<Warning>
  If you use intent-bound JWT sponsorship, [upgrade your backend helpers first](#upgrade-sponsorship-before-clients). Older sponsorship filters reject the approval contract sent by v3 clients.
</Warning>

Target `@rhinestone/sdk@3.0.0` on both your client and backend, then run your type checker:

```bash theme={null}
npm install @rhinestone/sdk@3.0.0 viem@^2.55.0
```

### Nest account configuration by VM

Move your existing account configuration under `evm`, including `account`, `owners`, `sessions`, `recovery`, `eoa`, `modules` and `initData`. Keep your existing owner, nonce and account version to preserve the EVM address.

The examples reuse your existing viem `owner`. Shared SDK authentication and provider configuration stay on `RhinestoneSDK`:

```ts theme={null}
import { RhinestoneSDK } from '@rhinestone/sdk'

const rhinestone = new RhinestoneSDK({
  auth: { mode: 'apiKey', apiKey: process.env.RHINESTONE_API_KEY! },
})
```

The top-level `new RhinestoneSDK({ apiKey })` shorthand is removed and throws; use `auth` as shown. Keep API-key mode server-side. Browser integrations continue using [JWT authentication](/wallets/custom-signer/integration/security).

<CodeGroup>
  ```ts v2 theme={null}
  const account = await rhinestone.createAccount({
    owners: { type: 'ecdsa', accounts: [owner] },
  })
  const address = account.getAddress()
  ```

  ```ts v3 theme={null}
  const account = await rhinestone.createAccount({
    evm: {
      owners: { type: 'ecdsa', accounts: [owner] },
    },
  })
  const address = account.getAddress('evm')
  ```
</CodeGroup>

Read configuration through `account.config.evm`, not `account.config.owners`. Let TypeScript infer the configuration: widening it to `RhinestoneAccountConfig` can hide the managed-account methods. An address-only entry such as `{ evm: { address } }` is a receiver, not an account that can sign, fund or execute transactions.

<Warning>
  V3 defaults to Startale `1.0.1`, as do recent v2 releases. If your integration relied on the older `1.0.0` default, pin it to avoid changing the counterfactual address and signing domain. Preserve an existing v1.0.0 account with `evm.account: { type: 'startale', version: '1.0.0' }`. Factory-bearing `initData` preserves its version automatically; address-only `initData` does not. Verify the derived address before funding or deploying.
</Warning>

### Use VM-specific imports

`@rhinestone/sdk/utils` is removed. Its helpers keep their names under `@rhinestone/sdk/evm`. EVM validator constants also move there; `solanaMainnet` moves to `@rhinestone/sdk/solana`.

<CodeGroup>
  ```ts v2 theme={null}
  import { OWNABLE_VALIDATOR_ADDRESS, solanaMainnet } from '@rhinestone/sdk'
  import { walletClientToAccount, wrapParaAccount } from '@rhinestone/sdk/utils'
  ```

  ```ts v3 theme={null}
  import {
    OWNABLE_VALIDATOR_ADDRESS,
    walletClientToAccount,
    wrapParaAccount,
  } from '@rhinestone/sdk/evm'
  import { solanaMainnet } from '@rhinestone/sdk/solana'
  ```
</CodeGroup>

The same move applies to `experimental_getV0InitData`, `experimental_getRhinestoneInitData`, `experimental_getModuleSetup`, `toViewOnlyAccount`, and the WebAuthn, multi-factor and smart-session constants. VM-specific configuration types live in `/evm` or `/solana`; errors stay in `@rhinestone/sdk/errors`.

The EVM initialization and module-setup helpers still take the inner EVM configuration, not the composite `{ evm: ... }` object.

### Name one source and one destination

The SDK no longer discovers funding chains or tokens. Choose one source chain/token and one destination token per transaction. Multiple source chains, multiple source tokens and multiple delivery tokens have no equivalent in one v3 transaction.

```ts theme={null}
import { arbitrum, base } from 'viem/chains'

const arbitrumUsdc = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'
const baseUsdc = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'
```

<CodeGroup>
  ```ts v2 theme={null}
  const prepared = await account.prepareTransaction({
    targetChain: base,
    sourceChains: [arbitrum],
    sourceAssets: [{
      chain: arbitrum,
      address: arbitrumUsdc,
      amount: 5_000_000n,
    }],
    tokenRequests: [{ address: baseUsdc, amount: 1_000_000n }],
  })
  ```

  ```ts v3 theme={null}
  const prepared = await account.prepareTransaction({
    source: {
      chain: arbitrum,
      token: arbitrumUsdc,
      maxAmount: 5_000_000n,
    },
    destination: {
      chain: base,
      token: baseUsdc,
      amount: 1_000_000n,
    },
  })
  ```
</CodeGroup>

Keep using viem chain objects, chain-specific token addresses and `bigint` amounts in token base units. Do not copy the HTTP API's decimal-string amounts or `destination.execution` wrapper into SDK transaction inputs.

| v2 | v3 |
| - | - |
| Same-chain `chain` | `destination.chain`; omit `source.chain` |
| `targetChain` | `destination.chain` |
| `sourceChains: [chain]` | `source.chain` |
| `sourceAssets` token selection | `source.token` on the selected chain |
| `sourceAssets: [{ chain, address, amount }]` | `source: { chain, token, maxAmount }` |
| `tokenRequests: [{ address, amount }]` | `destination.token` and `destination.amount` |
| `recipient`, `calls`, `gasLimit` | `destination.recipient`, `destination.calls`, `destination.gasLimit` |
| `sourceCalls: { [chainId]: calls }` | `source.calls` |
| `auxiliaryFunds: { [chainId]: { [token]: amount } }` | `source.auxiliaryFunds`, in `source.token` |
| `hyperCore` | `destination.hyperCore` |

Amount and funding rules:

* `destination.token` with `amount` requests exact output. With only `token`, receive the maximum output the source yields. Omit both for execution without token delivery.
* `source.maxAmount` caps the source balance available to the route; it does **not** promise to debit exactly that amount. Replace exact-input assumptions accordingly. For EVM sources, auxiliary funds are added after this cap.
* A delivery always needs `source`, even when sponsored. Execution without delivery can omit it only when gas-sponsored. HyperCore, Tron and Stellar destinations still need an explicit EVM `source.chain`.
* `source.calls` run before the claim. Their `provides` entries must name `source.token`; those amounts add to `source.auxiliaryFunds`. Calls formerly omitted by source selection now run or are refused.
* An explicit `destination.recipient` is delivery-only: do not combine it with destination calls. To execute through the invoking managed account, omit `recipient`.
* Put `gasLimit` alongside non-empty `destination.calls`, not on a delivery-only request. Cross-chain `customDeadline` and `hyperCore` options on non-HyperCore destinations are refused rather than ignored.

For same-chain calls, the old `{ chain: base, calls }` becomes:

```ts theme={null}
const prepared = await account.prepareTransaction({
  source: { token: baseUsdc },
  destination: { chain: base, calls },
})
```

For a gas-sponsored, no-delivery execution, use `{ destination: { chain: base, calls }, sponsored: true }`. If a smart session still needs enabling, name a source or enable it first; source-free preparation cannot insert its enable call.

### Update deployment and setup

`deploy` now takes the VM first. Unsponsored intent-path deployment and setup also require a same-chain fee token:

<CodeGroup>
  ```ts v2 theme={null}
  await account.deploy(base)
  await account.setup(base)
  ```

  ```ts v3 theme={null}
  await account.deploy('evm', base, { source: { token: baseUsdc } })
  await account.setup(base, { source: { token: baseUsdc } })
  ```
</CodeGroup>

A sponsored deployment uses `account.deploy('evm', base, { sponsored: true })`. UserOperation execution and funding options remain unchanged; the VM argument still applies to `deploy`.

### Read quote requirements

Keep selecting `prepared.quotes.best`, or pass `{ intentId }` to sign another quote from `prepared.quotes.all`. The quote now includes `purpose`, `plan`, `requirements` and `signingRequests`.

<CodeGroup>
  ```ts v2 theme={null}
  const quote = prepared.quotes.best
  const approval = quote.tokenRequirements?.[base.id]?.[baseUsdc]
  ```

  ```ts v3 theme={null}
  const quote = prepared.quotes.best
  const approval = quote.requirements.find(
    (requirement) => requirement.kind === 'erc20Approval'
      && requirement.chainId === 'eip155:8453'
      && requirement.tokenAddress.toLowerCase() === baseUsdc.toLowerCase(),
  )
  ```
</CodeGroup>

`requirements` is an array of `erc20Approval` and `wrapNative` requirements, replacing the chain/token map. Preparing discloses these prerequisites; it does not approve or wrap funds for you.

Quote costs, plans, requirements, signing context and `bridgeFill.destinationChainId` use CAIP-2 strings such as `'eip155:8453'`, not `8453`. Bridge-fill variants include `fillStatusTimeout`; provider-specific IDs remain separate metadata, not replacements for chain identity. SDK transaction inputs and the submission handle's `sourceChains`/`targetChain` remain numeric/viem-based.

### Migrate custom and detached signing

The ordinary lifecycle stays the same:

```ts theme={null}
const signed = await account.signTransaction(prepared)
const result = await account.submitTransaction(signed)
const status = await account.waitForExecution(result)
```

If you inspect signing messages or build signatures yourself, replace role-keyed payloads and signatures with ordered requests and proofs:

<CodeGroup>
  ```ts v2 theme={null}
  const { origin, destination, targetExecution } =
    account.getTransactionMessages(prepared)
  const signed = await account.signTransaction(prepared)
  const signatures = signed.originSignatures
  ```

  ```ts v3 theme={null}
  const requests = account.getTransactionMessages(prepared)
  const signed = await account.signTransaction(prepared)
  const proofs = signed.proofs
  // proofs[i] answers requests[i].
  ```
</CodeGroup>

Each request describes the account, authority, scope, chains, purpose, validity and payload. Keep one proof per request, in order, even when two payloads are identical. Never sort, filter or deduplicate the proof array.

For custom signers, handle `payload.kind` explicitly:

* `eip712`: honor `signatureFormat`; an account-validator envelope is not a raw secp256k1 signature.
* `personalSign`: sign `message.value` as UTF-8 characters, not decoded hex.
* `eip7702`: return the authorization nonce and `{ r, s, yParity }` signature.
* `webauthn`: return the assertion, not an ECDSA signature.

A smart-session origin produces one `eip712` proof containing `{ preClaim, notarizedClaim }`, not two slots. `signIntent` now takes `SigningRequest[]` instead of `SignData` and returns `{ proofs }`.

Detached EVM owner signing keeps the same method flow:

```ts theme={null}
const contribution = await account.signTransaction(prepared, { owner })
const signed = await account.assembleTransaction(prepared, [contribution])
```

Collect enough owners to satisfy the configured threshold. Pass SDK-returned contributions unchanged; the runtime contribution data uses `slots` instead of `origin`, and covers all required account-authorized requests, including target execution. Do not rebuild it from the old serialized shape. Contributions must belong to the same intent and ordered request set. Independent owner signing is not supported for smart-session or Solana-origin transactions.

#### EIP-7702 authorizations

Normal signing now collects delegation proofs automatically. Remove the separate submission option:

<CodeGroup>
  ```ts v2 theme={null}
  const signed = await account.signTransaction(prepared)
  const authorizations = await account.signAuthorizations(prepared)
  await account.submitTransaction(signed, { authorizations })
  ```

  ```ts v3 theme={null}
  const signed = await account.signTransaction(prepared)
  await account.submitTransaction(signed)
  ```
</CodeGroup>

When a different party signs delegations, `signAuthorizations` returns intent-bound, indexed proof contributions. Pass them to assembly, not submission:

```ts theme={null}
const proofs = await account.signAuthorizations(prepared)
const signed = await account.assembleTransaction(prepared, ownerSignatures, {
  proofs,
})
```

Assembly rejects missing, duplicate or mismatched proof slots rather than returning an incomplete signed transaction.

### Upgrade sponsorship before clients

For intent-bound JWT sponsorship, deploy the updated `@rhinestone/sdk/jwt-server` helpers, or your custom signer, **before** upgrading clients. The v3 approval identifier is `sdk-3.0.0-caucasus`.

The updated `shouldSponsor` helper accepts this input and the Blanc input without a `contractVersion` from v2 clients, so you can upgrade the backend while v2 clients remain active. Other contract identifiers, including those from earlier v3 snapshots, are not supported by the helper.

Custom policies must stop reading the old approval fields:

| v2 approval input | v3 approval input |
| - | - |
| No `contractVersion` | `contractVersion: 'sdk-3.0.0-caucasus'` |
| `destinationChainId` (number) | `destination.chainId` (CAIP-2 string) |
| `account.address` | VM-specific `account.evm.address` or `account.svm.address` |
| `destinationExecutions` | EVM `destination.execution.calls`; HyperCore `destination.execution.settlement.calls` |
| `tokenRequests` | `destination.token` and `destination.amount` |

These are **approval-input fields**, not SDK transaction inputs. Forward the exact `intentInput` supplied to `getIntentExtensionToken`; do not reconstruct it from your UI transaction. See the sponsorship section of the [API migration guide](/api-reference/api-versioning#migrating-from-blanc) for the wire contract and digest rules.

Using the built-in policy adapter avoids hard-coding those approval fields. Its callbacks retain numeric chain IDs and `bigint` call values:

```ts theme={null}
import { shouldSponsor } from '@rhinestone/sdk/jwt-server'

const allowed = await shouldSponsor(prepared.intentInput, {
  chain: ({ id }) => id === base.id,
  account: (address) => address === account.getAddress('evm'),
})
```

Apply these checks on your server before issuing the grant, not as client-side authorization. In `createJwtSigner`, configure `shouldSponsor` filters explicitly; omitting filters does not validate your sponsorship policy. For Solana-only operations, the account filter receives the Swig wallet rather than an EVM address, and a calls filter is not a Solana-instruction policy.

As in current v2 releases, approval runs during preparation, not submission. Denial stops preparation without an unsponsored fallback; submitting a restored prepared transaction does not request another grant.

Swap sponsorship continues to use `sponsored.swaps`, as in current v2 releases. If older integration code still sends `sponsored.swapValue`, remove it; there is no separate client switch for swap-value sponsorship:

```ts theme={null}
const prepared = await account.prepareTransaction({
  source: { chain: arbitrum, token: arbitrumUsdc },
  destination: { chain: base, token: baseUsdc, amount: 1_000_000n },
  sponsored: { gas: true, bridging: true, swaps: true },
})
```

### Handle grouped status and native transaction references

`PENDING`, `COMPLETED` and `FAILED` remain unchanged. Replace the flat operation list with chain groups containing operation items:

<CodeGroup>
  ```ts v2 theme={null}
  for (const operation of status.operations) {
    console.log(
      operation.chain,
      operation.status,
      'txHash' in operation ? operation.txHash : undefined,
    )
  }
  ```

  ```ts v3 theme={null}
  for (const group of status.operations) {
    for (const operation of group.items) {
      if (operation.type === 'EXECUTION') {
        console.log(group.chainId, operation.result.outcome)
      } else {
        console.log(group.chainId, operation.status, operation.transaction)
      }
    }
  }
  ```
</CodeGroup>

Branch on the transaction reference's `vm`: EVM and Stellar use `txHash`, Solana uses `signature`, and Tron uses `txId`. Historical unknown-chain records can retain a numeric chain ID and `{ vm: 'unknown', id }`; do not fabricate a chain mapping.

| v2 status | v3 status |
| - | - |
| `accountAddress` | Optional `accounts` array with recorded per-VM identities |
| One operation per chain | `operations[].items[]`, including claim, fill and execution items |
| Top-level `hyperCore` | An `EXECUTION` item's `result` |
| Refund `{ chain, txHash }` | Refund `{ transaction }` with a native reference |
| No purpose discriminator | `purpose: 'execution'` or `'deployment'` |

Do not infer missing account or Swig-authority evidence. An empty refund list means none were observed, not proof no refund occurred.

HyperCore's six outcomes are unchanged. On failure, inspect `IntentFailedError.context.operations` instead of `.hyperCore`. For `partial`, `error` or `unknown` outcomes, check the Hyperliquid account before retrying to avoid duplicate orders.

Use `rhinestone.getIntentStatus(intentId, { full: true })` for recorded details. Execution details describe the intended execution, not individual call-success results; use operation status and transaction evidence to establish completion.

### Replace removed public types

| Removed v2 type or field | v3 replacement |
| - | - |
| `TokenRequest`, `NonEvmTokenRequest`, `NonEvmTokenRequests` | `TransactionDestination` |
| `AuxiliaryFunds` | `TransactionSource['auxiliaryFunds']` |
| `SignData` | `SigningRequest[]` |
| `OriginSignature` | `SigningProof` |
| `SignedIntentData.originSignatures`, `.destinationSignature`, `.targetExecutionSignature` | `SignedIntentData.proofs` |
| `TokenRequirements`, `ApprovalRequired`, `WrapRequired` | `IntentRequirement` variants |
| `ChainOperation` | `IntentOperationGroup` and `IntentOperationItem` |

### Reconcile persisted transactions

Do not restore v2 prepared or signed transactions into v3. Reconcile any possible submission using its original intent ID before deliberately preparing a replacement; a timeout is not proof the spend failed.

Persist the **complete** new prepared artifact, including its opaque `request` binding, using serialization that preserves `bigint` values. Do not retain only `intentInput`, edit the binding, or combine proofs with a fresh quote. Old-generation artifacts or artifacts missing `request` fail with `InvalidPreparedTransactionError`; the SDK does not convert or automatically re-quote them. Older Solana snapshot artifacts can fail with `InvalidSolanaTransactionArtifactError`.

### Review smart session definitions

v3 removes these inputs. Delete the keys entirely: setting them to `undefined` or an empty value still throws.

| Removed input | v3 replacement |
| - | - |
| `new RhinestoneSDK({ apiKey })` | `new RhinestoneSDK({ auth: { mode: 'apiKey', apiKey } })` |
| `SessionDefinition.claimPolicies` | [`crossChainPermits`](/wallets/session-keys/custom-setup/policies/cross-chain) for standard settlement routes. Arbitrary custom-spender claim policies have no replacement. |
| `SolanaPasskeyPermission` (v3 prerelease versions only) | `SolanaAuthorityPermission` from `@rhinestone/sdk/solana` |

v2 deprecates the shorthand and `claimPolicies` but still accepts them. Nested API-key and JWT auth, and bundler and paymaster credentials, are unchanged.

Smart session behavior changed in v2 2.17.0, and v3 carries it forward. If you are upgrading from 2.17.x, your session semantics already match; remove the inputs above, and pass an explicit `source` on [one-time-use](/wallets/session-keys/custom-setup/policies/one-time-use) intents. If you are upgrading from 2.16.x or earlier:

* Cross-chain permits are scoped by default. Open wildcard access is opt-in through [`fallback`](/wallets/session-keys/custom-setup/policies/cross-chain#open-fallback).
* Omitted settlement layers select every layer that can settle the permit, and explicit lists are strict. `ECO` is retired; use `ECO_IE`.
* A reusable permit's `maxAmount` is a lifetime budget. Re-enabling an enabled session requires [`allowBudgetReset`](/wallets/session-keys/custom-setup/use-a-session#re-enable-a-session-with-a-lifetime-budget).
* Sessions add permit, swap, and one-time-use [windows](/wallets/session-keys/custom-setup/policies/timeframe#session-windows), and a [one-time-use](/wallets/session-keys/custom-setup/policies/one-time-use) option.
* When `signing` is omitted, scoped sessions default to disabled signing; see [Session signing](/wallets/session-keys/custom-setup/session-signing).

Recreate affected definitions with `createSession`, inspect [`validateSession`](/wallets/session-keys/custom-setup/create-a-session#validate-a-definition) output, and persist the result. Sessions you already stored stay usable for enable, sign, and disable: pass the stored `Session` directly and do not rebuild it from a definition. An installed grant keeps its installed policies, so revoke it or authorize a replacement deliberately. See [Store the created session](/wallets/session-keys/custom-setup/create-a-session#store-the-created-session).

These session changes do not change account addresses or factory data.

### What stays the same for EVM-only integrations

* You do not need a Solana account, signer or dependency. Configure only `evm`.
* Existing EVM account identities remain stable with the same configuration. Preserve any older Startale version explicitly, as described above.
* The prepare → sign → submit → wait lifecycle, quote selection and terminal status names stay the same.
* Managed EVM accounts retain reads, message signing, modules, recovery and UserOperation methods. UserOperation inputs remain flat `{ chain, calls, ... }`; do not apply the intent input migration to them.
* Shared authentication/providers, `appFees`, `protocolFees`, settlement-layer/quoter filters, and HyperCore order builders retain their roles. Move only the transaction-specific fields described above.
* Preparation neither signs, spends nor deploys.

If you add managed Solana, configure its owner and explicit Swig state address separately. `getAddress('solana')` returns the asset-holding wallet, not the Swig state account; construction alone does not deploy or verify it. EVM-to-Solana delivery does not require a managed Solana account when you provide the recipient. It does not authorize destination instructions. Solana-to-EVM calls remain restricted to a backend-compatible EVM-derived Swig pairing; unrelated pairs support delivery only.

Before switching traffic, verify derived EVM addresses, explicit funding and amount behavior, custom signing, sponsored preparation, status rendering, and your existing UserOperation path.

## SDK v1 to v2

This section is historical v2 guidance. For v3, also apply the migration above.

This guide targets `@rhinestone/sdk` 2.16.1. Upgrade and run your type checker before changing behavior:

```bash theme={null}
npm install @rhinestone/sdk@2.16.1 viem@^2.55.0
```

The 2.x package is ESM-only. Use `import` syntax and only published entry points such as `@rhinestone/sdk`, `@rhinestone/sdk/utils`, `@rhinestone/sdk/actions/*`, and `@rhinestone/sdk/smart-sessions`.

### Initialize the SDK and account

`createRhinestoneAccount` is removed. Create one SDK instance for shared authentication, RPC, bundler, and paymaster configuration, then create accounts from it.

```ts theme={null}
import { RhinestoneSDK } from '@rhinestone/sdk'

const rhinestone = new RhinestoneSDK({
  auth: {
    mode: 'apiKey',
    apiKey: process.env.RHINESTONE_API_KEY!,
  },
})

const account = await rhinestone.createAccount({
  owners: {
    type: 'ecdsa',
    accounts: [owner],
  },
})
```

Keep API-key mode server-side. If the SDK runs in a browser, migrate to short-lived JWTs as described in [security](/wallets/custom-signer/integration/security).

<Warning>
  The default account is Nexus `1.2.1`. If a 1.x integration derived an unfunded Nexus `1.2.0` address that you must preserve, set `account: { type: 'nexus', version: '1.2.0' }`. Verify the expected address before funding or deploying.
</Warning>

### Replace transaction shortcuts

`account.sendTransaction` is removed for intents. Use the explicit lifecycle:

```ts theme={null}
const prepared = await account.prepareTransaction(transaction)
const signed = await account.signTransaction(prepared)
const result = await account.submitTransaction(signed)
const status = await account.waitForExecution(result)
```

Update the surrounding transaction code as follows:

* Pass token addresses, not symbols such as `USDC` or `WETH`.
* Read the recommended route from `prepared.quotes.best`; all routes are in `prepared.quotes.all`.
* To select another route, call `signTransaction(prepared, { intentId })` with an ID from `quotes.all`.
* Change `settlementLayers` arrays to `{ include: [...] }` or `{ exclude: [...] }`.
* Pass EIP-7702 authorizations as `submitTransaction(signed, { authorizations })`.
* Remove the second `acceptsPreconfirmations` argument from `waitForExecution`.
* Store intent IDs as strings before passing them to `rhinestone.getIntentStatus(intentId)`.

A valid 2.x token request uses the token address for the target chain:

```ts theme={null}
import type { Transaction } from '@rhinestone/sdk'
import { base } from 'viem/chains'

const baseUsdc = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'
const amount = 10_000_000n

const transaction = {
  targetChain: base,
  tokenRequests: [{ address: baseUsdc, amount }],
  settlementLayers: { include: ['ACROSS'] },
} satisfies Transaction
```

### Migrate session keys

The 1.x experimental session names became stable in 2.x. Replace `experimental_sessions` and `type: 'experimental_session'` with `sessions` and `type: 'session'`, then remove the `experimental_` prefix from session account methods.

Do not copy a 1.x resolved `Session.actions` object into 2.x. Recreate the permission with the current ABI-driven configuration and verify its permission ID before enabling it. Continue with [create a session](/wallets/session-keys/custom-setup/create-a-session) for the current session workflow.

### Update account and provider configuration

* Alchemy's `{ type: 'alchemy', apiKey }` provider configuration is removed. Supply complete per-chain URLs with `{ type: 'custom', urls }`; see [JSON-RPC providers](/wallets/custom-signer/configuration/json-rpc-providers).
* `passport` accounts are removed.
* Nexus supports `1.2.0` and `1.2.1`; Kernel supports `3.3`. Remove older pinned versions.
* `submitTransaction` options are now an object, including EIP-7702 authorizations.
* `PortfolioToken.decimals` and aggregate `balances` moved to per-chain entries as `chains[].decimals` and `chains[].amount`.

### Update imports and removed helpers

Use these published 2.x locations:

```ts theme={null}
import {
  experimental_getRhinestoneInitData,
  experimental_getV0InitData,
  walletClientToAccount,
  wrapParaAccount,
} from '@rhinestone/sdk/utils'
import { toSession } from '@rhinestone/sdk/smart-sessions'
```

The bundled token registry and its helpers are removed. Obtain supported chain and token metadata from Rhinestone's chain catalog through the SDK-backed flows, and keep token addresses chain-specific.

The Compact action package, Permit2 batch-signing helpers, `deployAccountsForOwners`, and `checkERC20AllowanceDirect` are also removed. Replace allowance reads with viem `readContract`; use the current account deployment methods and orchestrator-provided signing data for the other flows.

After migration, verify derived account addresses, session permission IDs, token decimals per chain, and both intent and UserOperation paths in a test environment.


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