# Private ERC-20 Transfers

*Private Transfers* *require you to "Generate Proof" and, optionally, make use of "Relayers": See* [*UX for Private Transactions*](/developer-guide/wallet/transactions/ux-private-transactions.md)

RAILGUN users may transfer multiple ERC-20 tokens to other RAILGUN users in a single transaction, in a fully encrypted and privacy-preserving way.

### Imports

```typescript

import {
  calculateGasPrice,
  NetworkName,
  TXIDVersion,
  type FeeTokenDetails,
  type RailgunERC20AmountRecipient,
  type RailgunWalletInfo,
  type TransactionGasDetails,
} from "@railgun-community/shared-models";
import {
  gasEstimateForUnprovenTransfer,
  generateTransferProof,
  populateProvedTransfer,
} from "@railgun-community/wallet";
import {
  getGasDetailsForTransaction,
  getOriginalGasDetailsForTransaction,
  serializeERC20Transfer,
} from "../util";
import { TEST_NETWORK, TEST_TOKEN } from "../../utils/constants";
import { getProviderWallet } from "../../utils/provider";
import {
  getBroadcasterDetails,
  getBroadcasterFeeRecipientDetails,
  getFeeTokenDetails,
} from "../../waku/waku";
```

### Gas Estimate

```typescript
export const erc20PrivateTransferGasEstimate = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  erc20AmountRecipient: RailgunERC20AmountRecipient[],
  sendWithPublicWallet: boolean = true,
  feeTokenDetails: FeeTokenDetails | undefined = undefined,
  memoText: string | undefined = undefined
) => {
  const originalGasDetails = await getOriginalGasDetailsForTransaction(
    network,
    sendWithPublicWallet
  );
  console.log("originalGasDetails: ", originalGasDetails);
  const { gasEstimate } = await gasEstimateForUnprovenTransfer(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    memoText,
    erc20AmountRecipient,
    [], // nftAmountRecipients
    originalGasDetails,
    feeTokenDetails,
    sendWithPublicWallet
  );

  const estimatedGasDetails = { ...originalGasDetails, gasEstimate };

  return {
    gasEstimate,
    estimatedGasDetails,
    originalGasDetails,
  };
};

```

### Generate Proof

```typescript
export const erc20PrivateTransferGenerateProof = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  tokenAmountRecipients: RailgunERC20AmountRecipient[],
  overallBatchMinGasPrice: bigint,
  showSenderAddressToRecipient: boolean = true,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined,
  memoText: string | undefined = undefined
) => {
  const progressCallback = (progress: number) => {
    // Handle proof progress (show in UI).
    // Proofs can take 20-30 seconds on slower devices.
    console.log("Private ERC20 Transfer Proof progress: ", progress);
  };
  // GENERATES RAILGUN SPENDING PROOF
  await generateTransferProof(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    showSenderAddressToRecipient,
    memoText,
    tokenAmountRecipients,
    [], // nftAmountRecipients
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    progressCallback
  );
};

```

### Populate Transaction

```typescript
export const erc20PrivateTransferPopulateTransaction = async (
  network: NetworkName,
  railgunWalletID: string,
  tokenAmountRecipients: RailgunERC20AmountRecipient[],
  overallBatchMinGasPrice: bigint,
  transactionGasDetails: TransactionGasDetails,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined,
  showSenderAddressToRecipient: boolean = true,
  memoText: string | undefined = undefined
) => {
  const populateResponse = await populateProvedTransfer(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    showSenderAddressToRecipient,
    memoText,
    tokenAmountRecipients,
    [], // nftAmountRecipients
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    transactionGasDetails
  );

  return populateResponse;
};

```

### Example Usage

```typescript
export const TEST_PrivateTransfer = async (
  encryptionKey: string,
  railgunWalletInfo: RailgunWalletInfo,
  memoText: string | undefined, // optional memo text for the transfer
  sendWithPublicWallet: boolean = true
) => {
  console.log("TEST_PrivateTransfer");
  const { wallet } = getProviderWallet();
  // get gas estimate,
  // generate proof,
  // populate tx

  const erc20AmountRecipients: RailgunERC20AmountRecipient[] = [
    serializeERC20Transfer(
      TEST_TOKEN, // WETH
      1n,
      railgunWalletInfo.railgunAddress
    ),
  ];

  // dependent if !sendWithPublicWallet
  const { broadcaster, feeTokenDetails } = await getFeeTokenDetails(
    TEST_NETWORK,
    TEST_TOKEN,
    sendWithPublicWallet
  );

  const { gasEstimate, originalGasDetails, estimatedGasDetails } =
    await erc20PrivateTransferGasEstimate(
      encryptionKey,
      TEST_NETWORK,
      railgunWalletInfo.id,
      erc20AmountRecipients,
      sendWithPublicWallet,
      feeTokenDetails,
      memoText // optional memo text for the transfer
    );

  const transactionGasDetails = await getGasDetailsForTransaction(
    TEST_NETWORK,
    gasEstimate,
    sendWithPublicWallet, // true if using public wallet
    wallet
  );

  // only do this if !sendWithPublicWallet
  const broadcasterDetails = await getBroadcasterDetails(
    estimatedGasDetails,
    broadcaster,
    feeTokenDetails
  );

  console.log("Private ERC20 TX gasEstimate: ", gasEstimate);
  const overallBatchMinGasPrice = calculateGasPrice(transactionGasDetails);

  // generate proof
  await erc20PrivateTransferGenerateProof(
    encryptionKey,
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc20AmountRecipients,
    overallBatchMinGasPrice /* overallBatchMinGasPrice */,
    true /* showSenderAddressToRecipient */,
    sendWithPublicWallet /*true if using public wallet*/,
    broadcasterDetails?.broadcasterFeeERC20AmountRecipient /* pass the broadcaster fee recipient */,
    memoText /* memoText */
  );

  // populate tx
  const transaction = await erc20PrivateTransferPopulateTransaction(
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc20AmountRecipients,
    overallBatchMinGasPrice,
    transactionGasDetails,
    sendWithPublicWallet,
    broadcasterDetails?.broadcasterFeeERC20AmountRecipient /* pass the broadcaster fee recipient */,
    true /* showSenderAddressToRecipient */,
    memoText /* memoText (optional) */
  );
  console.log("ERC20 transaction: ", transaction);
The
};

```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.railgun.org/developer-guide/wallet/transactions/private-transfers/private-erc-20-transfers.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
