Unshield NFTs
Unshielding requires you to "Generate Proof" and, optionally, make use of "Relayers": See UX for Private Transactions UX for Private Transactions
Unshield multiple ERC-721 tokens into a public wallet in a single transaction.
Gas Estimate
import {
NetworkName,
TransactionGasDetails,
RailgunNFTAmountRecipient,
EVMGasType,
FeeTokenDetails,
SelectedRelayer,
getEVMGasTypeForTransaction
} from '@railgun-community/shared-models';
import {
gasEstimateForUnprovenUnshield
} from '@railgun-community/wallet';
// Formatted NFT amounts and recipients.
const nftAmountRecipients: RailgunNFTAmountRecipient[] = [
{
nftAddress: '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d',
tokenSubID: '135', // tokenID of NFT
amount: 1n, // unshield amount - always 1n for ERC-721
nftTokenType: NFTTokenType.ERC721,
recipientAddress: '0x....123', // Public address
},
];
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;
}
// 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 gasEstimateForUnprovenUnshield(
NetworkName.Ethereum,
railgunWalletID,
encryptionKey,
[], // erc20AmountRecipients
nftAmountRecipients,
originalGasDetails,
feeTokenDetails,
sendWithPublicWallet,
);
const transactionGasDetails: TransactionGasDetails = {
evmGasType,
gasEstimate,
gasPrice
}
Generate Proof
...
import {
...,
calculateGasPrice,
} from '@railgun-community/shared-models';
import {
...,
generateUnshieldProof,
} from '@railgun-community/wallet';
...
// See above for previously generated fields
const generateProof = async (
selectedTokenFeeAddress: string,
selectedRelayer: SelectedRelayer,
railgunWalletID: string,
encryptionKey: string,
nftAmountRecipients: RailgunNFTAmountRecipient[],
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.
};
await generateUnshieldProof(
NetworkName.Ethereum,
railgunWalletID,
encryptionKey,
[], // erc20AmountRecipients
nftAmountRecipients, // nftAmountRecipients
relayerFeeERC20AmountRecipient,
sendWithPublicWallet,
overallBatchMinGasPrice,
progressCallback,
);
}
// Call generateProof on interaction from user
Populate Transaction
...
import {
...,
populateProvedUnshield,
} 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,
nftAmountRecipients: RailgunNFTAmountRecipient[],
sendWithPublicWallet: boolean,
transactionGasDetails: TransactionGasDetails,
overallBatchMinGasPrice: Optional<BigInt>, // If sending with Relayer
relayerFeeERC20AmountRecipient: Optional<RailgunERC20AmountRecipient> // If sending with Relayer
) => {
const populateResponse = await populateProvedUnshield(
NetworkName.Ethereum,
railgunWalletID,
[], // 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