Private NFT Transfers

Private Transfers require you to "Generate Proof" and, optionally, make use of "Broadcasters": See UX for Private Transactions

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

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

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

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

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

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.
};

Last updated