Skip to content

Turnkey Integration Guide

Turnkey is secure, non-custodial wallet infrastructure that allows users to generate wallets scoped to your application via Email or WebAuthn. Turnkey leverages secure enclaves and a proprietary policy engine; this novel security architecture ensures that key material is only decrypted within an enclave and any signing is governed by your application's policies. This is great for enabling a secure, flexible experience for your users that can be powerfully enhanced by the benefits of Account Abstraction (gas sponsorship, batching, etc).

Combining Turnkey with Account Kit allows you to create a magical UX for your users. Use Turnkey via the aa-signers package to generate embedded wallets at scale, and then leverage aa-alchemy to create smart accounts for your users!

Integration

Create a Turnkey Account

Create an account and API keys on Turnkey's Dashboard.

Install the SDK

Using TurnkeySigner in the aa-signers package requires installation of the @turnkey/http and @turnkey/viem dependencies. aa-signers lists them as optional dependencies.

Every request to Turnkey must be signed using a stamper. Turnkey supports multiple stampers including @turnkey/webauthn-stamper to sign requests with Passkeys or WebAuthn devices, @turnkey/iframe-stamper with Email, and @turnkey/api-key-stamper with API keys.

bash
npm i -s @turnkey/http
npm i -s @turnkey/viem
bash
yarn add @turnkey/http
yarn add @turnkey/viem

Create a TurnkeySigner

Next, setup the Turnkey SDK and create an authenticated TurnkeySigner using the aa-signers package:

ts
import {
  TurnkeySigner,
  TurnkeySubOrganization,
} from "@alchemy/aa-signers/turnkey";
import { WebauthnStamper } from "@turnkey/webauthn-stamper";
import { http } from "viem";

const TURNKEY_BASE_URL = "https://api.turnkey.com";

export const createTurnkeySigner = async () => {
  const turnkeySigner = new TurnkeySigner({
    apiUrl: TURNKEY_BASE_URL,
    // API Key, WebAuthn, or Email Auth [stampers](https://docs.turnkey.com/category/api-design)
    // must sign all requests to Turnkey.
    stamper: new WebauthnStamper({
      rpId: "your.app.xyz",
    }),
  });

  await turnkeySigner.authenticate({
    resolveSubOrganization: async () => {
      return new TurnkeySubOrganization({
        subOrganizationId: "12345678-1234-1234-1234-123456789abc",
        signWith: "0x1234567890123456789012345678901234567890",
      });
    },
    transport: http("https://eth-sepolia.g.alchemy.com/v2/ALCHEMY_API_KEY"),
  });

  return turnkeySigner;
};

Use it with Light Account

Let's see it in action with aa-alchemy and ModularAccount from aa-accounts:

ts
import { createModularAccountAlchemyClient } from "@alchemy/aa-alchemy";
import { sepolia } from "@alchemy/aa-core";
import { createTurnkeySigner } from "./turnkey";

const chain = sepolia;

const provider = await createModularAccountAlchemyClient({
  apiKey: "ALCHEMY_API_KEY",
  chain,
  signer: await createTurnkeySigner(),
});
ts
import {
  TurnkeySigner,
  TurnkeySubOrganization,
} from "@alchemy/aa-signers/turnkey";
import { WebauthnStamper } from "@turnkey/webauthn-stamper";
import { http } from "viem";

const TURNKEY_BASE_URL = "https://api.turnkey.com";

export const createTurnkeySigner = async () => {
  const turnkeySigner = new TurnkeySigner({
    apiUrl: TURNKEY_BASE_URL,
    // API Key, WebAuthn, or Email Auth [stampers](https://docs.turnkey.com/category/api-design)
    // must sign all requests to Turnkey.
    stamper: new WebauthnStamper({
      rpId: "your.app.xyz",
    }),
  });

  await turnkeySigner.authenticate({
    resolveSubOrganization: async () => {
      return new TurnkeySubOrganization({
        subOrganizationId: "12345678-1234-1234-1234-123456789abc",
        signWith: "0x1234567890123456789012345678901234567890",
      });
    },
    transport: http("https://eth-sepolia.g.alchemy.com/v2/ALCHEMY_API_KEY"),
  });

  return turnkeySigner;
};