Skip to main content

Migrating from 1.x Alpha SDK

To use the latest version of the SDK, install it with the beta tag:
npm i @rhinestone/sdk@beta

New entry point

RhinestoneSDK is now the main entry point to the SDK functionality. To migrate, change the account creation code:
const rhinestoneAccount = await createRhinestoneAccount({
  // Optional
  account: {
    'type': 'nexus'
  },
  owners: {
    type: "ecdsa" as const,
    accounts: [owner],
  },
  // …
  // Optional
  rhinestoneApiKey,
  // Optional
  orchestratorUrl,
});
to this:
const rhinestone = new RhinestoneSDK({
  // Optional
  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:
// 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:
// 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)
  ],
  // …
})
  • /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

Errors

Error classes were moved to a separate subpackage:
// 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:
const result = await rhinestoneAccount.sendTransaction(
  // …
)
and
const data = await rhinestoneAccount.prepareTransaction({
  // …
})
const signedData = await rhinestoneAccount.signTransaction(data)
const result = await rhinestoneAccount.submitTransaction(signedData)
to:
const result = await rhinestoneAccount.sendUserOperation(
  // …
)
and:
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:
npm i @rhinestone/sdk@alpha
Note that the deployerAccount parameter has been removed, as all deployments are now handled via the Orchestrator. Also, sourceChains now accepts a list of chains instead of a single chain.
Due to the module address changes, you’d need to redeploy and refund the accounts.

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.

Installation

Previously:
npm i @rhinestone/module-sdk @rhinestone/orchestrator-sdk permissionless viem
Now:
npm i @rhinestone/sdk viem

Account Creation

Choosing an account implementation

Before, you’d need to construct a smart account client with permissionless:
const sourceSafeAccount = await toSafeSmartAccount({
  version: "1.4.1",
  entryPoint: {
    address: entryPoint07Address,
    version: "0.7",
  },
  // …
});
 
const smartAccountClient = createSmartAccountClient({
  account: sourceSafeAccount,
  chain: sourceChain,
  // …
}).extend(erc7579Actions());
Now:
const account = await createRhinestoneAccount({
  account: {
    type: 'safe'
  }
})
The account object is multi-chain; you don’t need to create separate instances for each chain.
See 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:
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:
const owner = privateKeyToAccount(generatePrivateKey());

const account = await createRhinestoneAccount({
  owners: {
    type: 'ecdsa',
    accounts: [owner],
  },
})
Learn more about using the ECDSA and passkey validators as the account owner. If you are using Smart Sessions, see the relevant guide.

Setting up Omni Account modules

Before, you’d need to provide the module configurations for the Omni Account manually:
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.
Under the hood, the SDK installs a single executor module that handles chain abstraction operations.

Initializing the Orchestrator Client

Before, you’d initialize an Orchestrator API client:
const orchestrator = getOrchestrator(orchestratorApiKey);
Now, you need to pass the API key directly to the account instance:
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:
const opHash = await sourceSmartAccountClient.sendTransaction({
  to: zeroAddress,
  data: "0x11111111",
});

await sourcePublicClient.waitForTransactionReceipt({
  hash: opHash,
});
Now, you can use the deploy method:
await rhinestoneAccount.deploy(chain)

Fetching the Order Path

Before, you define the intent and use getOrderPath to get the path.
const usdcAddress = getTokenAddress("USDC", targetChain.id);
const usdcAmount = 2n;
const recipient = '0xd8da6bf26964af9d7eed9e03e53415d37aa96045';

const metaIntent: MetaIntent = {
  targetChainId: targetChain.id,
  tokenTransfers: [
    {
      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:
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:
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:
const signedTransactionData =
  await rhinestoneAccount.signTransaction(transactionData)

Sending the Intent

Before, you’d use the postSignedOrderBundle to submit the intent to the orchestrator:
const bundleResults: PostOrderBundleResult = await orchestrator.postSignedOrderBundle([
  {
    signedOrderBundle,
  },
]);
Now, you can use the submitTransaction method:
const result = await rhinestoneAccount.submitTransaction(signedTransactionData)

Getting the Intent Status

Before, you’d poll the getBundleStatus method to get bundle status updates:
const bundleStatus = await orchestrator.getBundleStatus(
  bundleResults[0].bundleId,
);
Now, you can use the waitForExecution method:
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 if you need this.