> ## Documentation Index
> Fetch the complete documentation index at: https://sdk.umbraprivacy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Conversion

> getNetworkEncryptionToSharedEncryptionConverterFunction: upgrade MXE-only ETAs to Shared mode. getMintEncryptionKeyRotatorFunction: rotate per-mint X25519 encryption keys (MPC).

## Overview

When a user registers their X25519 key after already having encrypted token accounts in MXE-only mode, those existing balances remain in MXE-only mode. `getNetworkEncryptionToSharedEncryptionConverterFunction` upgrades them to **Shared mode** - re-encrypting the balance under both the Arcium MPC key and the user's X25519 key, enabling local balance queries in the future.

<Note>
  This step is only necessary if you deposited tokens before registering your X25519 key (i.e., before calling `register({ confidential: true })`). Deposits made after X25519 registration automatically use Shared mode.
</Note>

## Usage

```typescript theme={null}
import { getNetworkEncryptionToSharedEncryptionConverterFunction } from "@umbra-privacy/sdk/conversion";

const convert = getNetworkEncryptionToSharedEncryptionConverterFunction({ client });

const result = await convert(
  mints,                   // readonly Address[]
  optionalData?,           // OptionalData32 — pre-hashed only
  hooks?,                  // ConvertToSharedHooks
  microLamportsPerAcu?,    // optional Arcium priority fee
);
```

### Parameters

<ParamField path="mints" type="Address[]" required>
  An array of SPL or Token-2022 mint addresses to convert. Only accounts in MXE-only mode with an initialized balance will be converted. Others are automatically skipped and reported in `result.skipped`.
</ParamField>

<ParamField path="optionalData" type="Uint8Array">
  32 bytes of optional metadata stored with each conversion transaction. Defaults to all zeros.
</ParamField>

<ParamField path="hooks" type="ConvertToSharedHooks">
  Optional per-mint + overall lifecycle hooks. Each per-mint conversion is an MPC step (`onPreSend`, `onPostSend`, `onMonitorStarted`, `onFinalized`).
</ParamField>

<ParamField path="microLamportsPerAcu" type="MicroLamportsPerAcu">
  Optional Arcium compute-unit priority fee.
</ParamField>

### Return Value

```typescript theme={null}
type ConvertToSharedEncryptionResult = {
  converted: Map<Address, TransactionSignature>;
  skipped: Map<Address, ConvertToSharedEncryptionSkipReason>;
};
```

* `converted` - mints that were successfully upgraded, mapped to their transaction signature
* `skipped` - mints that were not processed, mapped to the reason they were skipped

Skip reasons:

* `"non_existent"` - No encrypted token account exists for this mint
* `"not_initialised"` - The token account exists but is not initialized
* `"already_shared"` - The token account is already in Shared mode (no-op)
* `"balance_not_initialised"` - The Arcium balance has not been initialized yet

## Example

```typescript theme={null}
import { getNetworkEncryptionToSharedEncryptionConverterFunction } from "@umbra-privacy/sdk/conversion";

const USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
const USDT = "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB";

const convert = getNetworkEncryptionToSharedEncryptionConverterFunction({ client });

const result = await convert([USDC, USDT]);

for (const [mint, signature] of result.converted) {
  console.log(`Converted ${mint}: ${signature}`);
}

for (const [mint, reason] of result.skipped) {
  console.log(`Skipped ${mint}: ${reason}`);
}
```

## Rotate Mint Encryption Key

If a per-mint X25519 key may have been compromised, use the mint X25519 rotator from `@umbra-privacy/sdk/account`:

```typescript theme={null}
import { getMintEncryptionKeyRotatorFunction } from "@umbra-privacy/sdk/account";

const rotateMintKey = getMintEncryptionKeyRotatorFunction({ client });
const result = await rotateMintKey({ mint });
```

This is an MPC operation — it re-encrypts the on-chain balance under the new per-mint key. The account must already be in Shared mode. See [Recovery & Rotation](/sdk/advanced/recovery) for details on when and how to rotate keys.

## When to Call This

A typical onboarding flow checks whether conversion is needed after completing registration:

```typescript theme={null}
import { getUserRegistrationFunction } from "@umbra-privacy/sdk/registration";
import { getNetworkEncryptionToSharedEncryptionConverterFunction } from "@umbra-privacy/sdk/conversion";
import { getEncryptedBalanceQuerierFunction } from "@umbra-privacy/sdk/query";

// 1. Register (ensures X25519 key is on-chain).
const register = getUserRegistrationFunction({ client });
await register({ confidential: true, anonymous: true });

// 2. Check which mints need conversion.
const queryBalance = getEncryptedBalanceQuerierFunction({ client });
const MINTS_TO_CHECK = [USDC, USDT];
const balances = await queryBalance(MINTS_TO_CHECK);

const mxeOnlyMints = [...balances.entries()]
  .filter(([, result]) => result.state === "mxe")
  .map(([mint]) => mint);

if (mxeOnlyMints.length > 0) {
  // 3. Upgrade MXE-only balances to Shared mode.
  const convert = getNetworkEncryptionToSharedEncryptionConverterFunction({ client });
  const conversionResult = await convert(mxeOnlyMints);
  console.log(`Converted ${conversionResult.converted.size} balance(s) to Shared mode`);
}
```

## Error Handling

Conversion processes each mint sequentially. If a transaction fails or is cancelled mid-way, any mints already converted will have their signatures in `result.converted` - they are not rolled back.

```typescript theme={null}
import { isConversionError } from "@umbra-privacy/sdk/errors";

try {
  const result = await convert([USDC, USDT, WSOL]);

  console.log(`Converted ${result.converted.size} balance(s)`);
  console.log(`Skipped ${result.skipped.size} balance(s)`);
} catch (err) {
  if (isConversionError(err)) {
    switch (err.stage) {
      case "transaction-sign":
        // User rejected a per-mint transaction in their wallet.
        // Any mints converted before this point are already confirmed on-chain.
        // Re-call convert() with the remaining mints to resume.
        showNotification("Conversion cancelled. Progress has been saved.");
        break;

      case "account-fetch":
        // RPC connectivity issue while fetching token account state.
        console.error("RPC error during conversion:", err.message);
        break;

      case "transaction-send":
        // Transaction submitted but confirmation timed out.
        // Any mints already converted in this batch are confirmed on-chain.
        console.warn("Confirmation timeout. Check on-chain before retrying.");
        break;

      default:
        // Other stages: initialization, pda-derivation, instruction-build,
        // transaction-build, transaction-compile, transaction-validate.
        console.error("Conversion failed at stage:", err.stage, err);
    }
  } else {
    throw err;
  }
}
```

<Note>
  Conversion is idempotent for mints that are already in Shared mode - they are returned in `result.skipped` with reason `"already_shared"`. It is safe to call `convert()` repeatedly with the same mint list.
</Note>

See [Error Handling](/reference/errors) for a full reference of all error types.

## How It Works

The conversion follows the [dual-instruction pattern](/concepts/how-umbra-works#the-dual-instruction-pattern). For each eligible token account:

1. The handler instruction queues a re-encryption computation on Arcium
2. Arcium MPC decrypts the MXE-only balance and re-encrypts it under both the MPC key and the user's X25519 key
3. The callback instruction updates the on-chain account to Shared mode

After conversion, future balance queries will be able to decrypt locally using the user's X25519 private key.
