Documentation Index
Fetch the complete documentation index at: https://mintlify.com/tkhq/sdk/llms.txt
Use this file to discover all available pages before exploring further.
@turnkey/wallet-stamper stamps Turnkey API requests using an existing Ethereum or Solana wallet. Instead of registering a new API key or passkey, the user’s existing wallet signs each request. This is useful for onboarding flows where you want to link a Turnkey sub-organization to a wallet the user already controls.
Both Ethereum (secp256k1 / EIP-191) and Solana (ed25519) wallets are supported. The stamper selects the correct signature scheme based on the type property of the wallet interface you provide.
Installation
npm install @turnkey/wallet-stamper @turnkey/http
Prerequisites
Before the wallet can stamp requests, the wallet’s public key must be registered as an API key authenticator on the Turnkey user. You can do this by:
- Creating a sub-organization and passing the wallet public key as an
apiKey during creation, or
- Calling
createApiKeys on an existing user using a parent-organization API key stamper.
Once registered, the wallet can stamp requests independently without any server-side key.
Ethereum wallet example
The package ships with a built-in EthereumWallet class that uses the browser’s injected Ethereum provider (e.g., MetaMask) via EIP-1193.
import { WalletStamper, EthereumWallet } from "@turnkey/wallet-stamper";
import { TurnkeyClient } from "@turnkey/http";
// Uses window.ethereum (MetaMask or any EIP-1193 provider)
const walletStamper = new WalletStamper(new EthereumWallet());
const client = new TurnkeyClient(
{ baseUrl: "https://api.turnkey.com" },
walletStamper,
);
const whoami = await client.getWhoami({
organizationId: process.env.ORGANIZATION_ID!,
});
For Ethereum wallets, the secp256k1 public key cannot be read directly from the wallet. The stamper derives it by requesting a signature from the user and recovering the public key from that signature.
Solana wallet example
Solana wallets expose the public key directly, so no preliminary signature is required. Implement the SolanaWalletInterface for your wallet:
import { Keypair } from "@solana/web3.js";
import { decodeUTF8 } from "tweetnacl-util";
import nacl from "tweetnacl";
import { TurnkeyClient } from "@turnkey/http";
import { WalletStamper, SolanaWalletInterface } from "@turnkey/wallet-stamper";
class SolanaWallet implements SolanaWalletInterface {
keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
type = "solana" as const;
async signMessage(message: string): Promise<string> {
const messageBytes = decodeUTF8(message);
const signature = nacl.sign.detached(messageBytes, this.keypair.secretKey);
return Buffer.from(signature).toString("hex");
}
async getPublicKey(): Promise<string> {
// Convert the Solana public key to a hex-encoded ed25519 public key
const ed25519PublicKey = Buffer.from(
this.keypair.publicKey.toBuffer(),
).toString("hex");
return ed25519PublicKey;
}
}
const walletStamper = new WalletStamper(new SolanaWallet());
const client = new TurnkeyClient({ baseUrl: BASE_URL }, walletStamper);
const whoami = await client.getWhoami({
organizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!,
});
const wallets = await client.getWallets({
organizationId: whoami.organizationId,
});
WalletStamper constructor
An object implementing either EthereumWalletInterface or SolanaWalletInterface. The stamper reads the type property ("ethereum" or "solana") to select the correct signature scheme.
Wallet interfaces
SolanaWalletInterface
Implement this interface to use a Solana wallet:
interface SolanaWalletInterface {
type: "solana";
signMessage: (message: string) => Promise<string>;
getPublicKey: () => Promise<string>; // hex-encoded ed25519 public key
}
getPublicKey() must return the hex-encoded ed25519 public key (not the base58 address). Convert with Buffer.from(keypair.publicKey.toBuffer()).toString("hex").
EthereumWalletInterface
Implement this interface for a custom Ethereum wallet, or use the built-in EthereumWallet class for browser providers:
interface EthereumWalletInterface {
type: "ethereum";
signMessage: (message: string) => Promise<string>;
getPublicKey: () => Promise<string>; // hex-encoded compressed secp256k1 public key
}
For Ethereum wallets, getPublicKey() recovers the compressed secp256k1 public key from a signed message rather than reading it directly from the wallet.
BaseEthereumWallet
For custom Ethereum implementations, extend BaseEthereumWallet and implement only signMessage. The getPublicKey method is provided for you:
import { BaseEthereumWallet } from "@turnkey/wallet-stamper";
class MyCustomWallet extends BaseEthereumWallet {
async signMessage(message: string): Promise<string> {
// Return a hex signature from your wallet
return myCustomSigningLogic(message);
}
}
Registering a wallet public key as an authenticator
Before the wallet can stamp requests, register its public key with Turnkey. Use an existing API key stamper to call createApiKeys:
import { ApiKeyStamper } from "@turnkey/api-key-stamper";
import { TurnkeyClient } from "@turnkey/http";
const apiKeyStamper = new ApiKeyStamper({
apiPublicKey: process.env.API_PUBLIC_KEY ?? "",
apiPrivateKey: process.env.API_PRIVATE_KEY ?? "",
});
const client = new TurnkeyClient({ baseUrl: BASE_URL }, apiKeyStamper);
const activityPoller = createActivityPoller({
client,
requestFn: client.createApiKeys,
});
// For Solana: API_KEY_CURVE_ED25519
// For Ethereum: API_KEY_CURVE_SECP256K1
const curveType = "API_KEY_CURVE_ED25519";
await activityPoller({
type: "ACTIVITY_TYPE_CREATE_API_KEYS_V2",
timestampMs: new Date().getTime().toString(),
organizationId: "your-organization-id",
parameters: {
apiKeys: [
{
apiKeyName: "solana-wallet",
publicKey: walletPublicKey,
curveType,
},
],
userId: "your-user-id",
},
});
After registration, the wallet can sign Turnkey requests without any server-side key material.