# Earn from a Private Zone

## Overview

Earn vaults live on the public Tempo chain, while a Zone keeps balances and recipients private. A
Zone gateway moves assets or vault shares to the public chain, performs the vault action, then sends
the result back to an encrypted recipient inside the Zone.

The vault transaction and recovery address are public. The recipient of the returned Zone deposit is
encrypted, and the resulting balance stays private inside the Zone.

:::warning
Tempo Zones are in early development and available on Tempo Testnet only. Expect breaking changes and
do not use them in production.
:::

## Recipes

These recipes assume you have [set up a Tempo client](/tempo),
[connected to a Zone](/tempo/guides/zones/connect), and
[deposited the required assets into it](/tempo/guides/zones/deposit).

### Deposit Zone Assets into a Vault

Use [`earn.privateDeposit.prepare`](/tempo/actions/earn.privateDeposit) on the parent Tempo client to build
the encrypted gateway callback. Submit the prepared withdrawal with the Zone client, then wait for
the public vault deposit and the encrypted return deposit.

:::code-group
```ts twoslash [example.ts]
import { client as parentClient } from './viem.config'
import { client as zoneClient } from './zones.config'

const gateway = '0x0000000000000000000000000000000000000001'
const prepared = await parentClient.earn.privateDeposit.prepare({
  assetAmount: 100_000_000n,
  gateway,
  recipient: zoneClient.account.address,
  recoveryRecipient: parentClient.account.address,
  shareAmountMin: 99_500_000n,
})

const { receipt, senderTag } = await zoneClient.earn.privateDepositSync(prepared)
// @log: { transactionHash: '0x1234...abcd', ... }
console.log('Sender tag:', senderTag)

const deposit = await parentClient.earn.waitForPrivateDeposit({
  actionId: prepared.actionId,
  fromBlock: prepared.fromBlock,
  gateway,
})
// @log: { shares: 99_750_000n, tempoBlockNumber: 123456n, ... }

const info = await zoneClient.zone.waitForTempoBlock({
  tempoBlockNumber: deposit.tempoBlockNumber,
})
// @log: { tempoBlockNumber: 123456n, ... }
```

```ts twoslash [viem.config.ts] filename="viem.config.ts"
// [!include ~/snippets/tempo/viem.config.ts:setup]
```

```ts twoslash [zones.config.ts] filename="zones.config.ts"
// [!include ~/snippets/tempo/zones.config.ts:setup]
```
:::

Use `shareAmount` with `slippageBps` when you have a quote instead of an exact share floor.

### Deposit Another Zone Asset

Pass `assetToken` when the Zone asset differs from the vault asset. The gateway swaps it before the
deposit. Set `vaultAssetAmountMin` to bound the vault assets produced by that swap independently from
the vault share bound.

:::code-group
```ts twoslash [example.ts]
import { client as parentClient } from './viem.config'
import { client as zoneClient } from './zones.config'

const alternateAsset = '0x20c0000000000000000000000000000000000002'
const gateway = '0x0000000000000000000000000000000000000001'
const prepared = await parentClient.earn.privateDeposit.prepare({
  assetAmount: 100_000_000n,
  assetToken: alternateAsset,
  gateway,
  recipient: zoneClient.account.address,
  recoveryRecipient: parentClient.account.address,
  shareAmountMin: 98_500_000n,
  vaultAssetAmountMin: 99_000_000n,
})

const { receipt } = await zoneClient.earn.privateDepositSync(prepared)
// @log: { transactionHash: '0x1234...abcd', ... }

const deposit = await parentClient.earn.waitForPrivateDeposit({
  actionId: prepared.actionId,
  fromBlock: prepared.fromBlock,
  gateway,
})

await zoneClient.zone.waitForTempoBlock({
  tempoBlockNumber: deposit.tempoBlockNumber,
})
```

```ts twoslash [viem.config.ts] filename="viem.config.ts"
// [!include ~/snippets/tempo/viem.config.ts:setup]
```

```ts twoslash [zones.config.ts] filename="zones.config.ts"
// [!include ~/snippets/tempo/zones.config.ts:setup]
```
:::

The gateway must support a swap route from `assetToken` to the vault asset. Omitting
`vaultAssetAmountMin` accepts any non-reverting swap output, so set a deliberate bound.

### Redeem Zone Vault Shares

Use [`earn.privateRedeem.prepare`](/tempo/actions/earn.privateRedeem) to withdraw vault shares from the Zone
and redeem them on the parent chain. Omitting `assetToken` returns the vault asset and lets the action
derive the minimum output from a live vault quote.

:::code-group
```ts twoslash [example.ts]
import { client as parentClient } from './viem.config'
import { client as zoneClient } from './zones.config'

const gateway = '0x0000000000000000000000000000000000000001'
const prepared = await parentClient.earn.privateRedeem.prepare({
  gateway,
  recipient: zoneClient.account.address,
  recoveryRecipient: parentClient.account.address,
  shareAmount: 99_750_000n,
  slippageBps: 50,
})

const { receipt, senderTag } = await zoneClient.earn.privateRedeemSync(prepared)
// @log: { transactionHash: '0x1234...abcd', ... }
console.log('Sender tag:', senderTag)

const redemption = await parentClient.earn.waitForPrivateRedeem({
  actionId: prepared.actionId,
  fromBlock: prepared.fromBlock,
  gateway,
})
// @log: { outputAmount: 99_000_000n, tempoBlockNumber: 123457n, ... }

const info = await zoneClient.zone.waitForTempoBlock({
  tempoBlockNumber: redemption.tempoBlockNumber,
})
// @log: { tempoBlockNumber: 123457n, ... }
```

```ts twoslash [viem.config.ts] filename="viem.config.ts"
// [!include ~/snippets/tempo/viem.config.ts:setup]
```

```ts twoslash [zones.config.ts] filename="zones.config.ts"
// [!include ~/snippets/tempo/zones.config.ts:setup]
```
:::

Pass `assetAmountMin` for an exact output floor, or pass `assetAmount` with `slippageBps` when you
already have a recent quote.

### Redeem into Another Zone Asset

Set `assetToken` to swap the redeemed vault asset before returning it to the Zone. For an alternate
output token, `assetAmountMin` bounds the final post-swap amount.

:::code-group
```ts twoslash [example.ts]
import { client as parentClient } from './viem.config'
import { client as zoneClient } from './zones.config'

const alternateAsset = '0x20c0000000000000000000000000000000000002'
const gateway = '0x0000000000000000000000000000000000000001'
const prepared = await parentClient.earn.privateRedeem.prepare({
  assetAmountMin: 98_500_000n,
  assetToken: alternateAsset,
  gateway,
  recipient: zoneClient.account.address,
  recoveryRecipient: parentClient.account.address,
  shareAmount: 99_750_000n,
})

const { receipt } = await zoneClient.earn.privateRedeemSync(prepared)
// @log: { transactionHash: '0x1234...abcd', ... }

const redemption = await parentClient.earn.waitForPrivateRedeem({
  actionId: prepared.actionId,
  fromBlock: prepared.fromBlock,
  gateway,
})

await zoneClient.zone.waitForTempoBlock({
  tempoBlockNumber: redemption.tempoBlockNumber,
})
```

```ts twoslash [viem.config.ts] filename="viem.config.ts"
// [!include ~/snippets/tempo/viem.config.ts:setup]
```

```ts twoslash [zones.config.ts] filename="zones.config.ts"
// [!include ~/snippets/tempo/zones.config.ts:setup]
```
:::

The gateway must support a route from the vault asset to `assetToken`. A live vault quote cannot
quote the swap output, so provide `assetAmountMin` or an alternate-asset quote with `slippageBps`.

### Return Funds to Another Zone Recipient

The Zone account submitting a private action can encrypt the returned assets for another address in
the same Zone. The sender supplies the private balance and pays the withdrawal fee.

:::code-group
```ts twoslash [example.ts]
import { client as parentClient } from './viem.config'
import { client as zoneClient } from './zones.config'

const beneficiary = '0x2222222222222222222222222222222222222222'
const gateway = '0x0000000000000000000000000000000000000001'
const prepared = await parentClient.earn.privateRedeem.prepare({
  gateway,
  recipient: beneficiary,
  recoveryRecipient: parentClient.account.address,
  shareAmount: 99_750_000n,
  slippageBps: 50,
})

const { receipt } = await zoneClient.earn.privateRedeemSync(prepared)
// @log: { transactionHash: '0x1234...abcd', ... }

const redemption = await parentClient.earn.waitForPrivateRedeem({
  actionId: prepared.actionId,
  fromBlock: prepared.fromBlock,
  gateway,
})

await zoneClient.zone.waitForTempoBlock({
  tempoBlockNumber: redemption.tempoBlockNumber,
})
```

```ts twoslash [viem.config.ts] filename="viem.config.ts"
// [!include ~/snippets/tempo/viem.config.ts:setup]
```

```ts twoslash [zones.config.ts] filename="zones.config.ts"
// [!include ~/snippets/tempo/zones.config.ts:setup]
```
:::

The same `recipient` behavior applies to `earn.privateDeposit.prepare`. The recipient is encrypted,
but the action amount, timing, gateway, and recovery address remain public at the privacy boundary.

### Move Existing Venue Shares into a Zone

Compose [`earn.depositShares.calls`](/tempo/actions/earn.depositShares) with
[`zone.encryptedDeposit.calls`](/tempo/actions/zone.encryptedDeposit) to approve venue shares, mint
EarnToken, approve it to the Zone Portal, and deposit it to an encrypted recipient in one atomic
transaction.

:::code-group
```ts twoslash [example.ts]
import { Abis } from 'viem/tempo'
import { client as parentClient } from './viem.config'
import { client as zoneClient } from './zones.config'

const vaultAddress = '0x0000000000000000000000000000000000000001'
const venueShareToken = '0x20c0000000000000000000000000000000000001'
const venueShareAmount = 500_000_000n
const zoneId = 7

const vault = await parentClient.earn.getVault({ vault: vaultAddress })
const earnShareAmount = await parentClient.readContract({
  abi: Abis.vaultAdapter,
  address: vaultAddress,
  args: [venueShareAmount],
  functionName: 'sharesToTokens',
})
const encrypted = await parentClient.zone.encryptedDeposit.prepare({
  amount: earnShareAmount,
  bouncebackRecipient: parentClient.account.address,
  recipient: zoneClient.account.address,
  token: vault.shareToken,
  zoneId,
})

const calls = [
  ...parentClient.earn.depositShares.calls({
    earnShareAmountMin: earnShareAmount,
    engine: vault.engine.address,
    recipient: parentClient.account.address,
    vault: vaultAddress,
    venueShareAmount,
    venueShareToken,
  }),
  ...parentClient.zone.encryptedDeposit.calls(encrypted),
]

const receipt = await parentClient.sendTransactionSync({ calls })
// @log: { transactionHash: '0x1234...abcd', ... }
```

```ts twoslash [viem.config.ts] filename="viem.config.ts"
// [!include ~/snippets/tempo/viem.config.ts:setup]
```

```ts twoslash [zones.config.ts] filename="zones.config.ts"
// [!include ~/snippets/tempo/zones.config.ts:setup]
```
:::

:::warning
The encrypted deposit moves the fixed `earnShareAmount`. If the vault mints less, the exact minimum
reverts the whole batch. If it mints more, the difference remains public. Settle fees and quote
immediately before submission. When an exact quote is not reliable, use two transactions: call
`earn.depositSharesSync`, then pass its returned `earnShareAmount` to `zone.encryptedDepositSync`.
:::

## Best Practices

### Understand the Privacy Boundary

The vault action, amounts, gateway, and recovery address are visible on the parent chain. The Zone
recipient and balances returned to the Zone remain private.

### Use a Safe Recovery Recipient

`recoveryRecipient` receives funds on the public Tempo chain if a gateway callback fails. Use an
address you control and treat it as public information.

### Bound Every Vault Action

Set an exact minimum output or apply `slippageBps` to a recent quote. A bound protects the public
vault or swap step while the withdrawal crosses chains.

### Wait for Both Chains

The synchronous Zone action only confirms that the withdrawal was accepted. Wait for the gateway
event on Tempo, then wait for the encrypted deposit to be processed inside the Zone.

## See More

<Cards>
  <Card icon="lucide:circle-dollar-sign" title="Deposit & Withdraw" description="Enter and exit an Earn vault directly on Tempo." to="/tempo/guides/earn/deposit-withdraw" />

  <Card icon="lucide:plug" title="Connect to a Zone" description="Authenticate a Zone client before reading balances or submitting withdrawals." to="/tempo/guides/zones/connect" />

  <Card icon="lucide:arrow-down-to-line" title="Deposit to a Zone" description="Fund a Zone with a public or encrypted recipient." to="/tempo/guides/zones/deposit" />

  <Card icon="lucide:arrow-up-from-line" title="Withdraw from a Zone" description="Move assets from a Zone back to the public Tempo chain." to="/tempo/guides/zones/withdraw" />

  <Card icon="lucide:landmark" title="Inspect a Vault" description="Read vault configuration, state, and supported actions." to="/tempo/actions/earn.getVault" />
</Cards>
