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

## Migrating from 1.x SDK

To use the latest version of the SDK:

```bash theme={null}
npm i @rhinestone/sdk
```

### ESM-only build

The SDK is now ESM-only. `require('@rhinestone/sdk')` no longer works — use ESM `import` syntax. Internal subpath imports were also removed; use the curated entry points (`./actions/*`, `./errors`, `./utils`, `./smart-sessions`, `./jwt-server`).

### `sendTransaction` removed

The `account.sendTransaction(transaction)` shortcut is gone. Use the explicit `prepareTransaction → signTransaction → submitTransaction` flow:

```ts theme={null}
// Before
const result = await rhinestoneAccount.sendTransaction({
  targetChain,
  calls,
  tokenRequests,
})

// After
const prepared = await rhinestoneAccount.prepareTransaction({
  targetChain,
  calls,
  tokenRequests,
})
const signed = await rhinestoneAccount.signTransaction(prepared)
const result = await rhinestoneAccount.submitTransaction(signed)
```

`sendUserOperation` for ERC-4337 flows is unchanged.

### Session permissions are ABI-driven

`Session.actions` is gone. Build sessions with `toSession({ chain, owners, permissions })` instead — an ABI-driven definition the SDK resolves into a low-level `Session`. Each permission is an `{ abi, address, functions }` entry; function selectors and param calldata offsets are derived from the ABI, and param value types are checked against ABI input types:

```ts theme={null}
import { toSession } from '@rhinestone/sdk/smart-sessions'

// Before
const session: Session = {
  chain: base,
  owners: { type: 'ecdsa', accounts: [sessionOwner] },
  actions: [
    {
      target: usdcAddress,
      selector: toFunctionSelector(
        getAbiItem({ abi: erc20Abi, name: 'transfer' }),
      ),
      policies: [
        {
          type: 'universal-action',
          rules: [
            {
              condition: 'equal',
              calldataOffset: 0n,
              referenceValue: recipient,
            },
          ],
        },
      ],
    },
  ],
}

// After
const session = toSession({
  chain: base,
  owners: { type: 'ecdsa', accounts: [sessionOwner] },
  permissions: [
    {
      abi: erc20Abi,
      address: usdcAddress,
      functions: {
        transfer: {
          params: {
            recipient: { condition: 'equal', value: recipient },
          },
        },
      },
    },
  ],
})
```

The hand-written shape is now `SessionDefinition`; `Session` is the resolved output of `toSession`.

### Quote selection

`prepareTransaction` now returns `quotes: { best, all }` instead of a single `quote`. Existing `prepare → sign → submit` code keeps working — `signTransaction` defaults to `quotes.best`:

```ts theme={null}
const prepared = await rhinestoneAccount.prepareTransaction({
  // …
})
const signed = await rhinestoneAccount.signTransaction(prepared)
const result = await rhinestoneAccount.submitTransaction(signed)
```

To sign a non-default route, pass an `intentId` from `prepared.quotes.all`:

```ts theme={null}
const prepared = await rhinestoneAccount.prepareTransaction({
  // …
})
const chosen =
  prepared.quotes.all.find((q) => q.settlementLayer === 'across') ??
  prepared.quotes.best
const signed = await rhinestoneAccount.signTransaction(prepared, {
  intentId: chosen.intentId,
})
```

`getTransactionMessages(prepared, { intentId })` accepts the same selection so external signers see the route `signTransaction` will sign.

### `submitTransaction` options bag

`submitTransaction` now takes an options object instead of positional arguments:

```ts theme={null}
// Before
await rhinestoneAccount.submitTransaction(signed, authorizations)

// After
await rhinestoneAccount.submitTransaction(signed, { authorizations })
```

### `waitForExecution` no longer accepts preconfirmations

The `acceptsPreconfirmations` parameter is removed. `waitForExecution` always waits for `FILLED` / `COMPLETED` and never treats `PRECONFIRMED` as terminal:

```ts theme={null}
// Before
await rhinestoneAccount.waitForExecution(result, false)

// After
await rhinestoneAccount.waitForExecution(result)
```

### Passport account removed

`account.type: 'passport'` is no longer accepted. The `PassportAccount` type and the `passport` member of `AccountType` / `AccountProviderConfig` are removed.

### Intent status by ID

`getIntentStatus` now takes a `string` instead of a `bigint`. If you persist intent IDs across runs, switch the storage type to string.

### Portfolio shape

`PortfolioToken` no longer carries a token-level `decimals` or aggregate `balances`. `decimals` now lives on each per-chain `chains[]` entry alongside `address` and `amount`, since the same logical token can have different decimals across chains (e.g., USDC is 6 on Ethereum, 18 on BSC). Read the per-chain entry directly when rendering balances.

### Permit2 claim policy renames

If you constructed `Permit2ClaimPolicy` values directly, the type tag and field names changed to be chain-aware:

```ts theme={null}
// Before
const policy: Permit2ClaimPolicy = {
  type: 'permit2-claim',
  arbiters: [spender],
  tokensIn: [{ chainId: base.id, token: usdcAddress }],
  tokensOut: [{ chainId: base.id, token: outputToken }],
  recipients: [{ chainId: base.id, recipient: 'any' }],
  recipientIsSponsor: true,
  expiryBounds: { min: 1n, max: 100n },
  fillExpiryBounds: [{ chainId: base.id, min: 1n, max: 100n }],
}

// After
const policy: Permit2ClaimPolicy = {
  type: 'permit2',
  spenders: [spender],
  sourceTokens: [{ chain: base, address: usdcAddress }],
  destinationTokens: [{ chain: base, address: outputToken }],
  recipients: [{ chain: base, address: 'any' }],
  recipientIsAccount: true,
  permitDeadline: { min: 1n, max: 100n },
  fillDeadline: [{ chain: base, min: 1n, max: 100n }],
}
```

### Token registry helpers removed

`getSupportedTokens`, `getTokenAddress`, `getTokenDecimals`, and `getAllSupportedChainsAndTokens` are removed. Fetch the equivalent data from the orchestrator's `/chains` endpoint:

```ts theme={null}
const response = await fetch('https://v1.orchestrator.rhinestone.dev/chains', {
  headers: { 'x-api-key': apiKey },
})
const chains = await response.json()
```

### `deployAccountsForOwners` removed

Create a backend deployer account, take a view-only reference to each user account, and submit a sponsored intent that calls `deploy(userAccount)`. Pass multiple `deploy(...)` calls in one intent to batch deployments.

```ts theme={null}
import { RhinestoneSDK } from '@rhinestone/sdk'
import { deploy } from '@rhinestone/sdk/actions'
import { toViewOnlyAccount } from '@rhinestone/sdk/utils'

const rhinestone = new RhinestoneSDK({ apiKey })

const deployerAccount = await rhinestone.createAccount({
  owners: { type: 'ecdsa', accounts: [deployerSigner] },
})

const userAccount = await rhinestone.createAccount({
  owners: {
    type: 'ecdsa',
    accounts: [toViewOnlyAccount(userAddress)],
  },
})

const prepared = await deployerAccount.prepareTransaction({
  chain,
  calls: [deploy(userAccount)],
  sponsored: true,
})
const signed = await deployerAccount.signTransaction(prepared)
await deployerAccount.submitTransaction(signed)
```

### `checkERC20AllowanceDirect` removed

Read allowances directly with viem's `readContract`:

```ts theme={null}
const allowance = await publicClient.readContract({
  address: tokenAddress,
  abi: erc20Abi,
  functionName: 'allowance',
  args: [owner, spender],
})
```

### Removed and relocated helpers

* **Compact-bound surface.** The `@rhinestone/sdk/actions/compact` subpackage, the `lockFunds` transaction option, and `Account.emissaryConfig` are removed alongside the orchestrator's compact-based deposit/withdrawal flow.
* **Permit2 signing helpers.** `signPermit2Batch`, `signPermit2Sequential`, and the related `MultiChainPermit2Config` / `MultiChainPermit2Result` / `BatchPermit2Result` types are removed. Signing now uses orchestrator-provided EIP-712 typed data internally.
* **`getPermit2Address`** is removed. Permit2 lives at `0x000000000022D473030F116dDEE9F6B43aC78BA3` on every supported chain — hardcode the constant.
* **`walletClientToAccount` and `wrapParaAccount`** moved from the package root to `@rhinestone/sdk/utils`.

## Migrating from 1.x Alpha SDK

### New entry point

`RhinestoneSDK` is now the main entry point to the SDK functionality.

To migrate, change the account creation code:

```ts theme={null}
const rhinestoneAccount = await createRhinestoneAccount({
  // Optional
  account: {
    'type': 'nexus'
  },
  owners: {
    type: "ecdsa" as const,
    accounts: [owner],
  },
  // …
  // Optional
  rhinestoneApiKey,
  // Optional
  orchestratorUrl,
});
```

to this:

```ts theme={null}
const rhinestone = new RhinestoneSDK({
  apiKey: rhinestoneApiKey,
  // Optional
  endpointUrl: orchestratorUrl,
})
const rhinestoneAccount = await rhinestone.createAccount({
  // Optional
  account: {
    'type': 'nexus'
  },
  owners: {
    type: "ecdsa" as const,
    accounts: [owner],
  },
  // …
});
```

### Transaction utilities (actions)

Action utilities related to using modules and resource locking (e.g., `installModule`, `addOwner`, `recoverEcdsaOwnership`) were moved to separate subpackages:

```ts theme={null}
// Before
import { addPasskeyOwner } from '@rhinestone/sdk'
// After
import { addOwner as addPasskeyOwner } from '@rhinestone/sdk/actions/passkeys'
```

Additionally, you don't need to pass `rhinestoneAccount`, `address`, `chain`, and `provider` params anymore when using actions:

```ts theme={null}
// Before
await rhinestoneAccount.sendTransaction({
  calls: [
    ...installModule({
      rhinestoneAccount,
      module,
    })
  ],
  // …
})

// After
await rhinestoneAccount.sendTransaction({
  calls: [
    // Note that you don't need to spread the actions anymore
    installModule(module)
  ],
  // …
})
```

<Accordion title="All actions">
  * `/actions`:
    * `installModule` to install a module
    * `uninstallModule` to uninstall a module
  * `/actions/compact` (resource locking with TheCompact):
    * `depositEther` to deposit ETH into TheCompact
    * `enableEtherWithdrawal` to enable permissionless ETH withdrawal (starts reset period)
    * `disableEtherWithdrawal` to cancel permissionless ETH withdrawal
    * `withdrawEther` to withdraw ETH after the reset period
    * `approveErc20` to approve an ERC-20 token for deposit
    * `depositErc20` to deposit ERC-20 into TheCompact
    * `enableErc20Withdrawal` to enable permissionless ERC-20 withdrawal (starts reset period)
    * `disableErc20Withdrawal` to cancel permissionless ERC-20 withdrawal
    * `withdrawErc20` to withdraw ERC-20 after the reset period
  * `/actions/ecdsa` (ECDSA validator):
    * `enable` to enable the validator
    * `disable` to disable the validator
    * `addOwner` to add an owner
    * `removeOwner` to remove an owner
    * `changeThreshold` to change the signature threshold
  * `/actions/mfa` (multi-factor authorization):
    * `enable` to enable the validator
    * `disable` to disable the validator
    * `setSubValidator` to add a sub-validator to the MFA set
    * `removeSubValidator` to remove a sub-validator from the MFA set
    * `changeThreshold` to change the MFA signature threshold
  * `/actions/passkeys` (passkey validator):
    * `enable` to enable the validator
    * `disable` to disable the validator
    * `addOwner` to add an owner
    * `removeOwner` to remove an owner
    * `changeThreshold` to change the signature threshold
  * `/actions/recovery` (social recovery):
    * `enable` to enable the validator
    * `recoverEcdsaOwnership` to recover ownership to a new ECDSA owner
    * `recoverPasskeyOwnership` to recover ownership to a new passkey owner
</Accordion>

### Errors

Error classes were moved to a separate subpackage:

```ts theme={null}
// Before
import { isAccountError, AccountError, SigningNotSupportedForAccountError } from '@rhinestone/sdk'
// After
import { isAccountError, AccountError, SigningNotSupportedForAccountError } from '@rhinestone/sdk/errors'
```

### Using ERC-4337 flow

All transactions executed with `sendTransaction` and `prepareTransaction` now use Rhinestone intents.

Using the ERC-4337 user operations (for example, when using a social recovery) now requires a separate flow.

This change lets us improve type-safety and DX around using intents.

To keep using user operations for specific flows, change your code from:

```ts theme={null}
const result = await rhinestoneAccount.sendTransaction(
  // …
)
```

and

```ts theme={null}
const data = await rhinestoneAccount.prepareTransaction({
  // …
})
const signedData = await rhinestoneAccount.signTransaction(data)
const result = await rhinestoneAccount.submitTransaction(signedData)
```

to:

```ts theme={null}
const result = await rhinestoneAccount.sendUserOperation(
  // …
)
```

and:

```ts theme={null}
const data = await rhinestoneAccount.prepareUserOperation({
  // …
})
const signedData = await rhinestoneAccount.signUserOperation(data)
const result = await rhinestoneAccount.submitUserOperation(signedData)
```

## Migrating from 0.x SDK

To use the latest version of the SDK, install it with the `alpha` tag:

```bash theme={null}
npm i @rhinestone/sdk@alpha
```

Note that the `deployerAccount` parameter has been removed, as all deployments are now handled via [the Orchestrator](../../home/introduction/rhinestone-intents#the-orchestrator).

Also, `sourceChains` now accepts a list of chains instead of a single chain.

<Info>Due to the module address changes, you'd need to redeploy and refund the accounts.</Info>

## Migrating from Orchestrator SDK

This guide provides a detailed breakdown of the changes between the Orchestrator SDK and the new SDK. If you're looking for a fresh start, see our [Quickstart](../quickstart).

### Installation

Previously:

```bash theme={null}
npm i @rhinestone/module-sdk @rhinestone/orchestrator-sdk permissionless viem
```

Now:

```bash theme={null}
npm i @rhinestone/sdk viem
```

### Account Creation

#### Choosing an account implementation

Before, you'd need to construct a smart account client with `permissionless`:

```ts theme={null}
const sourceSafeAccount = await toSafeSmartAccount({
  version: "1.4.1",
  entryPoint: {
    address: entryPoint07Address,
    version: "0.7",
  },
  // …
});
 
const smartAccountClient = createSmartAccountClient({
  account: sourceSafeAccount,
  chain: sourceChain,
  // …
}).extend(erc7579Actions());
```

Now:

```ts theme={null}
const account = await createRhinestoneAccount({
  account: {
    type: 'safe'
  }
})
```

<Info>The `account` object is multi-chain; you don't need to create separate instances for each chain.</Info>

See [Smart Account Providers](../customize/smart-account-providers) for details on choosing the account implementation.

#### Choosing a validator

Before, you'd specify the validator config in your smart account setup:

```ts theme={null}
const owner = privateKeyToAccount(generatePrivateKey());

const ownableValidator = getOwnableValidator({
  owners: [owner.address],
  threshold: 1,
});

const sourceSafeAccount = await toSafeSmartAccount({
  validators: [
    {
      address: ownableValidator.address,
      context: ownableValidator.initData,
    },
  ],
  // …
});

const sourceSmartAccountClient = createSmartAccountClient({
  account: sourceSafeAccount,
  chain: sourceChain,
  bundlerTransport: http(
    `https://api.pimlico.io/v2/${sourceChain.id}/rpc?apikey=${pimlicoApiKey}`,
  ),
  paymaster: sourcePimlicoClient,
  userOperation: {
    estimateFeesPerGas: async () => {
      return (await sourcePimlicoClient.getUserOperationGasPrice()).fast;
    },
  },
}).extend(erc7579Actions());
```

Now, you can use `owners` when creating the account:

```ts theme={null}
const owner = privateKeyToAccount(generatePrivateKey());

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

Learn more about using the [ECDSA](../core/ecdsa-signer) and [passkey](../core/passkeys) validators as the account owner.

If you are using Smart Sessions, see the relevant [guide](../smart-sessions/overview).

#### Setting up Omni Account modules

Before, you'd need to provide the module configurations for the Omni Account manually:

```ts theme={null}
const sourceSafeAccount = await toSafeSmartAccount({
  // …
  executors: [
    {
      address: getSameChainModuleAddress(targetChain.id),
      context: "0x",
    },
    {
      address: getTargetModuleAddress(targetChain.id),
      context: "0x",
    },
    {
      address: getHookAddress(targetChain.id),
      context: "0x",
    },
  ],
  hooks: [
    {
      address: getHookAddress(targetChain.id),
      context: encodeAbiParameters(
        [
          { name: "hookType", type: "uint256" },
          { name: "hookId", type: "bytes4" },
          { name: "data", type: "bytes" },
        ],
        [
          0n,
          "0x00000000",
          encodeAbiParameters([{ name: "value", type: "bool" }], [true]),
        ],
      ),
    },
  ],
  fallbacks: [
    {
      address: getTargetModuleAddress(targetChain.id),
      context: encodeAbiParameters(
        [
          { name: "selector", type: "bytes4" },
          { name: "flags", type: "bytes1" },
          { name: "data", type: "bytes" },
        ],
        ["0x3a5be8cb", "0x00", "0x"],
      ),
    },
  ],
});
```

Now, that is handled automatically for you.

<Info>Under the hood, the SDK installs a single executor module that handles chain abstraction operations.</Info>

### Initializing the Orchestrator Client

Before, you'd initialize an Orchestrator API client:

```ts theme={null}
const orchestrator = getOrchestrator(orchestratorApiKey);
```

Now, you need to pass the API key directly to the account instance:

```ts theme={null}
const account = privateKeyToAccount(generatePrivateKey());

const account = await createRhinestoneAccount({
  rhinestoneApiKey: orchestratorApiKey,
  // …
})
```

### Funding

As before, you can send the tokens or ETH directly to the account to fund it.

### Deploying

Before, you'd deploy the smart account using an ERC-4337 bundler:

```ts theme={null}
const opHash = await sourceSmartAccountClient.sendTransaction({
  to: zeroAddress,
  data: "0x11111111",
});

await sourcePublicClient.waitForTransactionReceipt({
  hash: opHash,
});
```

Now, you can use the `deploy` method:

```ts theme={null}
await rhinestoneAccount.deploy(chain)
```

### Fetching the Order Path

Before, you define the intent and use `getOrderPath` to get the path.

```ts theme={null}
const usdcAddress = getTokenAddress("USDC", targetChain.id);
const usdcAmount = 2n;
const recipient = '0xd8da6bf26964af9d7eed9e03e53415d37aa96045';

const metaIntent: MetaIntent = {
  targetChainId: targetChain.id,
  tokenRequests: [
    {
      tokenAddress: usdcAddress,
      amount: usdcAmount,
    },
  ],
  targetAccount: targetSafeAccount.address,
  targetExecutions: [
    {
      to: usdcAddress,
      value: 0n,
      data: encodeFunctionData({
        abi: erc20Abi,
        functionName: "transfer",
        args: [recipient, usdcAmount],
      }),
    },
  ],
};

const orderPath = await orchestrator.getOrderPath(
  metaIntent,
  targetAccount.address,
);
```

Now, you can call `prepareTransaction`:

```ts theme={null}
const usdcAmount = 2n;
const recipient = '0xd8da6bf26964af9d7eed9e03e53415d37aa96045';

const transactionData = await rhinestoneAccount.prepareTransaction({
  targetChain,
  calls: [
    {
      to: 'USDC',
      data: encodeFunctionData({
        abi: erc20Abi,
        functionName: 'transfer',
        args: [recipient, usdcAmount],
      }),
    },
  ],
  tokenRequests: [
    {
      address: 'USDC',
      amount: usdcAmount,
    },
  ],
})
```

### Signing the Intent

Before, you'd craft the packed signature and pass that to the bundle structure:

```ts theme={null}
const orderBundleHash = getOrderBundleHash(orderPath[0].orderBundle);
 
const bundleSignature = await owner.signMessage({
  message: { raw: orderBundleHash },
});
const packedSig = encodePacked(
  ["address", "bytes"],
  [ownableValidator.address, bundleSignature],
);
 
const signedOrderBundle: SignedMultiChainCompact = {
  ...orderPath[0].orderBundle,
  originSignatures: Array(orderPath[0].orderBundle.segments.length).fill(
    packedSig,
  ),
  targetSignature: packedSig,
};
```

Now, you can use the `signTransaction` method:

```ts theme={null}
const signedTransactionData =
  await rhinestoneAccount.signTransaction(transactionData)
```

### Sending the Intent

Before, you'd use the `postSignedOrderBundle` to submit the intent to the orchestrator:

```ts theme={null}
const bundleResults: PostOrderBundleResult = await orchestrator.postSignedOrderBundle([
  {
    signedOrderBundle,
  },
]);
```

Now, you can use the `submitTransaction` method:

```ts theme={null}
const result = await rhinestoneAccount.submitTransaction(signedTransactionData)
```

### Getting the Intent Status

Before, you'd poll the `getBundleStatus` method to get bundle status updates:

```ts theme={null}
const bundleStatus = await orchestrator.getBundleStatus(
  bundleResults[0].bundleId,
);
```

Now, you can use the `waitForExecution` method:

```ts theme={null}
const status = await rhinestoneAccount.waitForExecution(result)
```

### Using with Existing Accounts

For now, using the SDK with existing accounts is not possible. Users would need to create a new smart account.

We're working on making it possible to use the SDK with existing (deployed) smart accounts.

[Reach out](http://t.me/kurt_larsen) if you need this.
