For the complete documentation index, see llms.txt. This page is also available as Markdown.

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

Environment
Supported
Notes

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 ethers
yarn add @privy-io/react-auth ethers

1. 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 tokenAmounts by token address (checksummed, ascending) before signing. The enclave re-sorts before verifying; an unsorted array produces a non-matching signature.

  • The nonce is 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.

  1. The wallet signs the Deposit typed data (or a write session is reused).

  2. POST /deposit with the signature and details.

  3. The enclave returns an unsigned transaction (txData).

  4. 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.

  1. The wallet signs the Withdraw typed data (or a write session is reused).

  2. POST /withdraw.

  3. 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.

  1. POST /private-send with the token, recipients, and amounts → returns an order (orderId, approvalAddress, serializedTx, amountIn, amountOut, fee).

  2. Approve the ERC20 spend if approvalAddress is set.

  3. Sign and broadcast the deposit with the user's Privy wallet.

  4. 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

Operation
Endpoint
Signature
Who broadcasts

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