Shield ERC-20 tokens
Shield one or more ERC-20 tokens into RAILGUN private balances in a single transaction
Imports
import {
NETWORK_CONFIG,
NetworkName,
TXIDVersion,
type RailgunERC20AmountRecipient,
} from "@railgun-community/shared-models";
import {
getGasDetailsForTransaction,
getShieldSignature,
serializeERC20Transfer,
} from "../util";
import {
gasEstimateForShield,
populateShield,
} from "@railgun-community/wallet";
import { Contract, type HDNodeWallet, type Wallet } from "ethers";
import { TEST_NETWORK, TEST_TOKEN } from "../../utils/constants";
import { getProviderWallet } from "../../utils/provider";
Gas Estimate
An asynchronous function erc20ShieldGasEstimate
that calculates the estimated gas required to execute a shielding transaction for ERC-20 tokens in the RAILGUN system. It takes as parameters the network name, a wallet instance, and a list of ERC-20 amount recipients. The function first obtains a unique shield signature for the transaction using the provided wallet. It then derives the address from which the tokens are being shielded and calls gasEstimateForShield
to perform the gas estimation based on transaction details like the recipient information and other transaction parameters. The estimated gas is then returned, providing insights into transaction costs before execution.
export const erc20ShieldGasEstimate = async (
network: NetworkName,
wallet: Wallet | HDNodeWallet,
erc20AmountRecipients: RailgunERC20AmountRecipient[]
) => {
const shieldPrivateKey = await getShieldSignature(wallet);
// Address of public wallet we are shielding from
const fromWalletAddress = wallet.address;
const { gasEstimate } = await gasEstimateForShield(
TXIDVersion.V2_PoseidonMerkle,
network,
shieldPrivateKey,
erc20AmountRecipients,
[], // nftAmountRecipients
fromWalletAddress
);
return gasEstimate;
};
Populate Transaction
The code block defines an asynchronous function erc20PopulateShieldTransaction
that facilitates the preparation of a transaction for shielding ERC20 tokens using the Railgun protocol. Initially, it approves the necessary token allowances for the specified erc20AmountRecipients
by interacting with their token contracts. It checks the current allowance and approves additional tokens if needed. The function then estimates the gas required for the shielding transaction by calling erc20ShieldGasEstimate
. With this estimate, it retrieves gas details tailored to whether the transaction is sent using a public or private wallet. Finally, the transaction is populated using populateShield
, which returns the transaction details including gas estimation, nullifiers, and transaction object, suitable for execution on the Ethereum network.
export const erc20PopulateShieldTransaction = async (
network: NetworkName,
wallet: Wallet | HDNodeWallet,
erc20AmountRecipients: RailgunERC20AmountRecipient[],
sendWithPublicWallet: boolean
) => {
// 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 tokens = erc20AmountRecipients.map((erc20AmountRecipient) => {
return erc20AmountRecipient.tokenAddress;
});
for (const amountRecipient of erc20AmountRecipients) {
const contract = new Contract(
amountRecipient.tokenAddress,
[
"function allowance(address owner, address spender) view returns (uint256)",
"function approve(address spender, uint256 amount) external returns (bool)",
],
wallet
);
const allowance = await contract.allowance(wallet.address, spender);
if (allowance >= amountRecipient.amount) {
console.log("already have enough allowance");
continue;
}
const tx = await contract.approve(spender, amountRecipient.amount);
await tx.wait();
}
const gasEstimate = await erc20ShieldGasEstimate(
network,
wallet,
erc20AmountRecipients
);
const shieldPrivateKey = await getShieldSignature(wallet);
const gasDetails = await getGasDetailsForTransaction(
network,
gasEstimate,
sendWithPublicWallet,
wallet
);
const { transaction, nullifiers } = await populateShield(
TXIDVersion.V2_PoseidonMerkle, // this is for V2 of the railgun protocol
network,
shieldPrivateKey,
erc20AmountRecipients,
[],
gasDetails
);
return {
gasEstimate,
gasDetails,
transaction,
nullifiers,
};
};
Example Usage
export const TEST_shieldERC20 = async (railgunWalletAddress: string) => {
const { wallet } = getProviderWallet();
const erc20AmountRecipients = [
serializeERC20Transfer(
TEST_TOKEN, // WETH
1n,
railgunWalletAddress
),
];
const { gasEstimate, gasDetails, transaction, nullifiers } =
await erc20PopulateShieldTransaction(
TEST_NETWORK,
wallet,
erc20AmountRecipients,
true
);
// console.log("gasEstimate: ", gasEstimate);
// console.log("gasDetails: ", gasDetails);
// console.log("transaction: ", transaction);
// console.log("nullifiers: ", nullifiers);
const tx = await wallet.sendTransaction(transaction);
console.log("tx: ", tx);
await tx.wait();
};
Last updated