Unshield base token

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.
const wrappedERC20Amount: RailgunERC20Amount = {
  tokenAddress: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // wETH
  amount: BigInt('0x10'), // hexadecimal amount
};

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 publicWalletAddress = '0x123...abc'; // The public wallet to unshield to

const { gasEstimate } = await gasEstimateForUnprovenUnshieldBaseToken(
  NetworkName.Ethereum,
  publicWalletAddress,
  railgunWalletID,
  encryptionKey,
  wrappedERC20Amount,
  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,
  wrappedERC20Amount: RailgunERC20Amount,
  sendWithPublicWallet: boolean,
  transactionGasDetails: TransactionGasDetails,
  publicWalletAddress: string,
) => {
  // 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 generateUnshieldBaseTokenProof(
    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 fields
const transact = (
  railgunWalletID: string,
  wrappedERC20Amount: RailgunERC20Amount,
  sendWithPublicWallet: boolean,
  transactionGasDetails: TransactionGasDetails,
  overallBatchMinGasPrice: Optional<BigInt>, // If sending with Relayer
  relayerFeeERC20AmountRecipient: Optional<RailgunERC20AmountRecipient> // If sending with Relayer
  publicWalletAddress: string,
) => {
  const populateResponse = await populateProvedUnshieldBaseToken(
    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"
}

Last updated