<!-- tempo-docs-context -->
> Source: [/docs/guide/private-zones/send-tokens-across-zones](https://tempo.xyz/developers/docs/guide/private-zones/send-tokens-across-zones.md)
> Section: [Zones](https://tempo.xyz/developers/docs/zones.md). Connect to a Zone and work with private balances, deposits, and withdrawals. Zones is in limited preview; see the introduction for access. Earlier testnet sandbox guides describe a separate integration.
> Earlier testnet sandbox: use [Zones](https://tempo.xyz/developers/docs/zones.md) for the current limited preview.
> [Documentation index](https://tempo.xyz/developers/llms.txt) · [Docs skill](https://tempo.xyz/developers/SKILL.md)

> Tempo MCP: Use `search`, `find_pages`, `read_page`, and `code` at `https://mcp.tempo.xyz` for Tempo and related documentation.
> If an indexed page is missing or reflects an older site, read this site’s [documentation index](https://tempo.xyz/developers/llms.txt) and Markdown pages directly.

# Send tokens across zones

Send `OUSD` test tokens from **Zone A** to **Zone B** in the [earlier testnet sandbox](https://tempo.xyz/developers/docs/guide/private-zones#testnet-compatibility). The transfer settles through the public chain before Zone B credits the balance.

The flow uses `swapAndDepositRouter` on the public chain in same-token mode: withdraw from `Zone A`, skip the swap because the asset stays as `OUSD`, then deposit that `OUSD` into `Zone B`.

Zone A withdraws OUSD to a public router, which skips the swap and deposits OUSD into the Zone B portal. Zone B imports the deposit and credits its private balance later.

<a id="sending-pathusd-from-zone-a-into-zone-b" />

## Sending OUSD from Zone A into Zone B

**Interactive demo: Send tokens across zones**

1. Send tokens across zones

## Code example

This snippet assumes you already have a signed-in `rootClient` on the public chain plus `zoneAClient`, and the shared token, router, and portal constants used throughout the zone guides.

The callback encoding below is specific to the legacy testnet router. Current routers require an encrypted deposit payload and a Tempo refund recipient. See the [testnet compatibility notes](https://tempo.xyz/developers/docs/guide/private-zones#testnet-compatibility) before adapting this route to another deployment.

It shows the core routed send submission path; use the demo above when you want to watch the routed deposit settle into Zone B.

```ts
import { encodeAbiParameters, parseUnits } from 'viem'

const transferAmount = parseUnits('25', 6)

await zoneAClient.zone.signAuthorizationToken({ zoneId: 6 })

const callbackData = encodeAbiParameters(
  [
    { type: 'bool' },
    { type: 'address' },
    { type: 'address' },
    { type: 'address' },
    { type: 'bytes32' },
    { type: 'uint128' },
  ],
  [false, ousd, ZONE_B.portalAddress, rootClient.account.address, zeroBytes32, 0n],
)

const { receipt } = await zoneAClient.zone.requestWithdrawalSync({
  account: rootClient.account,
  amount: transferAmount,
  data: callbackData,
  feeToken: ousd,
  fallbackRecipient: rootClient.account.address,
  gas: routerCallbackGasLimit,
  to: swapAndDepositRouter,
  token: ousd,
})

console.log(receipt.blockNumber)
```

## What this routed send does

The cross-zone transfer path looks like this: the token leaves `Zone A`, briefly lands on the public chain, and is deposited back into `Zone B` as the same asset.

1. Withdraws `OUSD` from `Zone A` through `ZoneOutbox`.
2. Routes that withdrawal to `swapAndDepositRouter` on Tempo.
3. Skips the DEX swap because the input and output token are both `OUSD`.
4. Deposits the routed `OUSD` into `Zone B` through `ZonePortal`.

The target deposit still pays the normal portal deposit fee, so the amount that arrives in `Zone B` is the routed `OUSD` minus that fee.

:::warning
If the routed withdrawal fails on Tempo—for example because the callback reverts or the target deposit cannot be completed—the amount is bounced back to the withdrawal's `fallbackRecipient` inside `Zone A`. The fee is still paid to the sequencer.
:::
