Skip to main content

Overview

Turnkey provides secure key management infrastructure that enables you to create and manage signing keys for your users. This guide shows you how to integrate Turnkey signers with Rhinestone smart accounts for secure, non-custodial wallet experiences. How it works: Unlike Privy and Dynamic, Turnkey operates at a lower level. You create a viem-compatible account using @turnkey/viem, then pass that account to Rhinestone. Turnkey handles the secure key management while Rhinestone adds cross-chain capabilities.

Prerequisites

  • A Turnkey account and organization
  • Turnkey API credentials (API public/private key pair)
  • Your organization ID from Turnkey
1

Install Dependencies

Install the required dependencies:
npm install @turnkey/http @turnkey/api-key-stamper @turnkey/viem @rhinestone/sdk viem
2

Create a Signer Wallet

Using the Turnkey dashboard or API, create a new Ethereum (EVM) wallet. This wallet will be the owner of the Rhinestone smart account.
  1. Log into your Turnkey dashboard
  2. Navigate to the Wallets section
  3. Create a new Ethereum wallet
  4. Note the wallet address for the next step
3

Create a Turnkey Signer

Use @turnkey/viem to create a viem-compatible account backed by Turnkey’s signing infrastructure:
The createAccount function from @turnkey/viem returns a full viem LocalAccount with signMessage, signTransaction, and signTypedData already implemented.
import { TurnkeyClient } from "@turnkey/http"
import { ApiKeyStamper } from "@turnkey/api-key-stamper"
import { createAccount } from "@turnkey/viem"

const turnkeyClient = new TurnkeyClient(
  { baseUrl: "https://api.turnkey.com" },
  new ApiKeyStamper({
    apiPublicKey: process.env.API_PUBLIC_KEY,
    apiPrivateKey: process.env.API_PRIVATE_KEY,
  })
)

const turnkeySigner = await createAccount({
  client: turnkeyClient,
  organizationId: process.env.ORGANIZATION_ID,
  signWith: "0x...", // Your Turnkey wallet address
})
4

Initialize Rhinestone Account

Create a new Rhinestone account using the Turnkey signer. This is the same pattern as with other providers - pass the signer to Rhinestone, and it wraps it with cross-chain functionality:
import { RhinestoneSDK } from "@rhinestone/sdk"

const rhinestone = new RhinestoneSDK({
  apiKey: process.env.RHINESTONE_API_KEY,
})
const rhinestoneAccount = await rhinestone.createAccount({
  owners: {
    type: "ecdsa",
    accounts: [turnkeySigner],
  },
})

Usage

Send a Cross-chain Transaction

The Rhinestone account will automatically use the Turnkey signer for all transactions:
import { encodeFunctionData, parseUnits, erc20Abi } from 'viem'
import { baseSepolia, arbitrumSepolia } from 'viem/chains'

const transaction = await rhinestoneAccount.sendTransaction({
  sourceChains: [baseSepolia],
  targetChain: arbitrumSepolia,
  calls: [
    {
      to: "USDC",
      data: encodeFunctionData({
        abi: erc20Abi,
        functionName: "transfer",
        args: ["0xrecipient", parseUnits("10", 6)],
      }),
    },
  ],
  tokenRequests: [
    {
      address: "USDC",
      amount: parseUnits("10", 6),
    },
  ],
})
Don’t forget to fund the account before making any transactions.

Environment Variables

Make sure to set the following environment variables:
API_PRIVATE_KEY=your_turnkey_api_private_key
API_PUBLIC_KEY=your_turnkey_api_public_key
ORGANIZATION_ID=your_turnkey_organization_id
RHINESTONE_API_KEY=your_rhinestone_api_key

Next Steps