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/stabletrustSDK - 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
1. Get a signer from Turnkey
Stabletrust expects a standardethers.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.
2. Point the SDK at the signer
Create oneConfidentialTransferClient 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 theprivateKey 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 separateapprove 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 calledensureAccount.
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.privateKeyis 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
ensureAccountonce per chain you support. - Account finalization takes time. The first
ensureAccountwaits for on-chain finalization (roughly 45 seconds). It resolves once the account is ready. - Recipients must exist.
confidentialTransferfails if the recipient has never calledensureAccount. 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.