Skip to main content
This guide covers the full lifecycle of a Zama confidential vault through the Earn API: discover the vaults, get the confidential token, encrypt amounts with the Zama SDK, enter, read and reveal balances, exit, and cancel a pending request. The request, sign, submit and poll loop is the same as for any other yield. What is different is the amount. Your app encrypts every amount on the client with the Zama SDK and sends the API an encrypted handle and a proof. The API never sees a plain amount, and balances come back encrypted until the owner reveals them with a signature. For how the vaults work, see Zama. To embed a ready-made interface instead of building your own, see the Zama widget guide.

At a glance

Prerequisites

  • Zama yields enabled for your project. Enable them in the Yield.xyz dashboard, from your project’s enabled-yields list. If you do not have a project yet, set one up first. Start with the Sepolia vault.
  • ETH for gas on Ethereum. A deposit has a high gas limit: at least 1,500,000 gas for each vault leg. The node checks that the wallet can pay the gas limit at the maximum fee before it accepts the transaction, so keep more ETH in the wallet than the deposit usually costs.
  • A signer that signs EIP-712 typed data and sends transactions. Revealing a balance needs an EIP-712 permit signed by the owner. Every action returns unsigned transactions that the same wallet signs.
  • The Zama SDK. Install @zama-fhe/sdk@3.7.0. The Zama SDK docs cover it in full. The examples use viem.
We construct. You sign. Every action returns unsigned transactions. Yield.xyz never holds keys and never signs on your behalf. Inspect unsignedTransaction before you sign it, and never modify it.

Basics

The examples use these variables:

The lifecycle

Ten steps cover every flow, in this order: discover, get the confidential token, set up the SDK, encrypt, enter, sign and submit, read and reveal balances, exit privately, exit instantly, and cancel.

1. Discover the vaults

Read every contract address from the yield object. Never hardcode one. status.enter and status.exit turn false while the vault’s own batcher is paused. Check them before you offer the vault. The helper below collects these addresses. Later steps use it:

2. Get the confidential token

A deposit takes the confidential token only. To deposit a plain token, wrap it first. Wrapping happens outside the Yield.xyz API, on the confidential token contract, in up to three transactions:
  1. If the wallet’s allowance for the confidential token is non-zero but too low, reset it to zero. USDT, for example, reverts when one non-zero allowance changes to another.
  2. Approve the confidential token to spend the plain amount.
  3. Call wrap(to, amount) on the confidential token.
Wrapping is one to one in value. The wrapped amount is amount / rate(). rate() converts the plain token’s decimals to the confidential token’s 6 decimals, so read it to know how much to encrypt in step 4. For USDC, USDT and AUSD the decimals match. For WBTC (8 decimals), the wrapper rounds the amount down to a multiple of rate() and refunds the rest. See Zama’s confidential wrapper docs for the contract.
Skip this step if the wallet already holds the confidential token. The Earn API does not unwrap a confidential token held in the wallet, such as cUSDC back to USDC. To leave a vault in the plain token, use an instant exit, which unwraps the share token for you.

3. Set up the SDK

Create one SDK instance per wallet and chain, pointed at the Yield.xyz relayer proxy. The proxy accepts your Yield.xyz API key and handles the Zama side, so you need no Zama key. It accepts chain ids 1 and 11155111, and only the Zama vault contracts that Yield.xyz lists. Zama’s authentication guide explains how the SDK uses relayerUrl and auth. The SDK asks your signer for EIP-712 signatures when it decrypts. It never sends transactions, because those come from the Yield.xyz API.
Create a separate instance for each connected wallet, and call sdk.terminate() when you are done with one. web() runs encryption in a Web Worker that it loads from the package, and falls back to the main thread with a console warning if your bundler cannot load that worker. If encryption freezes the page, copy dist/esm/encrypt.worker.js from @zama-fhe/sdk into your static directory and pass its path: web({ offloadWorker: '/encrypt.worker.js' }). A Content Security Policy then needs worker-src 'self'. To check the wallet’s confidential token balance before a deposit, decrypt it with sdk.createToken(address).balanceOf(owner). The SDK asks the owner to sign a permit the first time.

4. Encrypt the amount and the legs

A deposit needs two encrypted bundles, made for the depositing wallet:
Encrypt again for every new request. A handle made for another wallet or another contract makes the transaction revert. Zama’s Encrypt and decrypt guide covers sdk.encrypt, user decryption with a permit (step 7) and public decryption (step 9) in more depth.

5. Enter

Send the four encrypted arguments to POST /v1/actions/enter. inputToken is optional. If you send it, it must be the confidential token.
Response (excerpt)
One DEPOSIT transaction comes back. It transfers the encrypted amount to the router, which sends each leg to its vault’s batch. amount, amountRaw and amountUsd are null, and encryptedAmount carries the handle. Never send amount, amountRaw, shareAmount, shareAmountRaw or useMaxAmount to a Zama yield. The API rejects them.

6. Sign, broadcast and submit the hash

Run each transaction through the same loop as any other yield, in stepIndex order: sign unsignedTransaction, broadcast it, report the hash with PUT /v1/transactions/{transactionId}/submit-hash, then poll GET /v1/transactions/{transactionId} until the status is terminal. See Enter and exit flows for the full loop and Transaction signing for EVM signing code.
When the DEPOSIT confirms, the deposit waits in the current batch. It starts earning once that batch settles.

7. Read and reveal balances

Every Zama balance has isEncrypted: true, an empty amount, amountUsd set to null, and the encrypted handle in shareAmountRaw. On entering and exiting rows, date is when the batch can next settle. Pending rows appear for batches the wallet joined through Yield.xyz actions. To reveal, decrypt the handles with the owner’s permit. Each handle is decrypted against its own contract: the share token for active, this vault’s deposit leg for entering, and this vault’s redeem leg for exiting.
The first decrypt asks the owner to sign an EIP-712 permit. To show an active balance in the plain token, multiply the share amount by state.pricePerShareState.price on the yield. Revealed amounts belong to the owner. Keep them in memory, and clear them when the wallet changes.
A deposit or a private withdrawal sends an encrypted zero to every other vault of the same asset. Those vaults can show an encrypted balance for the wallet that reveals as zero. Your app can drop a position once it reveals as zero, as the Zama widget does.

8. Exit privately

A private exit redeems shares in the next redemption batch and pays out the confidential token. Leave outputToken out, or set it to the confidential token, and send legs for the redeem batchers. The legs carry the share amount, encrypted for the router, with 0 for every other leg. A private exit sends no encryptedAmount.
The transactions come back in this order: The first private exit can return several SET_OPERATOR transactions, one for each vault of the same asset. Sign them before the REDEEM. When the batch settles, the wallet receives the confidential token. There is nothing to claim.

9. Exit instantly

An instant exit skips the batch and pays out the plain token. Its amount becomes public on-chain. Set outputToken to the plain token (token.address) and send encryptedAmount and encryptedAmountProof, encrypted for the confidential share token (the exit encryptedAmount option):
One UNWRAP transaction comes back. It burns the shares and marks the burned amount for public decryption. Once it confirms, the balances show a withdrawable row with a WITHDRAW pending action:
Balance (excerpt)
Publicly decrypt the row’s handle, then send the cleartext and the proof to POST /v1/actions/manage with the pending action’s passthrough unchanged. A public decrypt needs no signature.
Two transactions come back: WITHDRAW finishes the unwrap, then REDEEM redeems the vault shares for the plain token. Sign them in order. If a WITHDRAW confirmed but its REDEEM did not, a withdrawable row with a plain amount and no isEncrypted appears. Its WITHDRAW pending action needs no arguments and returns one REDEEM.

10. Cancel a pending request

entering and exiting rows carry a CANCEL_PENDING pending action until their batch is sent to the vault. Send it to POST /v1/actions/manage with the passthrough copied from the balance:
cURL
One CANCEL_PENDING transaction comes back. It refunds everything the wallet has in that batch, including deposits made outside Yield.xyz, such as in the Zama app. A canceled deposit returns the confidential token. A canceled exit returns the share token. While a cancel is broadcast but not yet confirmed, the row stays and its CANCEL_PENDING action is not offered. The row disappears once the cancel confirms.

Reward rate

The yield’s reward rate is the vault’s base APY. The Sepolia test vault reports no reward rate. Zama runs separate incentive campaigns that holders opt into in the Zama app. The API does not perform that opt-in, and the reward rate does not include those incentives.

Errors and troubleshooting

API errors come back with the error name and a reason in the response body.

Next steps

Zama widget

Embed confidential vaults with the Yield.xyz widget.

Zama vaults

How confidential deposits, batches and exits work.

Enter and exit flows

The full sign, submit and poll loop.

Transaction signing

EVM signing code for the transactions the API returns.

Zama documentation

Zama’s SDK docs: configuration, encryption, decryption and confidential tokens.