# Private NFT Transfers

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

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

ERC-1155 token transfers are not yet supported.

### Imports

```typescript
import {
  calculateGasPrice,
  TXIDVersion,
  type NetworkName,
  type RailgunERC20AmountRecipient,
  type RailgunNFTAmountRecipient,
  type RailgunWalletInfo,
  type TransactionGasDetails,
} from "@railgun-community/shared-models";
import {
  getGasDetailsForTransaction,
  getOriginalGasDetailsForTransaction,
  serializeERC721Transfer,
} from "../util";
import {
  gasEstimateForUnprovenTransfer,
  generateTransferProof,
  populateProvedTransfer,
} from "@railgun-community/wallet";
import {
  TEST_NETWORK,
  TEST_NFT_ADDRESS,
  TEST_NFT_SUBID,
} from "../../utils/constants";
import { getProviderWallet } from "../../utils/provider";
```

### Gas Estimate

```typescript
export const erc721PrivateTransferGasEstimate = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  erc721AmountRecipients: RailgunNFTAmountRecipient[],
  sendWithPublicWallet: boolean = true,
  feeTokenDetails: RailgunERC20AmountRecipient | 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,
    [],
    erc721AmountRecipients,
    originalGasDetails,
    feeTokenDetails,
    sendWithPublicWallet
  );

  return gasEstimate;
};
```

### Generate Proof

```typescript
export const erc721PrivateTransferGenerateProof = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  erc721AmountRecipients: RailgunNFTAmountRecipient[],
  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 ERC721 Transfer Proof progress: ", progress);
  };

  await generateTransferProof(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    showSenderAddressToRecipient,
    memoText,
    [],
    erc721AmountRecipients,
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    progressCallback
  );
};
```

### Populate Transaction

```typescript
export const erc721PrivateTransferPopulateTransaction = async (
  network: NetworkName,
  railgunWalletID: string,
  erc721AmountRecipients: RailgunNFTAmountRecipient[],
  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,
    [], // erc20AmountRecipients
    erc721AmountRecipients,
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    transactionGasDetails
  );

  return populateResponse;
};
```

### Example Usage

```typescript
export const TEST_ERC721PrivateTransfer = async (
  encryptionKey: string,
  railgunWalletInfo: RailgunWalletInfo
) => {
  const { wallet } = getProviderWallet();

  const nftAddress = TEST_NFT_ADDRESS;
  const tokenSubID = TEST_NFT_SUBID;
  const erc721AmountRecipients = [
    serializeERC721Transfer(
      nftAddress,
      tokenSubID,
      railgunWalletInfo.railgunAddress
    ),
  ];

  const gasEstimate = await erc721PrivateTransferGasEstimate(
    encryptionKey,
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc721AmountRecipients
  );

  console.log("Private ERC721 TX gasEstimate: ", gasEstimate);

  await erc721PrivateTransferGenerateProof(
    encryptionKey,
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc721AmountRecipients,
    1n
  );

  const transactionGasDetails = await getGasDetailsForTransaction(
    TEST_NETWORK,
    gasEstimate,
    true,
    wallet
  );

  const overallBatchMinGasPrice = calculateGasPrice(transactionGasDetails);

  const transaction = await erc721PrivateTransferPopulateTransaction(
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc721AmountRecipients,
    overallBatchMinGasPrice,
    transactionGasDetails
  );
  console.log("ERC721 transaction: ", transaction);
  // send private ERC721 tx via self-signed tx or broadcaster.
};
```


---

# 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-nft-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.
