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