@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.
Versions of
@turnkey/iframe-stamper below 2.0.0 are vulnerable to cross-origin attacks. If you are using an older version, upgrade to 2.0.0 or later.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
Chains and formats
Transaction signing works the same way on both chains: serialized unsigned transaction in, serialized signed transaction out, ready to broadcast.
Turnkey reuses
ADDRESS_FORMAT_ETHEREUM (secp256k1) for all EVM chains, so one exported key can sign for any EVM network — the chain is determined by the chainId in the transaction you serialize, not by the key.Architecture
Security model
- Iframe Isolation: Private keys never touch your application’s JavaScript context
- HPKE Encryption: Export bundles are encrypted end-to-end using RFC 9180
- Enclave Verification: All bundles are signed by Turnkey’s secure enclave
- Sandboxed Iframe: The iframe runs with
allow-scripts allow-same-originsandbox restrictions - Organization Validation: Bundles are validated against your organization ID
Prerequisites
- Node.js v20+
- A Turnkey organization with API credentials
- Wallet accounts to export — see Chains and formats for supported address formats
@turnkey/iframe-stamper >= 2.8.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. The snippets below work for both Solana and EVM accounts. Derive the payload types once from the account’s address format, and reuse them everywhere you sign:Step 1: initialize the IframeStamper
Create a component that initializes the iframe and manages its lifecycle.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
0x-prefixed 65-byte secp256k1 signature produced with the EIP-191 personal_sign prefix (so it verifies with standard tooling such as viem’s verifyMessage). Normalize in your application if you handle both.
Step 5: sign transactions
Pass the serialized unsigned transaction and you get back the serialized signed transaction, ready to broadcast.- Solana: hex-encoded serialized
VersionedTransaction. The fee payer must be the injected account, since the iframe only holds that account’s key. - Ethereum/EVM:
0x-prefixed serialized unsigned transaction — build it with any library, e.g. viem’sserializeTransaction. The result is broadcast-ready; pass it straight tosendRawTransaction.
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
Keys for different chains can be loaded side by side. Thetype you pass must match the chain of the address you sign with:
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
Unless you specifically need the base58 representation, use
KeyFormat.Hexadecimal for every account you intend to sign with — see Chains and formats.
Complete example
Here’s a complete React component demonstrating the full flow, for both Solana and EVM accounts:Troubleshooting
”Iframe not ready”
Ensureinit() 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. CallinitEmbeddedKey() 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 toinjectKeyExportBundle.
Best practices
- Always pass the address parameter: When using multi-key support, always specify which address to sign with
- Reuse the embedded key: The embedded key persists across bundle injections, so you can export multiple keys without re-initializing
- Handle page reloads: Implement re-export logic since in-memory keys are lost on reload (the embedded key survives in localStorage)
- Monitor key expiration: Track when keys will expire - embedded key (48h), in-memory keys (24h)
- Default to
KeyFormat.Hexadecimal: It is required for EVM signing and works for Solana signing as well - Match the payload type to the key’s chain: Derive
MessageType/TransactionTypefrom the account’s address format rather than hardcoding it - Prefer typed EVM transactions: EIP-1559 transactions always encode the
chainId, avoiding EIP-155 replay-protection mistakes
Reference
IframeStamper methods
Supported operations
Message signing and transaction signing are both supported on Solana and Ethereum/EVM. See Chains and formats for the per-chain payload and key format requirements.Additional resources
- Example: wallet-export-sign - Sample Next.js app
- @turnkey/iframe-stamper - Package source code
- export-and-sign iframe - Iframe implementation