Unshielding requires you to "Generate Proof" and, optionally, make use of "Relayers": See UX for Private Transactions UX for Private Transactions
Unshield only the base token for a network (eg. ETH) from shielded wETH in a single transaction.
Base tokens cannot be shielded by themselves, as they are not ERC-20 tokens. They must be shielded as a wrapped token, eg. wETH for the Ethereum network. You can choose to unshield a wrapped base token (like wETH) from a user's private balance to the equivalent base token.
Base token unshields are executed as a multi call through RAILGUN Relay Adapt, which unwraps the wETH into ETH at a 1:1 ratio and unshields the result into a public balance. This transaction is performed in isolation and cannot be paired with ERC-20 unshields.
Gas Estimate
import { NetworkName, TransactionGasDetails, RailgunERC20AmountRecipient, EVMGasType, FeeTokenDetails, SelectedRelayer, getEVMGasTypeForTransaction} from'@railgun-community/shared-models';import { gasEstimateForUnprovenUnshieldBaseToken} from'@railgun-community/wallet';// Formatted wrapped token amount.// Tokens will unwrap as wETH and unshield as ETH.constwrappedERC20Amount:RailgunERC20Amount= { tokenAddress:'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',// wETH amount:BigInt('0x10'),// hexadecimal amount};constsendWithPublicWallet=false; // False if sending with Relayer. True if self-signing with public wallet.constevmGasType:EVMGasType=getEVMGasTypeForTransaction(NetworkName.Ethereum, sendWithPublicWallet);constoriginalGasEstimate=0n; // Always 0, we don't have this yet.let originalGasDetails:TransactionGasDetails;switch (evmGasType) {caseEVMGasType.Type0:caseEVMGasType.Type1: originalGasDetails = { evmGasType, originalGasEstimate, gasPrice:BigInt('0x100000'),// Proper calculation of network gasPrice is not covered in this guide }break;caseEVMGasType.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-1559constmaxFeePerGas:BigInt('0x100000');constmaxPriorityFeePerGas:BigInt('0x010000'); originalGasDetails = { evmGasType, originalGasEstimate, maxFeePerGas, maxPriorityFeePerGas, }break;}// From their private balance, the user must select a token to pay the relayer feeconstselectedTokenFeeAddress='0xabc...123';constselectedRelayer:SelectedRelayer=...; // See "Relayers" section to select a relayer// Token Fee for selected Relayer.constfeeTokenDetails:FeeTokenDetails= { tokenAddress: selectedTokenFeeAddress, feePerUnitGas:selectedRelayer.feePerUnitGas,}// Whether to use a Relayer or self-signing wallet.// true for self-signing, false for Relayer.constsendWithPublicWallet=false;constrailgunWalletID='...'; // Obtained after a previous call to `createRailgunWallet`constpublicWalletAddress='0x123...abc'; // The public wallet to unshield toconst { gasEstimate } =awaitgasEstimateForUnprovenUnshieldBaseToken(NetworkName.Ethereum, publicWalletAddress, railgunWalletID, encryptionKey, wrappedERC20Amount, originalGasDetails, feeTokenDetails, sendWithPublicWallet,);consttransactionGasDetails:TransactionGasDetails= { evmGasType, gasEstimate, gasPrice}
Generate Proof
...import { ..., calculateGasPrice,} from'@railgun-community/shared-models';import { ..., generateUnshieldProof,} from'@railgun-community/wallet';...// See above for previously generated fieldsconstgenerateProof=async ( selectedTokenFeeAddress:string, selectedRelayer:SelectedRelayer, railgunWalletID:string, encryptionKey:string, wrappedERC20Amount:RailgunERC20Amount, sendWithPublicWallet:boolean, transactionGasDetails:TransactionGasDetails, publicWalletAddress:string,) => {// Token fee to pay Relayer.constrelayerFeeERC20AmountRecipient: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.constoverallBatchMinGasPrice:Optional<bigint> =awaitcalculateGasPrice(transactionGasDetails);constprogressCallback= (progress:number) => {// Handle proof progress (show in UI). // Proofs can take 20-30 seconds on slower devices. };awaitgenerateUnshieldBaseTokenProof(NetworkName.Ethereum, publicWalletAddress, railgunWalletID, encryptionKey, wrappedERC20Amount, relayerFeeERC20AmountRecipient, sendWithPublicWallet, 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 fieldsconsttransact= ( railgunWalletID:string, wrappedERC20Amount:RailgunERC20Amount, sendWithPublicWallet:boolean, transactionGasDetails:TransactionGasDetails, overallBatchMinGasPrice:Optional<BigInt>,// If sending with Relayer relayerFeeERC20AmountRecipient:Optional<RailgunERC20AmountRecipient> // If sending with Relayer publicWalletAddress:string,) => {constpopulateResponse=awaitpopulateProvedUnshieldBaseToken(NetworkName.Ethereum, publicWalletAddress, railgunWalletID, wrappedERC20Amount, relayerFeeERC20AmountRecipient, sendWithPublicWallet, overallBatchMinGasPrice, transactionGasDetails, );// Submit via Relayer with populateResponse output. See "Relayers" section// or self-sign with any wallet. See "UX: Private Transactions"}