Private ERC-20 Transfers

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

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


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

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

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

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

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

Last updated