Privy
Privy lets your users sign in the way they already do -email, social login, passkey, or an existing wallet - and gives each one a secure, self-custodial embedded wallet with no seed phrase to manage. Because Hinkal only needs a standard signer to operate, a Privy wallet plugs in directly: the same wallet a user signs in with can hold a private, shielded balance and run confidential deposits, withdrawals, transfers, swaps, and private sends.
Privy's role is unchanged. It authenticates the user, manages the embedded wallet, and produces signatures. Hinkal uses those signatures to authorize private operations and never takes custody of the wallet.
Compatibility
Browser
✅
React, Next.js
Library
✅
@privy-io/react-auth v3
How the integration works
Three parties are involved:
The user's Privy wallet - produces every signature. Nothing executes without one.
The Hinkal API - an HTTP service running inside a secure enclave. It decrypts the user's shielded balance, generates the zero-knowledge proofs, and builds the on-chain transactions. The wallet's private key never reaches it.
The relayer - broadcasts private transactions, so the user's public wallet is never the on-chain sender of a confidential operation.
Installation
npm install @privy-io/react-auth ethersyarn add @privy-io/react-auth ethers1. Get a signer from Privy
Every Hinkal request is authorized by the user's wallet, so the first step is to obtain an ethers.Signer from their Privy embedded wallet.
embedded.getEthereumProvider() returns the wallet's EIP-1193 provider; wrapping it in an ethers BrowserProvider yields a Signer. From here, every Hinkal call is identical to any other wallet.
2. How requests are authorized
Hinkal authorizes requests with two kinds of signature, both produced by the Privy wallet. The enclave reconstructs the same data and verifies the signature server-side before doing anything.
Read signature (sessions and getters)
Read-only endpoints - fetching the shielded balance, getting fee structures, quoting a swap - use a personal-message signature. The signed message is exactly:
Transaction signature (EIP-712 typed data)
Each transaction is authorized with an EIP-712 typed-data signature that commits to the exact operation. The enclave rebuilds the identical structure and verifies it, so the user approves precisely what will execute.
The domain is:
Each operation has its own primary type. The full set:
When signing, include only the primary type you are using plus the types it references. A deposit, for example:
Two rules the enclave enforces - the signature must match exactly:
Sort
tokenAmountsby token address (checksummed, ascending) before signing. The enclave re-sorts before verifying; an unsorted array produces a non-matching signature.The
nonceis single-use and expires after 60 seconds. Generate a fresh UUID for every signed request; a reused or expired nonce is rejected server-side.
Write session (sign once for 24 hours)
If you open a write session, the user signs the session message once with an extra third line:
That single signature authorizes every transaction for 24 hours - the per-transaction typed-data signature is skipped. Without a write session, each transaction is signed individually as shown above.
3. Read the shielded balance
The shielded balance is the user's private, encrypted holdings. Reading it uses a read signature passed as query parameters:
The enclave decrypts the user's shielded outputs internally and returns the balances.
4. Shield - deposit (public → private)
A deposit moves funds from the user's public Privy wallet into their shielded balance.
The wallet signs the
Deposittyped data (or a write session is reused).POST /depositwith the signature and details.The enclave returns an unsigned transaction (
txData).The user's wallet signs and broadcasts it - a deposit is the public on-ramp into privacy, sent from the user's own address.
After confirmation, the funds are in the user's shielded balance.
5. Unshield - withdraw (private → public)
A withdrawal sends funds from the shielded balance to any public address. The user does not broadcast it: the enclave builds and proves the transaction and the relayer broadcasts it, so the user's wallet is never the on-chain sender.
The wallet signs the
Withdrawtyped data (or a write session is reused).POST /withdraw.The response is the resulting
txHash.
6. Transfer (private → private)
A transfer moves funds from the user's shielded balance to another user's shielded balance. Both sides stay private - there is no public on-chain link between sender and recipient, and the amount is not visible. The user does not broadcast it: the enclave proves the transaction and the relayer broadcasts it.
The recipient is identified by their recipient info - a private identifier the recipient obtains from GET /recipient-info(authorized by their own session signature) and shares with the sender. It is not a public address.
The sender then signs the Transfer typed data and submits:
The enclave proves and the relayer broadcasts.
7. Swap (within the shielded balance)
A swap exchanges one token for another inside the shielded balance. It is a two-call flow: first fetch a quote, then execute.
As with withdrawals and transfers, the enclave proves and the relayer broadcasts.
8. Private send (one deposit → many private payouts)
A private send deposits public funds once and has the enclave pay out privately to one or more recipients - the on-chain link between sender and recipients is never visible.
POST /private-sendwith the token, recipients, and amounts → returns an order (orderId,approvalAddress,serializedTx,amountIn,amountOut,fee).Approve the ERC20 spend if
approvalAddressis set.Sign and broadcast the deposit with the user's Privy wallet.
Poll
GET /private-send/{orderId}until every scheduled payout completes.
amountIn is what leaves the wallet; amountOut is what recipients collectively receive; the difference is the protocol fee. Amounts are in the token's smallest unit. The user broadcasts the single deposit; the enclave then proves and the relayer broadcasts each private payout.
Summary
Read balance
GET /balance
Read (message)
—
Deposit (shield)
POST /deposit
Deposit typed data
The user's wallet
Withdraw (unshield)
POST /withdraw
Withdraw typed data
Relayer
Transfer (private → private)
POST /transfer
Transfer typed data
Relayer
Swap
GET /get-swap-data + POST /swap
Swap typed data
Relayer
Private send
POST /private-send + poll
session or per-tx
User (deposit) + relayer (payouts)
Last updated