Skip to main content
This guide covers how to set up client-side signing using Turnkey’s @turnkey/iframe-stamper package and the export-and-sign iframe. This architecture enables secure transaction and message signing directly in the browser without exposing private keys to your application code. Note that mishandling of exported private keys introduces inherent risks; please proceed with caution.

Overview

Client-side signing allows you to:
  • Export private keys from Turnkey to a secure iframe
  • Sign transactions and messages directly in the browser via iframe
  • Maintain multiple keys simultaneously for signing operations
  • Keep private keys isolated from your application’s JavaScript context

Architecture

Security model

  1. Iframe Isolation: Private keys never touch your application’s JavaScript context
  2. HPKE Encryption: Export bundles are encrypted end-to-end using RFC 9180
  3. Enclave Verification: All bundles are signed by Turnkey’s secure enclave
  4. Sandboxed Iframe: The iframe runs with allow-scripts allow-same-origin sandbox restrictions
  5. Organization Validation: Bundles are validated against your organization ID

Prerequisites

  • Node.js v20+
  • A Turnkey organization with API credentials
  • Wallet accounts to export (Solana addresses for signing support)
  • @turnkey/iframe-stamper >= 2.7.0

Installation

Environment variables

Sample implementation

Note: the following is for a NextJS application with a separate frontend and backend. The foundations should be applicable for other configurations.

Step 1: initialize the IframeStamper

Create a component that initializes the iframe and manages its lifecycle.
For an example in context, we highly recommend taking a look at the wallet-export-sign example app.

Step 2: create the export caller (backend or client-side)

The export call can be made from a backend API route or a trusted client-side environment. The example below shows a server-side API route; if you call from the client, avoid exposing key material.

Step 3: export a private key to the iframe

Step 4: sign messages

Step 5: sign transactions

Multi-key support

One of the key capabilities of client-side signing is the ability to load and manage multiple private keys simultaneously within the iframe.

Loading multiple keys

Since the embedded key persists across bundle injections, you can export multiple keys using the same embedded key (as long as it hasn’t expired):

Signing with different keys

Clearing keys

Key lifecycle and expiration

Understanding the key lifecycle is important for building reliable applications.

Embedded key (P-256 ECDH)

  • Storage: localStorage within the iframe
  • TTL: 48 hours (default)
  • Purpose: Decrypt incoming export bundles via HPKE
  • Behavior: Persists across bundle injections - the same embedded key can decrypt multiple export bundles until it expires or is explicitly cleared

In-memory private keys

  • Storage: JavaScript memory only (never persisted)
  • TTL: 24 hours
  • Purpose: Sign messages and transactions
  • Behavior: Lost on page reload, cleared on expiration

Handling expiration

Key formats

FormatDescriptionUse Case
KeyFormat.SolanaBase58-encoded 64-byte format (private + public)Phantom, Solflare, Solana keys
KeyFormat.Hexadecimal64 hexadecimal digits (32 bytes)Non-Solana

Complete example

Here’s a complete React component demonstrating the full flow:

Troubleshooting

”Iframe not ready”

Ensure init() has completed before calling other methods. The iframe needs to load and establish the MessageChannel connection.

”Key not found for address”

  • Verify the address is exactly as provided during injectKeyExportBundle (case-sensitive)
  • Check if the key has expired (24-hour TTL)
  • Ensure the page hasn’t been reloaded (keys are in-memory only)

“Embedded key not found”

The embedded key may have expired (48-hour TTL) or been explicitly cleared. Call initEmbeddedKey() to create a new one.

”Organization ID does not match”

The bundle was created for a different organization. Ensure your backend uses the same organization ID as passed to injectKeyExportBundle.

Best practices

  1. Always pass the address parameter: When using multi-key support, always specify which address to sign with
  2. Reuse the embedded key: The embedded key persists across bundle injections, so you can export multiple keys without re-initializing
  3. Handle page reloads: Implement re-export logic since in-memory keys are lost on reload (the embedded key survives in localStorage)
  4. Monitor key expiration: Track when keys will expire - embedded key (48h), in-memory keys (24h)
  5. Use appropriate key formats: Use KeyFormat.Solana for Solana keys

Reference

IframeStamper methods

MethodDescription
init()Insert iframe and establish connection
clear()Remove iframe and clean up resources
getEmbeddedPublicKey()Get current embedded key’s public key
initEmbeddedKey()Create new embedded key
clearEmbeddedKey()Clear the embedded key
injectKeyExportBundle(bundle, orgId, format?, address?)Inject private key into iframe
signMessage(message, address?)Sign a message
signTransaction(transaction, address?)Sign a transaction
clearEmbeddedPrivateKey(address?)Clear in-memory keys

Supported operations

OperationSolanaEthereum
Message signingYesPlanned
Transaction signingYesPlanned

Additional resources