Turnkey gives you embedded wallets whose private keys are generated and held inside hardware-isolated secure enclaves and never exposed - to you, to your users, or to Turnkey. Users sign in with a passkey, email, or social login; agents and backends sign programmatically with an API key. Either way you get a standard ethers signer, and the Stabletrust SDK only needs a signer to operate - so a Turnkey wallet plugs straight into ConfidentialTransferClient. Turnkey’s role is unchanged. It holds the key and produces signatures. Stabletrust uses those signatures to derive the user’s encryption key and to broadcast confidential transactions - the raw key never leaves the enclave.
This is the recommended path when you don’t want to custody raw private keys. The Stabletrust API ConfidentialTransferClient accepts any ethers.Signer, so you pass a Turnkey signer instead of building an ethers.Wallet from a raw key. This fits agent and server runtimes especially well: the agent’s key lives in an enclave, not in an environment variable.

Compatibility

How the integration works

Three pieces cooperate, and the wallet’s private key stays inside Turnkey’s enclave the whole time:
  • The Turnkey wallet - produces every signature: the one-time key-derivation signature and each on-chain transaction. It signs inside a secure enclave; the raw key is never exported.
  • The @fairblock/stabletrust SDK - runs in your app or agent. It derives the encryption key from a wallet signature, generates the zero-knowledge proof locally in WebAssembly, and builds the confidential transactions.
  • The chain - the Turnkey signer broadcasts the transaction directly. Amounts are encrypted on-chain; sender and recipient addresses remain visible. There is no Fairblock enclave or relayer in this flow.
A confidential transfer hides the amount, not the participants. The sender and recipient addresses are visible on-chain - only the value moved is encrypted. To also hide the sender’s address, use Unlinkable Transfers.

Installation

You will need a Turnkey organization with an API key pair and a wallet account, plus a JSON-RPC endpoint for your target network. Create these in the Turnkey dashboard.

1. Get a signer from Turnkey

Stabletrust expects a standard ethers.Signer. Turnkey provides one directly through @turnkey/ethers: build a TurnkeySigner, then connect it to a JSON-RPC provider so it can read nonces and broadcast transactions.
TurnkeySigner implements the full signer interface Stabletrust uses - signTypedData for key derivation and sendTransaction for on-chain operations. Everything below is identical no matter how the signer was produced.
In the browser (passkeys): obtain the signer from Turnkey’s React SDK instead, then use it with the SDK unchanged. The only difference is where the client comes from.
See Turnkey’s React quickstart for the exact hook fields. The invariant is the same: build a TurnkeySigner from the browser client, the user’s sub-organization ID, and their Ethereum address, then .connect() a provider.

2. Point the SDK at the signer

Create one ConfidentialTransferClient for the network, then call ensureAccount with the Turnkey signer. ensureAccount triggers a single signature, derives the encryption keypair from it, and - the first time only - registers the public key on-chain.

3. Read the confidential balance

The balance is stored encrypted on-chain. The SDK decrypts it client-side with the privateKey from ensureAccount - no signature needed for a read.

4. Deposit (shield)

Move public ERC-20 tokens into the encrypted balance. The SDK handles the ERC-20 approval for you - no separate approve call.

5. Transfer confidentially

Send tokens from the signer’s encrypted balance to another address’s encrypted balance. The amount is hidden on-chain; the recipient must already have called ensureAccount.

6. Withdraw (unshield)

Move tokens from the encrypted balance back to a public ERC-20 balance. After withdrawal the amount is visible on-chain again.

Putting it together

Notes and security

  • The raw key never leaves the enclave. Turnkey signs inside hardware-isolated secure enclaves; your app receives signatures, never the private key. Stabletrust cannot move funds - it only receives signatures and a derived encryption key.
  • Custody depends on who holds the root. With a server API key, your backend can sign for that wallet - treat the API key like any signing credential. With a passkey-rooted sub-organization (the browser flow), only the user’s passkey can authorize signing, so neither you nor Turnkey can move the funds.
  • The derived key never leaves the client. keys.privateKey is a decryption key derived from a signature - not the wallet’s private key. It is re-derived each session. Keep it in memory only; never log or persist it in plaintext.
  • Keys are per chain and contract. The same wallet derives a different confidential key on each network. Call ensureAccount once per chain you support.
  • Account finalization takes time. The first ensureAccount waits for on-chain finalization (roughly 45 seconds). It resolves once the account is ready.
  • Recipients must exist. confidentialTransfer fails if the recipient has never called ensureAccount. Verify the recipient account exists before sending.

Next steps

Stabletrust API

Confidential payments for agents, driven from a server runtime.

Privy

The same flow with a Privy embedded wallet.

Method Reference

Every client method, parameter, and return type.

Unlinkable Transfers

Hide the sender’s address as well as the amount.