ethers signer to operate, a Privy wallet plugs straight in: the same wallet the user signs in with holds their confidential balance and runs deposits, transfers, and withdrawals.
Privy’s role is unchanged. It authenticates the user, manages the embedded wallet, and produces signatures. Stabletrust uses those signatures to derive the user’s encryption key and to broadcast confidential transactions - it never sees or stores the wallet’s private key.
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 Privy signer instead of building an ethers.Wallet from a raw key. For a complete Next.js app built on this pattern, see Building an App with the SDK.Compatibility
How the integration works
Three pieces cooperate, and the wallet’s private key stays inside Privy the whole time:- The user’s Privy wallet - produces every signature. That includes the one-time key-derivation signature and each on-chain transaction. Nothing moves without it.
- The
@fairblock/stabletrustSDK - runs in your app. It derives the user’s encryption key from a wallet signature, generates the zero-knowledge proof locally in WebAssembly, and builds the confidential transactions. - The chain - the Privy wallet broadcasts the transaction directly. Amounts are encrypted on-chain; the 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 Privy
Stabletrust expects a standardethers.Signer. Privy exposes its wallet as an EIP-1193 provider, so wrap that provider with ethers and take the signer from it.
2. Point the SDK at the signer
Create oneConfidentialTransferClient for the network, then call ensureAccount with the Privy signer. ensureAccount triggers a single wallet signature, derives the user’s 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 - the wallet is not 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 sender’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
- Self-custodial. The wallet’s private key stays inside Privy’s infrastructure. Stabletrust only ever receives signatures and a derived encryption key - it cannot move the user’s funds.
- The derived key never leaves the client.
keys.privateKeyis a decryption key derived in the browser from a signature. It is re-derived on each session and is not the wallet’s private key. 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
Build an App with the SDK
The full Next.js walkthrough this integration is based on.
Turnkey
The same flow with a Turnkey embedded wallet.
Method Reference
Every client method, parameter, and return type.
Unlinkable Transfers
Hide the sender’s address as well as the amount.