Private NFT 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-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.
Gas Estimate
import {
NetworkName,
TransactionGasDetails,
RailgunNFTAmountRecipient,
EVMGasType,
FeeTokenDetails,
SelectedRelayer,
NFTTokenType,
getEVMGasTypeForTransaction
} from '@railgun-community/shared-models';
import {
gasEstimateForUnprovenTransfer
} from '@railgun-community/wallet';
// RAILGUN wallet to transfer to.
const railgunAddress = '0zk...e7n';
// Database encryption key. Keep this very safe.
const encryptionKey = '...'; // See "Encryption Keys" in the Private Wallets section.
// Optional encrypted memo text only readable by the sender and receiver.
// May include text and emojis. See "Private Transfers" page for details.
const memoText = 'Thank you for dinner! 🍝😋';
// Formatted NFT amounts and recipients.
const nftAmountRecipients: RailgunNFTAmountRecipient[] = [
{
nftAddress: '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d',
tokenSubID: '135', // tokenID of NFT
amount: 1n, // transfer amount - always 1n for ERC-721
nftTokenType: NFTTokenType.ERC721,
recipientAddress: railgunAddress,
},
];
const sendWithPublicWallet = false; // False if sending with Relayer. True if self-signing with public wallet.
const evmGasType: EVMGasType = getEVMGasTypeForTransaction(
NetworkName.Ethereum,
sendWithPublicWallet
);
const originalGasEstimate = 0n; // Always 0, we don't have this yet.
let originalGasDetails: TransactionGasDetails;
switch (evmGasType) {
case EVMGasType.Type0:
case EVMGasType.Type1:
originalGasDetails = {
evmGasType,
originalGasEstimate,
gasPrice: BigInt('0x100000'), // Proper calculation of network gasPrice is not covered in this guide
}
break;
case EVMGasType.Type2:
// Proper calculation of gas Max Fee and gas Max Priority Fee is not covered in this guide. See: https://docs.alchemy.com/docs/how-to-build-a-gas-fee-estimator-using-eip-1559
const maxFeePerGas: BigInt('0x100000');
const maxPriorityFeePerGas: BigInt('0x010000');
originalGasDetails = {
evmGasType,
originalGasEstimate,
maxFeePerGas,
maxPriorityFeePerGas,
}
break;
}
// If using a Relayer. From their private balance, the user must select a token to pay the relayer fee
const selectedTokenFeeAddress = '0xabc...123';
const selectedRelayer: SelectedRelayer = ...; // See "Relayers" section to select a relayer
// Token Fee for selected Relayer.
const feeTokenDetails: FeeTokenDetails = {
tokenAddress: selectedTokenFeeAddress,
feePerUnitGas: selectedRelayer.feePerUnitGas,
}
// Whether to use a Relayer or self-signing wallet.
// true for self-signing, false for Relayer.
const sendWithPublicWallet = false;
const railgunWalletID = '...'; // Obtained after a previous call to `createRailgunWallet`
const { gasEstimate } = await gasEstimateForUnprovenTransfer(
NetworkName.Ethereum,
railgunWalletID,
encryptionKey,
memoText,
[], // tokenAmountRecipients
nftAmountRecipients,
originalGasDetails,
feeTokenDetails,
sendWithPublicWallet,
);
const transactionGasDetails: TransactionGasDetails = {
evmGasType,
gasEstimate,
gasPrice
}
Generate Proof
...
import {
...,
calculateGasPrice,
} from '@railgun-community/shared-models';
import {
...,
generateTransferProof,
} from '@railgun-community/wallet';
...
// See above for examples of other required fields.
// See above for previously generated fields
const generateProof = async (
selectedTokenFeeAddress: string,
selectedRelayer: SelectedRelayer,
railgunWalletID: string,
encryptionKey: string,
memoText: Optional<string>,
tokenAmountRecipients: RailgunERC20AmountRecipient[],
sendWithPublicWallet: boolean,
transactionGasDetails: TransactionGasDetails,
) => {
// Token fee to pay Relayer.
const relayerFeeERC20AmountRecipient: Optional<RailgunERC20AmountRecipient> = {
tokenAddress: selectedTokenFeeAddress,
// NOTE: Proper calculation of "amount" is based on transactionGasDetails and selectedRelayer.
amount: BigInt('0x10000000'), // See "Relayers" > "Calculating the Relayer Fee" for more info.
recipientAddress: selectedRelayer.railgunAddress,
};
// ONLY required for transactions that are using a Relayer. Can leave undefined if self-signing.
const overallBatchMinGasPrice: Optional<bigint> = await calculateGasPrice(transactionGasDetails);
const progressCallback = (progress: number) => {
// Handle proof progress (show in UI).
// Proofs can take 20-30 seconds on slower devices.
};
const showSenderAddressToRecipient: boolean = true; // Allow recipient to see RAILGUN address of sender
await generateTransferProof(
NetworkName.Ethereum,
railgunWalletID,
encryptionKey,
showSenderAddressToRecipient,
memoText,
tokenAmountRecipients,
[], // nftAmountRecipients
relayerFeeERC20AmountRecipient,
sendWithPublicWallet,
overallBatchMinGasPrice,
progressCallback,
);
}
// Call generateProof on interaction from user
Populate Transaction
...
import {
...,
populateProvedTransfer,
} from '@railgun-community/wallet';
...
// NOTE: Must follow proof generation.
// Use the exact same parameters as proof or this will throw invalid error.
// See above for previously generated fields
const transact = (
railgunWalletID: string,
memoText: Optional<string>,
nftAmountRecipients: RailgunNFTAmountRecipient[],
sendWithPublicWallet: boolean,
transactionGasDetails: TransactionGasDetails,
showSenderAddressToRecipient: boolean,
overallBatchMinGasPrice: Optional<BigInt>, // If sending with Relayer
relayerFeeERC20AmountRecipient: Optional<RailgunERC20AmountRecipient> // If sending with Relayer
) => {
const populateResponse = await populateProvedTransfer(
NetworkName.Ethereum,
railgunWalletID,
showSenderAddressToRecipient,
memoText,
[], // erc20AmountRecipients
nftAmountRecipients,
relayerFeeERC20AmountRecipient,
sendWithPublicWallet,
overallBatchMinGasPrice,
transactionGasDetails,
);
// Submit via Relayer with populateResponse output. See "Relayers" section
// or self-sign with any wallet. See "UX: Private Transactions"
}
Last updated