Transaction Utils
Functions used throughout the generation of all types of Railgun Interactions.
// transaction/util.ts
import {
EVMGasType,
getEVMGasTypeForTransaction,
NetworkName,
RailgunERC20AmountRecipient,
type RailgunERC20Amount,
type RailgunNFTAmount,
type RailgunNFTAmountRecipient,
type TransactionGasDetails,
} from "@railgun-community/shared-models";
import {
getShieldPrivateKeySignatureMessage,
NFTTokenType,
} from "@railgun-community/wallet";
import { keccak256, type HDNodeWallet, type Wallet } from "ethers";
import { TEST_NETWORK } from "../utils/constants";
import { getProviderWallet } from "../utils/provider";
/**
* Generates a shield private key signature by signing a predefined message with the provided wallet
* and then hashing the signature with keccak256.
*
* @param wallet - The wallet (Wallet or HDNodeWallet) used to sign the shield signature message
* @returns A Promise that resolves to the shield private key signature as a hex string
*/
export const getShieldSignature = async (
wallet: Wallet | HDNodeWallet
): Promise<string> => {
const shieldSignatureMessage = getShieldPrivateKeySignatureMessage();
const shieldPrivateKey = keccak256(
await wallet.signMessage(shieldSignatureMessage)
);
return shieldPrivateKey;
};
/**
* Serializes token address and amount into a RailgunERC20Amount format for relay adapt unshield transactions.
*
* @param tokenAddress - The Ethereum address of the ERC20 token
* @param amount - The token amount as a BigInt value
* @returns A RailgunERC20Amount object containing the token address and amount
*/
export const serializeERC20RelayAdaptUnshield = (
tokenAddress: string,
amount: bigint
): RailgunERC20Amount => {
return {
tokenAddress,
amount,
};
};
/**
* Serializes an ERC721 token for relay adaptation to unshield it.
*
* This function creates a RailgunNFTAmount object that represents an ERC721 token
* with the specified address and token ID. The amount is always set to 1 since
* ERC721 tokens are non-fungible and can only be transferred as whole units.
*
* @param tokenAddress - The contract address of the ERC721 token.
* @param tokenSubID - The unique identifier of the specific ERC721 token.
* @returns A RailgunNFTAmount object configured for ERC721 unshielding.
*/
export const serializeERC721RelayAdaptUnshield = (
tokenAddress: string,
tokenSubID: string
): RailgunNFTAmount => {
return {
nftAddress: tokenAddress,
amount: 1n,
tokenSubID,
nftTokenType: NFTTokenType.ERC721,
};
};
/**
* Serializes ERC20 transfer data into a RailgunERC20AmountRecipient object.
*
* @param tokenAddress - The address of the ERC20 token contract
* @param amount - The amount of tokens to transfer as a bigint
* @param recipient - The address of the transfer recipient
* @returns A RailgunERC20AmountRecipient object containing the transfer details
*/
export const serializeERC20Transfer = (
tokenAddress: string,
amount: bigint,
recipient: string
): RailgunERC20AmountRecipient => {
return {
tokenAddress,
amount,
recipientAddress: recipient,
};
};
/**
* Serializes an ERC721 NFT transfer into a RailgunNFTAmountRecipient object.
*
* @param nftAddress - The contract address of the ERC721 NFT
* @param tokenSubID - The token ID of the ERC721 NFT
* @param recipient - The address of the recipient who will receive the NFT
* @returns A RailgunNFTAmountRecipient object representing the ERC721 transfer with amount always set to 1n
*/
export const serializeERC721Transfer = (
nftAddress: string,
tokenSubID: string,
recipient: string
): RailgunNFTAmountRecipient => {
return {
nftAddress,
amount: 1n, // shield amount - always 1n for ERC-721
tokenSubID,
nftTokenType: NFTTokenType.ERC721,
recipientAddress: recipient,
};
};
export const getOriginalGasDetailsForTransaction = async (
network: NetworkName,
sendWithPublicWallet: boolean
): Promise<TransactionGasDetails> => {
// MOCK HANDLE WALLET MANAGEMENT AND GAS ESTIMATES
const { wallet } = getProviderWallet();
const gasDetails = await getGasDetailsForTransaction(
network,
0n,
sendWithPublicWallet,
wallet
);
return gasDetails;
};
/**
* Retrieves gas details for a transaction based on network and wallet information.
*
* This function determines the appropriate EVM gas type for the transaction and
* creates a structured gas details object with the necessary gas parameters.
* For Type0 and Type1 transactions, it returns gasPrice, while for Type2 transactions,
* it returns maxFeePerGas and maxPriorityFeePerGas according to EIP-1559.
*
* @param network - The blockchain network name to perform the transaction on
* @param gasEstimate - The estimated gas amount required for the transaction as a bigint
* @param sendWithPublicWallet - Indicates whether the transaction is being sent from a public wallet
* @param wallet - The wallet instance used to sign and populate the transaction
*
* @returns A promise that resolves to a TransactionGasDetails object containing appropriate gas parameters
* for the specified network and transaction type
*
* @example
* const gasDetails = await getGasDetailsForTransaction(
* 'ethereum',
* 250000n,
* false,
* myWallet
* );
*/
export const getGasDetailsForTransaction = async (
network: NetworkName,
gasEstimate: bigint,
sendWithPublicWallet: boolean,
wallet: Wallet | HDNodeWallet
) => {
const evmGasType: EVMGasType = getEVMGasTypeForTransaction(
network,
sendWithPublicWallet
);
let gasDetails: TransactionGasDetails;
// populate tx
// send 1 wei to self. get gas details
// THIS IS AN INSECURE WAY TO GET GAS ESTIMATE
// DO NOT USE IN PRODUCTION
const { maxFeePerGas, maxPriorityFeePerGas } =
await wallet.populateTransaction({
to: wallet.address,
value: 1n,
});
switch (evmGasType) {
case EVMGasType.Type0:
case EVMGasType.Type1:
gasDetails = {
evmGasType,
gasEstimate,
gasPrice: BigInt(maxFeePerGas?.valueOf() ?? 0), // 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
gasDetails = {
evmGasType,
gasEstimate,
maxFeePerGas: BigInt(maxFeePerGas?.valueOf() ?? 0),
maxPriorityFeePerGas: BigInt(maxPriorityFeePerGas?.valueOf() ?? 0),
};
break;
}
return gasDetails;
};
// TEST_ function examples are 'real world usage examples'
// as to how it can be consumed in your application.
export const TEST_gasDetails = async () => {
const { wallet } = getProviderWallet();
const gasDetails = await getGasDetailsForTransaction(
TEST_NETWORK,
21000n,
true,
wallet
);
console.log("gasDetails", gasDetails);
};
process.on("uncaughtException", (err, origin) => {
console.log("Uncaught Error", err, origin);
});
process.on("unhandledRejection", async (reason, promise) => {
console.log("Unhandled rejection", reason, promise);
});
Last updated