Shield base token

RAILGUN only supports shielding ERC-20 tokens. Base tokens cannot be shielded directly, as they are not ERC-20 tokens. They must be shielded as a wrapped token, eg. wETH for the Ethereum network.

Base token shields are executed as a multi-call through the RAILGUN Relay Adapt contract, which wraps the ETH into wETH at 1:1 ratio, and shields the result into your balance. This must be performed in isolation and cannot support shielding other ERC-20 tokens as part of the same transaction.

Imports

import { type HDNodeWallet, type Wallet } from "ethers";
import {
  getGasDetailsForTransaction,
  getShieldSignature,
  serializeERC20Transfer,
} from "../util";
import {
  NETWORK_CONFIG,
  NetworkName,
  TXIDVersion,
  type RailgunERC20AmountRecipient,
} from "@railgun-community/shared-models";
import {
  gasEstimateForShieldBaseToken,
  populateShieldBaseToken,
} from "@railgun-community/wallet";
import { TEST_NETWORK, TEST_TOKEN } from "../../utils/constants";
import { getProviderWallet } from "../../utils/provider";

Gas Estimate

The baseShieldGasEstimate function is used to estimate the gas required for shielding a base token, such as ETH wrapped as an ERC-20 token (e.g., wETH), using a designated network. It requires parameters such as the network name, a user wallet, recipient details, and the Railgun wallet address. The function retrieves a private key for shielding signatures and calculates the gas estimate required for the operation.

export const baseShieldGasEstimate = async (
  network: NetworkName,
  wallet: Wallet | HDNodeWallet,
  erc20AmountRecipient: RailgunERC20AmountRecipient,
  railgunWalletAddress: string
) => {
  const shieldPrivateKey = await getShieldSignature(wallet);

  // Address of public wallet we are shielding from
  const fromWalletAddress = wallet.address;

  const { gasEstimate } = await gasEstimateForShieldBaseToken(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletAddress,
    shieldPrivateKey,
    erc20AmountRecipient,
    fromWalletAddress
  );

  return gasEstimate;
};

Populate Transaction

The basePopulateShieldTransaction function goes a step further to prepare the transaction for execution. It not only estimates the gas required but also collects necessary gas details and nullifiers to formulate a complete transaction object. The function considers whether the transaction is executed with a public wallet and ensures necessary tokens are approved for the transaction spender. This prepares a transaction that can be sent to the blockchain, facilitating the seamless shielding of tokens into the user's Railgun balance, while maintaining privacy and integrity.

export const basePopulateShieldTransaction = async (
  network: NetworkName,
  wallet: Wallet | HDNodeWallet,
  erc20AmountRecipient: RailgunERC20AmountRecipient,
  sendWithPublicWallet: boolean,
  railgunWalletAddress: string
) => {
  // get gas estimate for tx,
  // populate tx with gas estimate
  // approve token to spender.

  // approve token to spender
  const spender = NETWORK_CONFIG[network].proxyContract;

  const gasEstimate = await baseShieldGasEstimate(
    network,
    wallet,
    erc20AmountRecipient,
    railgunWalletAddress
  );

  const shieldPrivateKey = await getShieldSignature(wallet);

  const gasDetails = await getGasDetailsForTransaction(
    network,
    gasEstimate,
    sendWithPublicWallet,
    wallet
  );

  const { transaction, nullifiers } = await populateShieldBaseToken(
    TXIDVersion.V2_PoseidonMerkle, // this is for V2 of the railgun protocol
    network,
    railgunWalletAddress,
    shieldPrivateKey,
    erc20AmountRecipient,
    gasDetails
  );

  return {
    gasEstimate,
    gasDetails,
    transaction,
    nullifiers,
  };
};

Example Usage

In the example usage (TEST_shieldBASE), these functions are utilized to shield a small amount of a test token (e.g., wETH) into a Railgun wallet address, showcasing the entire process from gas estimation to transaction execution and confirmation on the blockchain.

export const TEST_shieldBASE = async (railgunWalletAddress: string) => {
  console.log("TEST_shieldBASE");
  const { wallet } = getProviderWallet();

  const erc20AmountRecipients = [
    serializeERC20Transfer(
      TEST_TOKEN, // WETH
      1n,
      railgunWalletAddress
    ),
  ];

  const { gasEstimate, gasDetails, transaction, nullifiers } =
    await basePopulateShieldTransaction(
      TEST_NETWORK,
      wallet,
      erc20AmountRecipients[0],
      true,
      railgunWalletAddress
    );

  const tx = await wallet.sendTransaction(transaction);
  console.log("tx: ", tx);
  await tx.wait();
};

Last updated