# Wallet Overview

RAILGUN Wallet SDK adds incognito mode to any crypto wallet

The [RAILGUN Wallet SDK](https://github.com/Railgun-Community/quickstart) is an open-source project developed by RAILGUN contributors.&#x20;

RAILGUN Wallet SDK gives crypto wallet developers an easy, safe and convenient way to offer on-chain privacy to users on Ethereum, Arbitrum, Polygon, BSC and more.

Wallet is also useful for creating node.js backends that are capable of generating ZK-Proofs and sending anonymous web3 transactions.

### Use cases

RAILGUN Wallet SDK enables dApp and DeFi developers to:&#x20;

* [Shield ERC-20 tokens](/developer-guide/wallet/transactions/shielding) and NFTs
* Manage [shielded balances](/developer-guide/wallet/private-balances)
* [Transfer tokens](/developer-guide/wallet/transactions/private-transfers) privately
* [Interact with smart contracts](/developer-guide/wallet/transactions/cross-contract-calls) privately (see the [Cookbook](/developer-guide/cookbook/cookbook-overview) for recipes)

### Links

*Git repo*: <https://github.com/Railgun-Community/wallet><br>


# Org

Env set up Constants

```typescript
import { NETWORK_CONFIG, NetworkName } from "@railgun-community/shared-models";
import dotenv from "dotenv";
dotenv.config();

if (process.env.RAILGUN_TEST_RPC == null) {
  throw new Error("No TEST RPC URL configured.");
}

/**
 * The network used for testing purposes.
 * Currently set to the Polygon network.
 * @constant {NetworkName}
 */
export const TEST_NETWORK = NetworkName.Polygon;

/**
 * The RPC URL for RAILGUN test environment.
 * This constant retrieves the URL from the RAILGUN_TEST_RPC environment variable.
 * Used for connecting to the test blockchain network for RAILGUN operations.
 */
export const TEST_RPC_URL = `${process.env.RAILGUN_TEST_RPC}`;

/**
 * The address of the wrapped base token for the test network.
 * This constant is derived from the network configuration for the test network.
 * @type {string}
 */
export const TEST_TOKEN = NETWORK_CONFIG[TEST_NETWORK].baseToken.wrappedAddress;

/**
 * The Ethereum address of a test NFT (Non-Fungible Token) contract.
 * This constant is used for testing purposes related to NFT functionality.
 * The actual address is abbreviated as "0x....".
 */
export const TEST_NFT_ADDRESS = "0x....";

/**
 * The sub-identifier for the test NFT.
 * This value is used to uniquely identify a specific NFT within a collection for testing purposes.
 * @constant {string}
 */
export const TEST_NFT_SUBID = "1";

/**
 * Test mnemonic phrase for RAILGUN.
 * Uses the environment variable RAILGUN_TEST_MNEMONIC if available,
 * otherwise falls back to a default test mnemonic.
 * @remarks This should only be used for testing purposes.
 */
export const TEST_MNEMONIC =
  process.env.RAILGUN_TEST_MNEMONIC ??
  "test test test test test test test test test test test junk";

/**
 * A constant representing a test encryption key for development purposes.
 * This is a 64-character hexadecimal string (32 bytes) that can be used as a placeholder
 * or default encryption key in test environments.
 *
 * @remarks
 * Do not use this key in production environments as it is a publicly known value.
 */
export const TEST_ENCRYPTION_KEY =
  "0101010101010101010101010101010101010101010101010101010101010101";
```

Env set up Providers

```typescript
import { JsonRpcProvider, Wallet } from "ethers";
import { TEST_MNEMONIC, TEST_RPC_URL } from "./0_constants";

/**
 * Creates and returns a provider and wallet for JSON-RPC interactions.
 *
 * This utility function initializes a JSON-RPC provider with a test URL
 * and creates a wallet from a test mnemonic phrase. Both the provider
 * and wallet are returned as an object.
 *
 * @returns An object containing the initialized provider and wallet
 * @property {JsonRpcProvider} provider - The JSON-RPC provider instance
 * @property {Wallet} wallet - The wallet instance created from the test mnemonic
 *
 * @example
 * const { provider, wallet } = getProviderWallet();
 * // Now use provider for RPC calls and wallet for signing transactions
 */
export const getProviderWallet = () => {
  const provider = new JsonRpcProvider(TEST_RPC_URL);
  const wallet = Wallet.fromPhrase(TEST_MNEMONIC, provider);

  return {
    provider,
    wallet,
  };
};
```

Env set up Database

````typescript
// Setup for Node builds.
import LevelDownDB from "leveldown";
/**
 * Creates a LevelDown database instance at the specified path.
 *
 * @param dbLocationPath - The file system path where the database will be created
 * @returns A LevelDown database instance
 *
 * @example
 * ```typescript
 * const db = createNodeDatabase('./path/to/database');
 * ```
 */
export const createNodeDatabase = (dbLocationPath: string) => {
  console.log("Creating local database at path: ", dbLocationPath);
  const db = LevelDownDB(dbLocationPath);
  return db;
};

// Setup for Web builds.
import LevelDB from "level-js";
/**
 * Creates a new web database instance at the specified location path
 * @param dbLocationPath - The file system path where the database will be created
 * @returns A new LevelDB database instance
 * @remarks This function initializes a new LevelDB database at the specified path
 * and provides basic database functionality through the returned instance.
 * @example
 * const db = createWebDatabase('./data/mydb');
 */
export const createWebDatabase = (dbLocationPath: string) => {
  indexedDB.databases;
  console.log("Creating local database at path: ", dbLocationPath);
  const db = new LevelDB(dbLocationPath);
  return db;
};

// Setup for React Native builds.

// @ts-ignore
import LeveldownNodejsMobile from "leveldown-nodejs-mobile";
/**
 * Creates a native database instance using LeveldownNodejsMobile.
 *
 * @param dbLocationPath - The file system path where the database will be stored
 * @returns A Promise that resolves to a new LeveldownNodejsMobile database instance
 *
 * @example
 * ```typescript
 * const db = await createNativeDatabase('/path/to/database');
 * ```
 */
export const createNativeDatabase = async (dbLocationPath: string) => {
  return new LeveldownNodejsMobile(dbLocationPath);
};
````

Part 1: Env set up Artifact Storage

````typescript
import { ArtifactStore } from "@railgun-community/wallet";
import fs from "fs";

/**
 * Creates a path for a download directory by joining a documents directory path
 * with a specified path.
 *
 * @param documentsDir - The base directory path for documents.
 * @param path - The specific path to append to the documents directory.
 * @returns A string representing the combined path.
 *
 * @example
 * ```typescript
 * const downloadDirPath = createDownloadDirPath('/path/to/documents', 'downloads');
 * console.log(downloadDirPath); // Output: '/path/to/documents/downloads'
 * ```
 */
const createDownloadDirPath = (documentsDir: string, path: string) => {
  return `${documentsDir}/${path}`;
};

/**
 * Creates an artifact store for managing file operations in a specified directory.
 *
 * @param documentsDir - The base directory path where artifacts will be stored
 * @returns An ArtifactStore instance with methods for file operations:
 *   - getFile: Reads a file from the artifact store
 *   - storeFile: Writes data to a file in the artifact store, creating directories as needed
 *   - fileExists: Checks if a file exists in the artifact store
 *
 * @example
 * ```typescript
 * const artifactStore = createArtifactStore('/path/to/documents');
 * const fileData = await artifactStore.getFile('path/to/file');
 * await artifactStore.storeFile('dir/name', 'dir/name/file.txt', 'file content');
 * const exists = await artifactStore.fileExists('path/to/file');
 * ```
 */
export const createArtifactStore = (documentsDir: string): ArtifactStore => {
  const getFile = async (path: string) => {
    return fs.promises.readFile(createDownloadDirPath(documentsDir, path));
  };

  const storeFile = async (
    dir: string,
    path: string,
    item: string | Uint8Array<ArrayBufferLike>
  ) => {
    await fs.promises.mkdir(createDownloadDirPath(documentsDir, dir), {
      recursive: true,
    });
    await fs.promises.writeFile(
      createDownloadDirPath(documentsDir, path),
      item
    );
  };

  const fileExists = (path: string): Promise<boolean> => {
    return new Promise((resolve) => {
      fs.promises
        .access(createDownloadDirPath(documentsDir, path))
        .then(() => resolve(true))
        .catch(() => resolve(false));
    });
  };

  return new ArtifactStore(getFile, storeFile, fileExists);
};
````

Engine&#x20;

````typescript
// main.ts
import {
  startRailgunEngine,
  stopRailgunEngine,
} from "@railgun-community/wallet";
import { createNodeDatabase } from "./1_database";
import { createArtifactStore } from "./2_artifact-storage"; // We'll get to this in Step 2: Build a persistent store
import type { POIList } from "@railgun-community/shared-models";
import Level from "level-js";

/**
 * Initializes the RAILGUN engine with the specified configuration.
 *
 * This function sets up the RAILGUN privacy engine with necessary configurations for wallet management,
 * database storage, artifact handling, and Private Proof of Innocence (POI) verification.
 *
 * @remarks
 * - The engine requires a database for storing encrypted wallets
 * - Artifact files are downloaded and stored in a specified directory
 * - POI node URLs are used for verifying that funds are not from undesirable sources
 *
 * @returns A Promise that resolves when the engine is successfully initialized
 *
 * @example
 * ```typescript
 * await initializeEngine();
 * ```
 *
 * @see https://docs.railgun.org for more information about RAILGUN privacy system
 */
/**
 * Initializes the RAILGUN Engine with provided configuration.
 *
 * @param args - Optional initialization parameters
 * @param args.walletSource - Name for your wallet implementation. Maximum of 16 characters, lowercase.
 * @param args.dbPath - Path to LevelDOWN compatible database for storing encrypted wallets
 * @param args.artifactsPath - Persistent storage path for required Engine artifact files
 * @param args.ppoiNodes - Array of Private POI aggregator node URLs
 *
 * @remarks
 * - The engine requires a database for storing encrypted wallets
 * - Artifact files are downloaded and stored in a specified directory
 * - POI node URLs are used for verifying that funds are not from undesirable sources
 *
 * This function configures and starts the RAILGUN Engine with appropriate settings for
 * wallet identification, database storage, artifact management, and Private POI verification.
 * It also sets up a shutdown handler to properly stop the engine when the process is terminated.
 *
 * Private POI (Private Proof of Innocence) provides cryptographic assurance that funds
 * entering the RAILGUN smart contract are not from known undesirable sources. For more
 * information see: https://docs.railgun.org/wiki/assurance/private-proofs-of-innocence
 *
 * @returns Promise that resolves when the engine is initialized
 *
 * @example
 * ```typescript
 * await initializeEngine();
 * ```
 *
 * @see https://docs.railgun.org for more information about RAILGUN privacy system
 */
export const initializeEngine = async (args?: {
  walletSource?: string;
  dbPath?: string;
  artifactsPath?: string;
  ppoiNodes?: string[];
}): Promise<void> => {
  // Name for your wallet implementation.
  // Encrypted and viewable in private transaction history.
  // Maximum of 16 characters, lowercase.
  const walletSource = args?.walletSource ?? "quickstart demo";

  // LevelDOWN compatible database for storing encrypted wallets.
  const dbPath = args?.dbPath ?? "./engine.db";

  //   const db = new Level(dbPath);
  const db = createNodeDatabase(dbPath);

  // Whether to forward Engine debug logs to Logger.
  const shouldDebug = true;

  // Persistent store for downloading large artifact files required by Engine.
  const artifactPath = args?.artifactsPath ?? "artifacts-directory";
  const artifactStore = createArtifactStore(artifactPath);

  // Whether to download native C++ or web-assembly artifacts.
  // True for mobile. False for nodejs and browser.
  const useNativeArtifacts = false;

  // Whether to skip merkletree syncs and private balance scans.
  // Only set to TRUE in shield-only applications that don't
  // load private wallets or balances.
  const skipMerkletreeScans = false;

  // Array of aggregator node urls for Private Proof of Innocence (Private POI), in order of priority.
  // Only one is required. If multiple urls are provided, requests will fall back to lower priority aggregator nodes if primary request fails.
  // Please reach out in the RAILGUN builders groups for information on the public aggregator nodes run by the community.
  //
  // Private POI is a tool to give cryptographic assurance that funds
  // entering the RAILGUN smart contract are not from a known list
  // of transactions or actors considered undesirable by respective wallet providers.
  // For more information: https://docs.railgun.org/wiki/assurance/private-proofs-of-innocence
  // (additional developer information coming soon).

  // LEAVE THIS OUT IN PRODUCTION. This is a public aggregator node for testing purposes.
  const poiNodeURLs: string[] = args?.ppoiNodes ?? [
    "https://ppoi-agg.horsewithsixlegs.xyz",
  ];

  // Add a custom list to check Proof of Innocence against.
  // Leave blank to use the default list for the aggregator node provided.
  const customPOILists: POIList[] = [];

  // Set to true if you would like to view verbose logs for private balance and TXID scans
  const verboseScanLogging = true;

  await startRailgunEngine(
    walletSource,
    db,
    shouldDebug,
    artifactStore,
    useNativeArtifacts,
    skipMerkletreeScans,
    poiNodeURLs,
    customPOILists,
    verboseScanLogging
  );
  process.on("SIGINT", async (sigint) => {
    console.log("EXIT DETECTED", sigint);
    await stopRailgunEngine();
    process.exit(0);
  });
};
````

Groth

````typescript
import { getProver, SnarkJSGroth16 } from "@railgun-community/wallet";
import { groth16 } from "snarkjs";

/**
 * Sets up the Groth16 proving system for the Node.js environment.
 *
 * This function configures the Railgun prover to use the SnarkJS Groth16 implementation.
 * It should be called before performing any zero-knowledge proof operations in a Node.js context.
 *
 * @remarks
 * The function uses the `getProver()` method to obtain the current prover instance
 * and sets the SnarkJS Groth16 implementation on it.
 *
 * @returns A Promise that resolves when the setup is complete
 *
 * @example
 * ```typescript
 * await setupNodeGroth16();
 * // Now the Railgun prover is configured to use Groth16 in Node.js
 * ```
 */
export const setupNodeGroth16 = async (): Promise<void> => {
  // @ts-ignore
  getProver().setSnarkJSGroth16(groth16 as SnarkJSGroth16);
};
````

Network

```typescript
import {
  FallbackProviderJsonConfig,
  NETWORK_CONFIG,
  NetworkName,
} from "@railgun-community/shared-models";
import { loadProvider } from "@railgun-community/wallet";
import { TEST_NETWORK } from "./utils/0_constants";

// forked function, replace with your own provider
/**
 * Loads an Engine provider with the specified URL.
 *
 * This function initializes a provider for the test network using either:
 * 1. A local development provider (default: http://127.0.0.1:8600)
 * 2. External providers that can be uncommented and configured
 *
 * The provider is configured with fallback options, priorities, and weights
 * to ensure reliable connectivity.
 *
 * @param providerUrl - The URL of the primary provider, defaults to "http://127.0.0.1:8600"
 * @returns A Promise that resolves when the provider is successfully loaded
 *
 * @example
 * // Load with default local provider
 * await loadEngineProvider();
 *
 * // Load with custom provider URL
 * await loadEngineProvider("http://my-custom-provider:8600");
 */
export const loadEngineProvider = async (
  providerUrl = "http://127.0.0.1:8600"
) => {
  const TEST_PROVIDERS_JSON: FallbackProviderJsonConfig = {
    chainId: NETWORK_CONFIG[TEST_NETWORK].chain.id,
    providers: [
      // The following is an example of how to use a forked setup

      getProviderInfo(providerUrl),
      // These should be uncommented and filled in with your preferred providers.
      // The above is for using FORK only, do not mix them.
      //   {
      //     provider: "https://cloudflare-eth.com/",
      //     priority: 3,
      //     weight: 2,
      //     maxLogsPerBatch: 1,
      //   },
      //   {
      //     provider: "https://rpc.ankr.com/eth",
      //     priority: 2,
      //     weight: 2,
      //     maxLogsPerBatch: 1,
      //   },
    ],
  };

  const pollingInterval = 1000 * 60 * 5; // 5 min

  await loadProvider(TEST_PROVIDERS_JSON, TEST_NETWORK, pollingInterval);
};

export const getProviderInfo = (providerUrl: string) => {
  return {
    provider: providerUrl,
    priority: 3,
    weight: 2,
    maxLogsPerBatch: 1,
  };
};
```

Debug

```typescript
import { setLoggers } from "@railgun-community/wallet";

/**
 * Sets up custom loggers for the engine.
 *
 * This function configures the engine to use custom logging functions that
 * prefix messages with timestamps and appropriate labels.
 *
 * - For standard logs: Prefixes with "Engine log: [timestamp]"
 * - For error logs: Prefixes with "Engine error: [timestamp]"
 *
 * The configured loggers will output to the console using console.log and console.error.
 *
 * @example
 * // Set up custom engine loggers
 * setEngineLoggers();
 *
 */
export const setEngineLoggers = () => {
  const logMessage = (msg: any) => {
    console.log(`Engine log: ${new Date()} `, msg);
  };
  const logError = (_msg: any) => {
    // console.error(`Engine error: ${new Date()} `, msg);
  };

  setLoggers(logMessage, logError);
};
```

Hash

```typescript
import { pbkdf2 } from "@railgun-community/wallet";
import { Pbkdf2Response } from "@railgun-community/shared-models";

/**
 * Type definition for hashed password data.
 *
 * @property {string} secret - The hashed password or secret.
 * @property {string} salt - The salt used in the password hashing process.
 * @property {number} iterations - The number of iterations used in the hashing algorithm.
 */
type HashPasswordString = {
  secret: string;
  salt: string;
  iterations: number;
};

/**
 * Generates a salted hash from a secret string using PBKDF2 algorithm.
 *
 * @param {object} params - The parameters for hashing
 * @param {string} params.secret - The secret string to be hashed
 * @param {string} params.salt - The salt used in the hashing process
 * @param {number} params.iterations - Number of iterations for the PBKDF2 algorithm
 * @returns {Promise<Pbkdf2Response>} A promise that resolves to the hashing result
 */
export const hashPasswordString = async ({
  secret,
  salt,
  iterations,
}: HashPasswordString): Promise<Pbkdf2Response> => {
  return pbkdf2(secret, salt, iterations);
};
```

Encryption

````typescript
import { getRandomBytes } from "@railgun-community/wallet";
import { hashPasswordString } from "./0_hash-service";
import fs from "fs";

/**
 * Asynchronously stores data in a JSON file.
 *
 * This function takes an object and persists it to the filesystem
 * as a JSON file with the provided filename with a `.json` extension.
 * The data is wrapped in an object with a `data` property before being stored.
 *
 * @param filename - The name of the file where the data will be stored (without extension)
 * @param data - The data to be stored, typically an object like {hashPasswordStored, salt}
 * @returns A Promise that resolves when the data has been written to the file
 *
 * @example
 * ```typescript
 * await storeData('credentials', { hashPasswordStored: '123abc', salt: 'xyz789' });
 * // Creates credentials.json with the content: {"data":{"hashPasswordStored":"123abc","salt":"xyz789"}}
 * ```
 */
export const storeData = async (filename: string, data: any): Promise<void> => {
  // incoming object will be
  // {hashPasswordStored, salt}
  fs.writeFileSync(`${filename}.json`, JSON.stringify({ data }));
};

/**
 * Asynchronously reads and parses JSON data from a file.
 *
 * @param filename - The name of the file to read (without the .json extension)
 * @returns A Promise that resolves to the parsed data property from the JSON file
 * @throws Will throw an error if the file cannot be read or if the JSON is invalid
 *
 * @example
 * ```typescript
 * const data = await getData('config');
 * console.log(data); // Outputs the 'data' property from config.json
 * ```
 */
export const getData = async (filename: string): Promise<any> => {
  return JSON.parse(fs.readFileSync(`${filename}.json`, "utf8")).data;
};

/**
 * Generates and stores encryption keys based on user-provided password.
 *
 * This function creates a secure encryption key from the user's password by:
 * 1. Generating a random salt
 * 2. Deriving an encryption key from the password and salt using 100,000 iterations
 * 3. Generating a separate password hash for storage using 1,000,000 iterations for extra security
 * 4. Storing the salt and the hashed password in local storage
 *
 * @param password - The user-provided password to generate encryption keys from
 * @returns A Promise that resolves with the derived encryption key
 *
 * @example
 * const encryptionKey = await setEncryptionKeyFromPassword("userPassword123");
 */
export const setEncryptionKeyFromPassword = async (
  password: string
): Promise<string> => {
  // Desired `password` comes from user input

  const salt = getRandomBytes(16); // Generate salt
  const [encryptionKey, hashPasswordStored] = await Promise.all([
    hashPasswordString({ secret: password, salt, iterations: 100000 }), // Generate hash from password and salt
    hashPasswordString({ secret: password, salt, iterations: 1000000 }), // Generate hash for stored password. Use more iterations for the stored value.
  ]);

  await Promise.all([
    await storeData("hashPasswordStored", hashPasswordStored), // Save `hashPasswordStored` to local storage
    await storeData("salt", salt), // Save `salt` to local storage
  ]);

  return encryptionKey;
};

/**
 * Validates the user's password and returns an encryption key if valid.
 *
 * This function performs two primary operations:
 * 1. Validates the provided password against a previously stored hash
 * 2. Generates an encryption key based on the password if validation succeeds
 *
 * @param {string} password - The user-provided password to validate
 * @returns {Promise<string>} A promise that resolves to the encryption key if the password is correct
 * @throws {Error} Throws an error if the password is incorrect
 *
 * @remarks
 * The function uses different iteration counts for generating the encryption key (100,000)
 * versus validating the password (1,000,000), which provides a balance between
 * security and performance.
 */
export const getEncryptionKeyFromPassword = async (
  password: string
): Promise<string> => {
  //   `password` comes from user input
  const [storedPasswordHash, storedSalt] = await Promise.all([
    getData("hashPasswordStored"), // Fetch the previously stored password hash from local storage
    getData("salt"), //   ..., // Fetch the previously stored `salt` from local storage
  ]);
  console.log("STORED DATA", storedPasswordHash, storedSalt);
  const [encryptionKey, hashPassword] = await Promise.all([
    hashPasswordString({
      secret: password,
      salt: storedSalt,
      iterations: 100000,
    }), // Same iterations as when generated, i.e. 100000
    hashPasswordString({
      secret: password,
      salt: storedSalt,
      iterations: 1000000,
    }), // Same iterations as when generated, i.e. 1000000
  ]);
  if (storedPasswordHash !== hashPassword) {
    throw new Error("Incorrect password.");
  }
  return encryptionKey;
};
````

Railgun Wallets

```typescript
import {
  NETWORK_CONFIG,
  NetworkName,
  type RailgunWalletInfo,
} from "@railgun-community/shared-models";
import { createRailgunWallet, loadWalletByID } from "@railgun-community/wallet";
import { Mnemonic, randomBytes } from "ethers";
import {
  getEncryptionKeyFromPassword,
  setEncryptionKeyFromPassword,
} from "./1_encryption";
import { TEST_ENCRYPTION_KEY, TEST_NETWORK } from "../utils/0_constants";

/**
 * Creates a new mnemonic phrase using 16 bytes of entropy.
 *
 * A mnemonic phrase is a human-readable representation of a cryptographic seed,
 * typically used to derive private keys in cryptocurrency wallets.
 *
 * @returns A string containing the mnemonic phrase with trimmed whitespace
 * @example
 * const mnemonic = createMnemonic();
 * // mnemonic = "word1 word2 word3 ... word12"
 */
export const createMnemonic = (): string => {
  const mnemonic = Mnemonic.fromEntropy(randomBytes(16)).phrase.trim();
  return mnemonic;
};

export const getEncryptionKey = async (password: string) => {
  const encryptionKey =
    (await getEncryptionKeyFromPassword(password).catch(async (err) => {
      // check for no such file or directory error.
      if (err.code === "ENOENT") {
        console.log("No such file or directory");
        // try to create a new wallet
        return await setEncryptionKeyFromPassword(password);
      }
    })) ?? TEST_ENCRYPTION_KEY;

  return encryptionKey;
};

/**
 * Creates a private RAILGUN wallet using a password and mnemonic phrase.
 *
 * This function generates an encryption key from the provided password,
 * then creates a new RAILGUN wallet using the mnemonic phrase and the
 * appropriate deployment block for the test network. After creation,
 * it loads the wallet by its ID to verify and return the wallet information.
 *
 * @param password - The password to encrypt the wallet
 * @param mnemonic - The mnemonic phrase (seed phrase) to generate the wallet
 * @returns A Promise resolving to the RAILGUN wallet information
 * @throws Will throw an error if wallet creation or loading fails
 */
export const createPrivateWallet = async (
  password: string,
  mnemonic: string
): Promise<RailgunWalletInfo> => {
  // check for stored hashPasswordStored and salt

  const encryptionKey = await getEncryptionKey(password);

  const { deploymentBlock } = NETWORK_CONFIG[TEST_NETWORK];
  const creationBlockMap = {
    [TEST_NETWORK]: deploymentBlock,
  };
  const railgunWalletInfo = await createRailgunWallet(
    encryptionKey,
    mnemonic,
    creationBlockMap
  );
  const walletInfo = await loadWalletByID(
    encryptionKey,
    railgunWalletInfo.id,
    false
  );
  return walletInfo;
};

/**
 * Loads a RAILGUN wallet from storage using a password and wallet ID.
 *
 * @param password - User's password to decrypt the wallet
 * @param walletId - Unique identifier of the RAILGUN wallet to load
 * @returns A Promise resolving to the wallet information (RailgunWalletInfo)
 *
 * @remarks
 * This function retrieves the encryption key derived from the password,
 * then loads the wallet from storage using the encryption key and wallet ID.
 * The third parameter to loadWalletByID is set to false to indicate this is not a viewing wallet.
 */
export const loadPrivateWallet = async (
  password: string,
  walletId: string
): Promise<RailgunWalletInfo> => {
  const encryptionKey = await getEncryptionKey(password);
  const walletInfo = await loadWalletByID(encryptionKey, walletId, false);
  return walletInfo;
};
```

Balances

```typescript
import {
  setOnUTXOMerkletreeScanCallback,
  setOnTXIDMerkletreeScanCallback,
  setOnBalanceUpdateCallback,
  refreshBalances,
} from "@railgun-community/wallet";
import {
  delay,
  MerkletreeScanUpdateEvent,
  NETWORK_CONFIG,
  NetworkName,
  RailgunBalancesEvent,
  RailgunWalletBalanceBucket,
} from "@railgun-community/shared-models";
import { TEST_NETWORK } from "../utils/0_constants";
import { Wallet } from "ethers";
import { FORK_getProviderWallet } from "../utils/forking";

import dotenv from "dotenv";
dotenv.config();

const BALANCE_POLLER_INTERVAL = 1000 * 60 * 1; // 1 min

/**
 * Example Callback function that gets invoked during a private balance scan process.
 * This function handles updates on the progress and status of the UTXO Merkletree scan.
 *
 * @param eventData - The event data containing information about the scan progress
 * @param eventData.progress - The current progress of the scan (typically a number between 0 and 1)
 * @param eventData.scanStatus - The current status of the scan process
 *
 */
const onUTXOMerkletreeScanCallback = (eventData: MerkletreeScanUpdateEvent) => {
  // Will get called throughout a private balance scan.
  // Handle updates on scan progress and status here, i.e. progress bar or loading indicator in the UI.
  console.log("UTXO scan update:", eventData.progress, eventData.scanStatus);
};

/**
 * Example Callback function for handling Merkle tree scan updates during a private balance scan.
 * This function is invoked with status updates throughout the scanning process.
 *
 * @param {MerkletreeScanUpdateEvent} eventData - The event data containing scan progress information
 * @param {number} eventData.progress - The progress percentage of the scan (0-100)
 * @param {string} eventData.scanStatus - The current status of the scan
 *
 */
const onTXIDMerkletreeScanCallback = (eventData: MerkletreeScanUpdateEvent) => {
  // Will get called throughout a private balance scan.
  // Handle updates on scan progress and status here, i.e. progress bar or loading indicator in the UI.
  console.log("TXID scan update:", eventData.progress, eventData.scanStatus);
};

/**
 * Callback function triggered when a RAILGUN wallet's balance is updated.
 *
 * This function is called at the end of a private balance scan for a RAILGUN wallet
 * for each txidVersion and balanceBucket. The function logs balance updates and
 * stores the updated balances in a cache.
 *
 * @param balancesFormatted - The updated balances event containing:
 *   - txidVersion: Currently only V2_PoseidonMerkle, with V3_PoseidonMerkle planned for future
 *   - chain: The blockchain network
 *   - railgunWalletID: Unique identifier for the RAILGUN wallet
 *   - balanceBucket: Category of funds based on the Private POI system status:
 *     - "Spendable": Funds available for use in private DeFi interactions
 *     - "ShieldBlocked": Funds that are blocked from being shielded
 *     - "ShieldPending": Funds in the process of being shielded
 *     - "ProofSubmitted": Funds for which proofs have been submitted
 *     - "MissingInternalPOI": Funds missing internal proof of innocence
 *     - "MissingExternalPOI": Funds missing external proof of innocence
 *     - "Spent": Funds that have been spent
 *   - erc20Amounts: Array of ERC20 token amounts in the wallet
 *   - nftAmounts: Array of NFT amounts in the wallet
 */
const onBalanceUpdateCallback = (balancesFormatted: RailgunBalancesEvent) => {
  // Will get called at the end of a private balance scan for a RAILGUN wallet
  // for each txidVersion and balanceBucket (explained below).
  // RailgunBalancesEvent includes:
  // *txidVersion: TXIDVersion;
  // chain: Chain;
  // railgunWalletID: string;
  // *balanceBucket: RailgunWalletBalanceBucket;
  // erc20Amounts: RailgunERC20Amount[];
  // nftAmounts: RailgunNFTAmount[];
  // *txidVersion: Currently, there is only the V2_PoseidonMerkle txidVersion. In the future,
  // with the launch of V3_PoseidonMerkle, there will be options to migrate balances
  // from V2 to V3.
  // *balanceBucket: With the Private Proof of Innocence system, balances are categorized
  // into "Spendable", "ShieldBlocked", "ShieldPending", "ProofSubmitted", "MissingInternalPOI",
  // "MissingExternalPOI", and "Spent". As funds move through the Private POI system, as
  // explained here: https://docs.railgun.org/wiki/assurance/private-proofs-of-innocence,
  // they will automatically end up in the "Spendable" category, which is when they are
  // able to be used in private DeFi interactions.
  // Handle updates on the private token balances of each available RAILGUN wallet here.
  console.log("Balances updated:", balancesFormatted.balanceBucket);
  if (balancesFormatted.erc20Amounts.length > 0) {
    console.log("ERC20 Balances: ", balancesFormatted.erc20Amounts);
  }
  balanceCache.set(balancesFormatted.balanceBucket, balancesFormatted);
};

/**
 * Sets up callbacks for balance-related operations.
 *
 * This function configures three callbacks:
 * 1. UTXO Merkletree scanning callback
 * 2. TXID Merkletree scanning callback
 * 3. Balance update callback
 *
 * These callbacks are essential for tracking and managing wallet balance updates
 * and scanning operations in the Railgun privacy system.
 *
 * @returns {void}
 */
export const setupBalanceCallbacks = () => {
  setOnUTXOMerkletreeScanCallback(onUTXOMerkletreeScanCallback);
  setOnTXIDMerkletreeScanCallback(onTXIDMerkletreeScanCallback);
  setOnBalanceUpdateCallback(onBalanceUpdateCallback);
};

/**
 * Polls for balance updates for specified RAILGUN wallets at regular intervals.
 *
 * This function starts the private balance scan for all provided RAILGUN wallet IDs.
 * It runs in the background and triggers the appropriate callbacks as the scan progresses.
 * When the scan completes, it calls the onBalanceUpdateCallback.
 * After completion, it waits for a specified interval before polling again recursively.
 *
 * @param walletIds - Array of RAILGUN wallet IDs to poll balances for
 * @returns A Promise that resolves when the current polling iteration is complete
 *
 * @remarks
 * - Uses the TEST_NETWORK configuration to determine the chain
 * - Automatically retries on error
 * - Will resolve the balanceLoadedPromise with "Loaded Balances" if it exists
 * - Waits for BALANCE_POLLER_INTERVAL milliseconds between polling iterations
 */
export const runBalancePoller = async (walletIds: string[]) => {
  // Run this function to start the private balance scan for all RAILGUN wallets.
  // This function will run in the background and call the callbacks above as the scan progresses.
  // It will also call the onBalanceUpdateCallback when the scan is complete.

  const chain = NETWORK_CONFIG[TEST_NETWORK].chain;
  console.log("Running balance poller... on chain", chain);
  try {
    // scan for all wallets.

    await refreshBalances(chain, walletIds);
  } catch (error) {
    console.error("BALANCE REFRESH ERROR", error);
    await refreshBalances(chain, walletIds);
  }
  console.log("Balance poller complete. Waiting for next poll...");
  if (balanceLoadedPromise != null) {
    balanceLoadedPromise("Loaded Balances");
    balanceLoadedPromise = undefined;
  }
  await delay(BALANCE_POLLER_INTERVAL);
  runBalancePoller(walletIds);
};

let balanceLoadedPromise: ((value: unknown) => void) | null | undefined =
  undefined;

/**
 * Waits for balances to be loaded by returning a Promise that resolves
 * when the balanceLoadedPromise is resolved elsewhere in the application.
 *
 * @returns {Promise<void>} A Promise that resolves when balances are loaded
 *
 * @example
 * await waitForBalancesLoaded();
 * // Now you can safely access balances
 */
export const waitForBalancesLoaded = async () => {
  return new Promise((resolve) => {
    balanceLoadedPromise = resolve;
  });
};

/**
 * Cache for storing RAILGUN wallet balance events.
 * This map uses {@link RailgunWalletBalanceBucket} as keys and stores {@link RailgunBalancesEvent} as values.
 * The cache helps reduce redundant balance lookups by storing the most recent balance events for each wallet bucket.
 */
export const balanceCache = new Map<
  RailgunWalletBalanceBucket,
  RailgunBalancesEvent
>();

/**
 * Retrieves the spendable balances from the balance cache.
 *
 * This function accesses the RAILGUN wallet's spendable balances from the cache.
 * Spendable balances are funds that are available for immediate use in transactions.
 *
 * @returns {Object} The spendable balances retrieved from the balance cache.
 *
 * @example
 * const balances = getSpendableBalances();
 * console.log(balances);
 */
export const getSpendableBalances = () => {
  return balanceCache.get(RailgunWalletBalanceBucket.Spendable);
};

/**
 * Displays the spendable balances of ERC20 tokens available to the wallet.
 *
 * This function retrieves spendable balances using the `getSpendableBalances()` function
 * and logs each ERC20 token balance to the console. If no balances are found,
 * it logs a message indicating that no spendable balances were found.
 *
 * @example
 * // Display all spendable token balances in the wallet
 * displaySpendableBalances();
 *
 * @returns {void}
 */
export const displaySpendableBalances = () => {
  const balances = getSpendableBalances();

  if (balances) {
    for (const erc20Amount of balances.erc20Amounts) {
      console.log("ERC20 Balance: ", erc20Amount);
    }
  } else {
    console.log("No spendable balances found.");
  }
};

/**
 * Funds a wallet address with the specified amount of Ether.
 * Uses a private key stored in the environment variable RAILGUN_TEST_PRIVATE_KEY.
 *
 * @param amount - The amount of Ether to send in bigint format
 * @throws {Error} If RAILGUN_TEST_PRIVATE_KEY is not configured in the environment
 * @returns {Promise<ethers.providers.TransactionResponse>} The transaction response object
 */
export const TEST_fundWalletAddress = async (amount: bigint) => {
  // get wallet
  if (!process.env.RAILGUN_TEST_PRIVATE_KEY) {
    console.log("test private key: ", process.env.RAILGUN_TEST_PRIVATE_KEY);
    throw new Error("No TEST_PRIVATE_KEY configured.");
  }
  const privateKey = process.env.RAILGUN_TEST_PRIVATE_KEY as string;
  const { provider, wallet: fundingWallet } = FORK_getProviderWallet();
  const wallet = new Wallet(privateKey, provider);
  const fundingTx = await wallet.sendTransaction({
    to: fundingWallet.address,
    value: amount,
  });
  await fundingTx.wait();
  return fundingTx;
};
```

Shield - Base

```typescript
// BASE TOKEN SHIELDING

import { parseEther, 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/0_constants";
import { getProviderWallet } from "../../utils/1_provider";

export const shieldETH = async (
  network: NetworkName,

  amount: bigint,
  railgunWalletAddress: string
) => {
  const { wallet } = getProviderWallet();

  const erc20AmountRecipients = serializeERC20Transfer(
    TEST_TOKEN, // WETH
    amount,
    railgunWalletAddress
  );
  const { gasEstimate, gasDetails, transaction, nullifiers } =
    await basePopulateShieldTransaction(
      network,
      wallet,
      erc20AmountRecipients,
      true,
      railgunWalletAddress
    );

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

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;
};

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,
  };
};

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();
};
```

Unshield - Base

```typescript
import {
  calculateGasPrice,
  TXIDVersion,
  type NetworkName,
  type RailgunERC20Amount,
  type RailgunERC20AmountRecipient,
  type RailgunWalletInfo,
  type TransactionGasDetails,
} from "@railgun-community/shared-models";
import { TEST_NETWORK, TEST_TOKEN } from "../../utils/0_constants";
import {
  getGasDetailsForTransaction,
  getOriginalGasDetailsForTransaction,
} from "../util";
import {
  gasEstimateForUnprovenUnshieldBaseToken,
  generateUnshieldBaseTokenProof,
  populateProvedUnshieldBaseToken,
} from "@railgun-community/wallet";
import { getProviderWallet } from "../../utils/1_provider";

export const ethUnshieldGasEstimate = async (
  network: NetworkName,
  railgunWalletID: string,
  encryptionKey: string,
  erc20AmountRecipients: RailgunERC20Amount,
  destinationAddress: string
) => {
  const sendWithPublicWallet = true;
  const originalGasDetails = await getOriginalGasDetailsForTransaction(
    network,
    sendWithPublicWallet
  );

  // dont setup broadcaster connection for simplicity.
  const feeTokenDetails = undefined;
  console.log("unshield ETH originalGasDetails: ", originalGasDetails);

  const { gasEstimate } = await gasEstimateForUnprovenUnshieldBaseToken(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    destinationAddress,
    railgunWalletID,
    encryptionKey,
    erc20AmountRecipients,
    originalGasDetails,
    feeTokenDetails,
    sendWithPublicWallet
  );

  return gasEstimate;
};

export const ethUnshieldGenerateProof = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  destinationAddress: string,
  tokenAmountRecipients: RailgunERC20Amount,
  overallBatchMinGasPrice: bigint,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined
) => {
  const progressCallback = (progress: number) => {
    // Handle proof progress (show in UI).
    // Proofs can take 20-30 seconds on slower devices.
    console.log("Unshield ETH Proof progress: ", progress);
  };

  await generateUnshieldBaseTokenProof(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    destinationAddress,
    railgunWalletID,
    encryptionKey,
    tokenAmountRecipients,
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    progressCallback
  );
};

export const ethUnshieldPopulateTransaction = async (
  network: NetworkName,
  railgunWalletID: string,
  destinationAddress: string,
  tokenAmountRecipients: RailgunERC20Amount,
  overallBatchMinGasPrice: bigint,
  transactionGasDetails: TransactionGasDetails,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined
) => {
  const populateResponse = await populateProvedUnshieldBaseToken(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    destinationAddress,
    railgunWalletID,
    tokenAmountRecipients,
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    transactionGasDetails
  );
  return populateResponse;
};

export const TEST_ETHUnshield = async (
  encryptionKey: string,
  railgunWalletInfo: RailgunWalletInfo
) => {
  const { wallet } = getProviderWallet();

  //
  const wrappedERC20Amount: RailgunERC20Amount = {
    tokenAddress: TEST_TOKEN, // wETH
    amount: 1n, //
  };

  // gas estimate
  const gasEstimate = await ethUnshieldGasEstimate(
    TEST_NETWORK,
    railgunWalletInfo.id,
    encryptionKey,
    wrappedERC20Amount,
    wallet.address
  );

  console.log("ETH UNSHIELD gasEstimate: ", gasEstimate);

  // generate eth unshield proof
  await ethUnshieldGenerateProof(
    encryptionKey,
    TEST_NETWORK,
    railgunWalletInfo.id,
    wallet.address,
    wrappedERC20Amount,
    1n // originalBatchMinGasPrice
  );
  const transactionGasDetails = await getGasDetailsForTransaction(
    TEST_NETWORK,
    gasEstimate,
    true,
    wallet
  );

  const overallBatchMinGasPrice = await calculateGasPrice(
    transactionGasDetails
  );

  // populate tx
  const transaction = await ethUnshieldPopulateTransaction(
    TEST_NETWORK,
    railgunWalletInfo.id,
    wallet.address,
    wrappedERC20Amount,
    overallBatchMinGasPrice,
    transactionGasDetails
  );
  console.log("ETH UNSHIELD transaction", transaction);
};
```

ERC 20 - Shield

```typescript
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, parseEther, type HDNodeWallet, type Wallet } from "ethers";
import { TEST_NETWORK, TEST_TOKEN } from "../../utils/0_constants";
import { getProviderWallet } from "../../utils/1_provider";

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;
};

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,
  };
};

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();
};
```

ERC20 - Transfer

```typescript
// PRIVATE ERC20 TRANSFER FUNCTIONALITY

import {
  calculateGasPrice,
  NetworkName,
  TXIDVersion,
  type FeeTokenDetails,
  type RailgunERC20AmountRecipient,
  type RailgunWalletInfo,
  type TransactionGasDetails,
} from "@railgun-community/shared-models";
import {
  gasEstimateForUnprovenTransfer,
  generateTransferProof,
  populateProvedTransfer,
} from "@railgun-community/wallet";
import {
  getGasDetailsForTransaction,
  getOriginalGasDetailsForTransaction,
  serializeERC20Transfer,
} from "../util";
import { TEST_NETWORK, TEST_TOKEN } from "../../utils/0_constants";
import { getProviderWallet } from "../../utils/1_provider";
import {
  getBroadcasterDetails,
  getBroadcasterFeeRecipientDetails,
  getFeeTokenDetails,
} from "../../waku/1_waku";

export const erc20PrivateTransferGasEstimate = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  erc20AmountRecipient: RailgunERC20AmountRecipient[],
  sendWithPublicWallet: boolean = true,
  feeTokenDetails: FeeTokenDetails | undefined = undefined,
  memoText: string | undefined = undefined
) => {
  const originalGasDetails = await getOriginalGasDetailsForTransaction(
    network,
    sendWithPublicWallet
  );
  console.log("originalGasDetails: ", originalGasDetails);
  const { gasEstimate } = await gasEstimateForUnprovenTransfer(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    memoText,
    erc20AmountRecipient,
    [], // nftAmountRecipients
    originalGasDetails,
    feeTokenDetails,
    sendWithPublicWallet
  );

  const estimatedGasDetails = { ...originalGasDetails, gasEstimate };

  return {
    gasEstimate,
    estimatedGasDetails,
    originalGasDetails,
  };
  // console.log("Gas estimate output for private ERC20 transfer:", output);

  // return gasEstimate;
};

export const erc20PrivateTransferGenerateProof = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  tokenAmountRecipients: RailgunERC20AmountRecipient[],
  overallBatchMinGasPrice: bigint,
  showSenderAddressToRecipient: boolean = true,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined,
  memoText: string | undefined = undefined
) => {
  const progressCallback = (progress: number) => {
    // Handle proof progress (show in UI).
    // Proofs can take 20-30 seconds on slower devices.
    console.log("Private ERC20 Transfer Proof progress: ", progress);
  };
  // GENERATES RAILGUN SPENDING PROOF
  await generateTransferProof(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    showSenderAddressToRecipient,
    memoText,
    tokenAmountRecipients,
    [], // nftAmountRecipients
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    progressCallback
  );
};

export const erc20PrivateTransferPopulateTransaction = async (
  network: NetworkName,
  railgunWalletID: string,
  tokenAmountRecipients: RailgunERC20AmountRecipient[],
  overallBatchMinGasPrice: bigint,
  transactionGasDetails: TransactionGasDetails,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined,
  showSenderAddressToRecipient: boolean = true,
  memoText: string | undefined = undefined
) => {
  const populateResponse = await populateProvedTransfer(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    showSenderAddressToRecipient,
    memoText,
    tokenAmountRecipients,
    [], // nftAmountRecipients
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    transactionGasDetails
  );

  return populateResponse;
};

export const TEST_PrivateTransfer = async (
  encryptionKey: string,
  railgunWalletInfo: RailgunWalletInfo,
  memoText: string | undefined, // optional memo text for the transfer
  sendWithPublicWallet: boolean = true
) => {
  console.log("TEST_PrivateTransfer");
  const { wallet } = getProviderWallet();
  // get gas estimate,
  // generate proof,
  // populate tx

  const erc20AmountRecipients: RailgunERC20AmountRecipient[] = [
    serializeERC20Transfer(
      TEST_TOKEN, // WETH
      1n,
      railgunWalletInfo.railgunAddress
    ),
  ];

  // only do this if !sendWithPublicWallet
  const { broadcaster, feeTokenDetails } = await getFeeTokenDetails(
    TEST_NETWORK,
    TEST_TOKEN,
    sendWithPublicWallet
  );
  // this has to move into the function called below.

  const { gasEstimate, originalGasDetails, estimatedGasDetails } =
    await erc20PrivateTransferGasEstimate(
      encryptionKey,
      TEST_NETWORK,
      railgunWalletInfo.id,
      erc20AmountRecipients,
      sendWithPublicWallet,
      feeTokenDetails,
      memoText // optional memo text for the transfer
    );

  // const feeRecipientDetails = await getBroadcasterFeeRecipientDetails(
  //   broadcaster,
  //   estimatedGasDetails,
  //   feeTokenDetails
  // );

  const transactionGasDetails = await getGasDetailsForTransaction(
    TEST_NETWORK,
    gasEstimate,
    sendWithPublicWallet, // true if using public wallet
    wallet
  );

  // only do this if !sendWithPublicWallet
  const broadcasterDetails = await getBroadcasterDetails(
    estimatedGasDetails,
    broadcaster,
    feeTokenDetails
  );

  console.log("Private ERC20 TX gasEstimate: ", gasEstimate);
  const overallBatchMinGasPrice = calculateGasPrice(transactionGasDetails);

  // generate proof
  await erc20PrivateTransferGenerateProof(
    encryptionKey,
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc20AmountRecipients,
    overallBatchMinGasPrice /* overallBatchMinGasPrice */,
    true /* showSenderAddressToRecipient */,
    sendWithPublicWallet /*true if using public wallet*/,
    broadcasterDetails?.broadcasterFeeERC20AmountRecipient /* pass the broadcaster fee recipient */,
    memoText /* memoText */
  );

  // populate tx
  const transaction = await erc20PrivateTransferPopulateTransaction(
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc20AmountRecipients,
    overallBatchMinGasPrice,
    transactionGasDetails,
    sendWithPublicWallet,
    broadcasterDetails?.broadcasterFeeERC20AmountRecipient /* pass the broadcaster fee recipient */,
    true /* showSenderAddressToRecipient */,
    memoText /* memoText (optional) */
  );
  console.log("ERC20 transaction: ", transaction);
  // send private ERC20 tx.
};
```

ERC20 - Unshield

```typescript
import {
  calculateGasPrice,
  NetworkName,
  TXIDVersion,
  type RailgunERC20AmountRecipient,
  type RailgunWalletInfo,
  type TransactionGasDetails,
} from "@railgun-community/shared-models";
import {
  gasEstimateForUnprovenUnshield,
  generateUnshieldProof,
  populateProvedUnshield,
} from "@railgun-community/wallet";
import {
  getGasDetailsForTransaction,
  getOriginalGasDetailsForTransaction,
  serializeERC20Transfer,
} from "../util";
import { TEST_NETWORK, TEST_TOKEN } from "../../utils/0_constants";
import { getProviderWallet } from "../../utils/1_provider";

export const erc20UnshieldGasEstimate = async (
  network: NetworkName,
  railgunWalletID: string,
  encryptionKey: string,
  erc20AmountRecipients: RailgunERC20AmountRecipient[]
) => {
  const sendWithPublicWallet = true;
  const originalGasDetails = await getOriginalGasDetailsForTransaction(
    network,
    sendWithPublicWallet
  );
  // dont setup broadcaster connection for simplicity.
  const feeTokenDetails = undefined;
  console.log("unshield originalGasDetails: ", originalGasDetails);

  const { gasEstimate } = await gasEstimateForUnprovenUnshield(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    erc20AmountRecipients,
    [], // nft amount recipients
    originalGasDetails,
    feeTokenDetails,
    sendWithPublicWallet
  );

  return gasEstimate;
};

export const erc20UnshieldGenerateProof = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  tokenAmountRecipients: RailgunERC20AmountRecipient[],
  overallBatchMinGasPrice: bigint,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined
) => {
  const progressCallback = (progress: number) => {
    // Handle proof progress (show in UI).
    // Proofs can take 20-30 seconds on slower devices.
    console.log("Unshield ERC20 Proof progress: ", progress);
  };
  await generateUnshieldProof(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    tokenAmountRecipients,
    [], // nft amount recipients
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    progressCallback
  );
};

export const erc20UnshieldPopulateTransaction = async (
  network: NetworkName,
  railgunWalletID: string,
  tokenAmountRecipients: RailgunERC20AmountRecipient[],
  overallBatchMinGasPrice: bigint,
  transactionGasDetails: TransactionGasDetails,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined
) => {
  const populateResponse = await populateProvedUnshield(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    tokenAmountRecipients,
    [], // nftAmountRecipients
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    transactionGasDetails
  );

  return populateResponse;
};

export const TEST_ERC20Unshield = async (
  encryptionKey: string,
  railgunWalletInfo: RailgunWalletInfo
) => {
  console.log("TEST_ERC20Unshield");
  const { wallet } = getProviderWallet();
  const erc20AmountRecipients: RailgunERC20AmountRecipient[] = [
    serializeERC20Transfer(
      TEST_TOKEN, // WETH
      1n,
      wallet.address
    ),
  ];

  const gasEstimate = await erc20UnshieldGasEstimate(
    TEST_NETWORK,
    railgunWalletInfo.id,
    encryptionKey,
    erc20AmountRecipients
  );

  console.log("ERC20 UNSHIELD gasEstimate: ", gasEstimate);

  // generate unshield proof
  await erc20UnshieldGenerateProof(
    encryptionKey,
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc20AmountRecipients,
    1n
  );

  const transactionGasDetails = await getGasDetailsForTransaction(
    TEST_NETWORK,
    gasEstimate,
    true,
    wallet
  );

  const overallBatchMinGasPrice = await calculateGasPrice(
    transactionGasDetails
  );

  // populate tx

  const transaction = await erc20UnshieldPopulateTransaction(
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc20AmountRecipients,
    overallBatchMinGasPrice,
    transactionGasDetails
  );

  console.log("ERC20 UNSHIELD transaction", transaction);
  // send unshield tx
};
```

Shield ERC-721

```typescript
import {
  NETWORK_CONFIG,
  TXIDVersion,
  type NetworkName,
  type RailgunNFTAmountRecipient,
} from "@railgun-community/shared-models";
import {
  gasEstimateForShield,
  populateShield,
} from "@railgun-community/wallet";
import { type Wallet, type HDNodeWallet, Contract, ZeroAddress } from "ethers";
import {
  getGasDetailsForTransaction,
  getShieldSignature,
  serializeERC721Transfer,
} from "../util";
import {
  TEST_NETWORK,
  TEST_NFT_ADDRESS,
  TEST_NFT_SUBID,
} from "../../utils/0_constants";
import { getProviderWallet } from "../../utils/1_provider";

export const erc721ShieldGasEstimate = async (
  network: NetworkName,
  wallet: Wallet | HDNodeWallet,
  erc721AmountRecipients: RailgunNFTAmountRecipient[]
) => {
  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,
    [],
    erc721AmountRecipients, // nftAmountRecipients
    fromWalletAddress
  );

  return gasEstimate;
};

export const erc721PopulateShieldTransaction = async (
  network: NetworkName,
  wallet: Wallet | HDNodeWallet,
  erc721AmountRecipients: RailgunNFTAmountRecipient[],
  sendWithPublicWallet: boolean
) => {
  const spender = NETWORK_CONFIG[network].proxyContract;

  for (const amountRecipient of erc721AmountRecipients) {
    const contract = new Contract(
      amountRecipient.nftAddress,
      [
        "function getApproved(uint256 tokenId) view returns (address)",
        "function approve(address to, uint256 tokenId) external returns (bool)",
      ],
      wallet
    );
    const allowance = await contract.getApproved(amountRecipient.tokenSubID);
    if (allowance === ZeroAddress) {
      throw new Error("ERC721 Not minted.");
    }
    if (allowance.toLowerCase() == spender.toLowerCase()) {
      console.log("already have enough allowance");
      continue;
    }

    const tx = await contract.approve(spender, amountRecipient.tokenSubID);
    await tx.wait();
  }

  const gasEstimate = await erc721ShieldGasEstimate(
    network,
    wallet,
    erc721AmountRecipients
  );

  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,
    [],
    erc721AmountRecipients,
    gasDetails
  );

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

export const TEST_shieldERC721 = async (railgunWalletAddress: string) => {
  const { wallet } = getProviderWallet();

  const nftAddress = TEST_NFT_ADDRESS;
  const tokenSubID = TEST_NFT_SUBID;
  const erc721AmountRecipients = [
    serializeERC721Transfer(nftAddress, tokenSubID, railgunWalletAddress),
  ];

  const { gasEstimate, gasDetails, transaction, nullifiers } =
    await erc721PopulateShieldTransaction(
      TEST_NETWORK,
      wallet,
      erc721AmountRecipients,
      true
    );

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

Transfer ERC-721

```typescript
import {
  calculateGasPrice,
  TXIDVersion,
  type NetworkName,
  type RailgunERC20AmountRecipient,
  type RailgunNFTAmountRecipient,
  type RailgunWalletInfo,
  type TransactionGasDetails,
} from "@railgun-community/shared-models";
import {
  getGasDetailsForTransaction,
  getOriginalGasDetailsForTransaction,
  serializeERC721Transfer,
} from "../util";
import {
  gasEstimateForUnprovenTransfer,
  generateTransferProof,
  populateProvedTransfer,
} from "@railgun-community/wallet";
import {
  TEST_NETWORK,
  TEST_NFT_ADDRESS,
  TEST_NFT_SUBID,
} from "../../utils/0_constants";
import { getProviderWallet } from "../../utils/1_provider";

export const erc721PrivateTransferGasEstimate = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  erc721AmountRecipients: RailgunNFTAmountRecipient[],
  sendWithPublicWallet: boolean = true,
  feeTokenDetails: RailgunERC20AmountRecipient | undefined = undefined,
  memoText: string | undefined = undefined
) => {
  const originalGasDetails = await getOriginalGasDetailsForTransaction(
    network,
    sendWithPublicWallet
  );
  console.log("originalGasDetails: ", originalGasDetails);

  const { gasEstimate } = await gasEstimateForUnprovenTransfer(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    memoText,
    [],
    erc721AmountRecipients,
    originalGasDetails,
    feeTokenDetails,
    sendWithPublicWallet
  );

  return gasEstimate;
};

export const erc721PrivateTransferGenerateProof = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  erc721AmountRecipients: RailgunNFTAmountRecipient[],
  overallBatchMinGasPrice: bigint,
  showSenderAddressToRecipient: boolean = true,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined,
  memoText: string | undefined = undefined
) => {
  const progressCallback = (progress: number) => {
    // Handle proof progress (show in UI).
    // Proofs can take 20-30 seconds on slower devices.
    console.log("Private ERC721 Transfer Proof progress: ", progress);
  };

  await generateTransferProof(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    showSenderAddressToRecipient,
    memoText,
    [],
    erc721AmountRecipients,
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    progressCallback
  );
};

export const erc721PrivateTransferPopulateTransaction = async (
  network: NetworkName,
  railgunWalletID: string,
  erc721AmountRecipients: RailgunNFTAmountRecipient[],
  overallBatchMinGasPrice: bigint,
  transactionGasDetails: TransactionGasDetails,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined,
  showSenderAddressToRecipient: boolean = true,
  memoText: string | undefined = undefined
) => {
  const populateResponse = await populateProvedTransfer(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    showSenderAddressToRecipient,
    memoText,
    [], // erc20AmountRecipients
    erc721AmountRecipients,
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    transactionGasDetails
  );

  return populateResponse;
};

export const TEST_ERC721PrivateTransfer = async (
  encryptionKey: string,
  railgunWalletInfo: RailgunWalletInfo
) => {
  const { wallet } = getProviderWallet();

  const nftAddress = TEST_NFT_ADDRESS;
  const tokenSubID = TEST_NFT_SUBID;
  const erc721AmountRecipients = [
    serializeERC721Transfer(
      nftAddress,
      tokenSubID,
      railgunWalletInfo.railgunAddress
    ),
  ];

  const gasEstimate = await erc721PrivateTransferGasEstimate(
    encryptionKey,
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc721AmountRecipients
  );

  console.log("Private ERC721 TX gasEstimate: ", gasEstimate);

  await erc721PrivateTransferGenerateProof(
    encryptionKey,
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc721AmountRecipients,
    1n
  );

  const transactionGasDetails = await getGasDetailsForTransaction(
    TEST_NETWORK,
    gasEstimate,
    true,
    wallet
  );

  const overallBatchMinGasPrice = calculateGasPrice(transactionGasDetails);

  const transaction = await erc721PrivateTransferPopulateTransaction(
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc721AmountRecipients,
    overallBatchMinGasPrice,
    transactionGasDetails
  );
  console.log("ERC721 transaction: ", transaction);
  // send private ERC721 tx
};
```

Unshield ERC-721

```typescript
import {
  calculateGasPrice,
  TXIDVersion,
  type NetworkName,
  type RailgunERC20AmountRecipient,
  type RailgunNFTAmountRecipient,
  type RailgunWalletInfo,
  type TransactionGasDetails,
} from "@railgun-community/shared-models";
import {
  getGasDetailsForTransaction,
  getOriginalGasDetailsForTransaction,
  serializeERC721Transfer,
} from "../util";
import {
  gasEstimateForUnprovenUnshield,
  generateUnshieldProof,
  populateProvedUnshield,
} from "@railgun-community/wallet";
import {
  TEST_NETWORK,
  TEST_NFT_ADDRESS,
  TEST_NFT_SUBID,
} from "../../utils/0_constants";
import { getProviderWallet } from "../../utils/1_provider";

export const erc721UnshieldGasEstimate = async (
  network: NetworkName,
  railgunWalletID: string,
  encryptionKey: string,
  erc721AmountRecipients: RailgunNFTAmountRecipient[]
) => {
  const sendWithPublicWallet = true;
  const originalGasDetails = await getOriginalGasDetailsForTransaction(
    network,
    sendWithPublicWallet
  );
  // dont setup broadcaster connection for simplicity.
  const feeTokenDetails = undefined;
  console.log("unshield originalGasDetails: ", originalGasDetails);
  const { gasEstimate } = await gasEstimateForUnprovenUnshield(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    [], // erc20AmountRecipients
    erc721AmountRecipients,
    originalGasDetails,
    feeTokenDetails,
    sendWithPublicWallet
  );

  return gasEstimate;
};

export const erc721UnshieldGenerateProof = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  erc721AmountRecipients: RailgunNFTAmountRecipient[],
  overallBatchMinGasPrice: bigint,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined
) => {
  const progressCallback = (progress: number) => {
    // Handle proof progress (show in UI).
    // Proofs can take 20-30 seconds on slower devices.
    console.log("Unshield ERC721 Proof progress: ", progress);
  };

  await generateUnshieldProof(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    [],
    erc721AmountRecipients, // nft amount recipients
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    progressCallback
  );
};

export const erc721UnshieldPopulateTransaction = async (
  network: NetworkName,
  railgunWalletID: string,
  erc721AmountRecipients: RailgunNFTAmountRecipient[],
  overallBatchMinGasPrice: bigint,
  transactionGasDetails: TransactionGasDetails,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined
) => {
  const populateResponse = await populateProvedUnshield(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    [], // erc20AmountRecipients
    erc721AmountRecipients,
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    transactionGasDetails
  );

  return populateResponse;
};

export const TEST_ERC721Unshield = async (
  encryptionKey: string,
  railgunWalletInfo: RailgunWalletInfo
) => {
  console.log("TEST_ERC721Unshield");
  const { wallet } = getProviderWallet();
  const erc721AmountRecipients: RailgunNFTAmountRecipient[] = [
    serializeERC721Transfer(TEST_NFT_ADDRESS, TEST_NFT_SUBID, wallet.address),
  ];

  const gasEstimate = await erc721UnshieldGasEstimate(
    TEST_NETWORK,
    railgunWalletInfo.id,
    encryptionKey,
    erc721AmountRecipients
  );

  console.log("ERC721 UNSHIELD gasEstimate");

  // generate erc721 unshield proof
  await erc721UnshieldGenerateProof(
    encryptionKey,
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc721AmountRecipients,
    1n
  );

  const transactionGasDetails = await getGasDetailsForTransaction(
    TEST_NETWORK,
    gasEstimate,
    true,
    wallet
  );

  const overallBatchMinGasPrice = await calculateGasPrice(
    transactionGasDetails
  );

  const transaction = await erc721UnshieldPopulateTransaction(
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc721AmountRecipients,
    overallBatchMinGasPrice,
    transactionGasDetails
  );

  console.log("ERC721 UNSHIELD transaction", transaction);
  // send unshield tx
};
```

Cross Contract

```typescript
// PRIVATE ERC20 TRANSFER FUNCTIONALITY

import {
  calculateGasPrice,
  NETWORK_CONFIG,
  NetworkName,
  TXIDVersion,
  type RailgunERC20Amount,
  type RailgunERC20AmountRecipient,
  type RailgunERC20Recipient,
  type RailgunNFTAmount,
  type RailgunNFTAmountRecipient,
  type RailgunWalletInfo,
  type TransactionGasDetails,
} from "@railgun-community/shared-models";
import {
  gasEstimateForUnprovenCrossContractCalls,
  generateCrossContractCallsProof,
  populateProvedCrossContractCalls,
} from "@railgun-community/wallet";
import {
  getGasDetailsForTransaction,
  getOriginalGasDetailsForTransaction,
  serializeERC20RelayAdaptUnshield,
  serializeERC20Transfer,
} from "../util";
import { TEST_NETWORK, TEST_TOKEN } from "../../utils/0_constants";
import { getProviderWallet } from "../../utils/1_provider";
import { Contract, type ContractTransaction } from "ethers";

export const crossContractGasEstimate = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  erc20AmountUnshieldAmounts: RailgunERC20Amount[],
  erc721AmountUnshieldAmounts: RailgunNFTAmount[],
  erc20ShieldRecipients: RailgunERC20Recipient[],
  erc721AmountShieldRecipients: RailgunNFTAmountRecipient[],
  crossContractCalls: ContractTransaction[],
  minGasLimit: bigint, // provided by user, or cookbook output.
  sendWithPublicWallet: boolean = true,
  feeTokenDetails: RailgunERC20AmountRecipient | undefined = undefined
) => {
  const originalGasDetails = await getOriginalGasDetailsForTransaction(
    network,
    sendWithPublicWallet
  );
  console.log("CrossContract: originalGasDetails: ", originalGasDetails);
  const { gasEstimate } = await gasEstimateForUnprovenCrossContractCalls(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    erc20AmountUnshieldAmounts,
    erc721AmountUnshieldAmounts,
    erc20ShieldRecipients,
    erc721AmountShieldRecipients,
    crossContractCalls,
    originalGasDetails,
    feeTokenDetails,
    sendWithPublicWallet,
    minGasLimit
  );
  return gasEstimate;
};

export const crossContractGenerateProof = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  erc20AmountUnshieldAmounts: RailgunERC20Amount[],
  erc721AmountUnshieldAmounts: RailgunNFTAmount[],
  erc20AmountShieldRecipients: RailgunERC20Recipient[],
  erc721AmountShieldRecipients: RailgunNFTAmountRecipient[],
  crossContractCalls: ContractTransaction[],
  overallBatchMinGasPrice: bigint,
  minGasLimit: bigint,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined
) => {
  const progressCallback = (progress: number) => {
    // Handle proof progress (show in UI).
    // Proofs can take 20-30 seconds on slower devices.
    console.log("CrossContract Call Proof progress: ", progress);
  };
  // GENERATES RAILGUN SPENDING PROOF
  await generateCrossContractCallsProof(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    erc20AmountUnshieldAmounts,
    erc721AmountUnshieldAmounts,
    erc20AmountShieldRecipients,
    erc721AmountShieldRecipients,
    crossContractCalls,
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    minGasLimit,
    progressCallback
  );
};

export const crossContractCallsPopulateTransaction = async (
  network: NetworkName,
  railgunWalletID: string,
  erc20AmountUnshieldAmounts: RailgunERC20Amount[],
  erc721AmountUnshieldAmounts: RailgunNFTAmount[],
  erc20AmountShieldRecipients: RailgunERC20Recipient[],
  erc721AmountShieldRecipients: RailgunNFTAmountRecipient[],
  crossContractCalls: ContractTransaction[],
  transactionGasDetails: TransactionGasDetails,
  overallBatchMinGasPrice: bigint,
  sendWithPublicWallet: boolean = true,

  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined
) => {
  const populateResponse = await populateProvedCrossContractCalls(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    erc20AmountUnshieldAmounts,
    erc721AmountUnshieldAmounts,
    erc20AmountShieldRecipients,
    erc721AmountShieldRecipients,
    crossContractCalls,
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    transactionGasDetails
  );

  return populateResponse;
};

export const TEST_CrossContractCalls = async (
  encryptionKey: string,
  railgunWalletInfo: RailgunWalletInfo
) => {
  console.log("TEST_CrossContractCalls");
  const { wallet, provider } = getProviderWallet();
  // get gas estimate,
  // generate proof,
  // populate tx

  const TEST_AMOUNT = 1n;

  const erc20AmountUnshieldAmounts: RailgunERC20Amount[] = [
    serializeERC20RelayAdaptUnshield(
      TEST_TOKEN, // WETH
      TEST_AMOUNT
    ),
  ];

  const erc20AmountShieldRecipients: RailgunERC20Recipient[] = [
    serializeERC20Transfer(
      TEST_TOKEN, // WETH
      1n,
      railgunWalletInfo.railgunAddress
    ),
  ];
  const { wrappedAddress } = NETWORK_CONFIG[TEST_NETWORK].baseToken;
  const wethContract = new Contract(
    wrappedAddress,
    [
      "function deposit() payable",
      "function withdraw(uint256)",
      "function balanceOf(address) view returns (uint256)",
    ],
    provider
  );

  const unwrap = await wethContract.withdraw.populateTransaction(TEST_AMOUNT);
  const crossContractCalls: ContractTransaction[] = [
    {
      to: unwrap.to,
      data: unwrap.data, // unwrapeth
      value: 0n,
    },
  ];

  const minGasLimit = 2_500_000n; // high estimate but should be enough.
  // const overallBatchMinGasPrice = 1n;
  const gasEstimate = await crossContractGasEstimate(
    encryptionKey,
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc20AmountUnshieldAmounts,
    [],
    erc20AmountShieldRecipients,
    [],
    crossContractCalls,
    minGasLimit,
    true
  );

  console.log("Private CrossContract TX gasEstimate: ", gasEstimate);

  const transactionGasDetails = await getGasDetailsForTransaction(
    TEST_NETWORK,
    gasEstimate,
    true,
    wallet
  );
  const overallBatchMinGasPrice = calculateGasPrice(transactionGasDetails);

  // generate proof
  await crossContractGenerateProof(
    encryptionKey,
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc20AmountUnshieldAmounts,
    [],
    erc20AmountShieldRecipients,
    [],
    crossContractCalls,
    overallBatchMinGasPrice,
    minGasLimit,
    true
  );

  // populate tx
  const transaction = await crossContractCallsPopulateTransaction(
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc20AmountUnshieldAmounts,
    [],
    erc20AmountShieldRecipients,
    [],
    crossContractCalls,
    transactionGasDetails,
    overallBatchMinGasPrice,
    true
  );
  console.log("CrossContractCall transaction: ", transaction);
  // send private ERC20 tx.
};
```

Approvals - Universal

```typescript
import {
  NETWORK_CONFIG,
  type NetworkName,
  type RailgunERC20AmountRecipient,
  type RailgunNFTAmountRecipient,
} from "@railgun-community/shared-models";
import { Contract, ZeroAddress, type HDNodeWallet, type Wallet } from "ethers";

export type ApprovalInputs = {
  network: NetworkName;
  wallet: Wallet | HDNodeWallet;
  erc20AmountRecipients: RailgunERC20AmountRecipient[] | undefined;
  erc721AmountRecipients: RailgunNFTAmountRecipient[] | undefined;
};

export const ERC20ApprovalAbi = [
  "function allowance(address owner, address spender) view returns (uint256)",
  "function approve(address spender, uint256 amount) external returns (bool)",
];

export const ERC721ApprovalAbi = [
  "function getApproved(uint256 tokenId) view returns (address)",
  "function approve(address to, uint256 tokenId) external returns (bool)",
];

export const handleApprovals = async (inputs: ApprovalInputs) => {
  const { wallet, network, erc20AmountRecipients, erc721AmountRecipients } =
    inputs;
  const spender = NETWORK_CONFIG[network].proxyContract;

  if (erc20AmountRecipients != null) {
    for (const amountRecipient of erc20AmountRecipients) {
      const contract = new Contract(
        amountRecipient.tokenAddress,
        ERC20ApprovalAbi,
        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();
    }
  }

  if (erc721AmountRecipients != null) {
    for (const amountRecipient of erc721AmountRecipients) {
      const contract = new Contract(
        amountRecipient.nftAddress,
        ERC721ApprovalAbi,
        wallet
      );
      const allowance = await contract.getApproved(amountRecipient.tokenSubID);
      if (allowance === ZeroAddress) {
        throw new Error("ERC721 Not minted.");
      }
      if (allowance.toLowerCase() == spender.toLowerCase()) {
        console.log("already have enough allowance");
        continue;
      }

      const tx = await contract.approve(spender, amountRecipient.tokenSubID);
      await tx.wait();
    }
  }
};
```

Gas - Universal

```typescript
import {
  TXIDVersion,
  type FeeTokenDetails,
  type NetworkName,
  type RailgunERC20AmountRecipient,
  type RailgunNFTAmountRecipient,
  type TransactionGasDetails,
} from "@railgun-community/shared-models";
import { getOriginalGasDetailsForTransaction } from "../util";
import {
  gasEstimateForShield,
  gasEstimateForShieldBaseToken,
  gasEstimateForUnprovenTransfer,
  gasEstimateForUnprovenUnshield,
} from "@railgun-community/wallet";

export type GasEstimateInputs = {
  network: NetworkName;
  encryptionKey: string;
  railgunWalletID: string;
  erc20AmountRecipients: RailgunERC20AmountRecipient[] | undefined;
  erc721AmountRecipients: RailgunNFTAmountRecipient[] | undefined;
  originalGasDetails: TransactionGasDetails;
  feeTokenDetails: RailgunERC20AmountRecipient | undefined;
  sendWithPublicWallet: boolean;
};

export type PrivateTransferGasEstimateInputs = GasEstimateInputs & {
  memoText: string | undefined;
};

export type ERC20PrivateTransferGasEstimateInputs =
  PrivateTransferGasEstimateInputs & {
    erc20AmountRecipients: RailgunERC20AmountRecipient[];
  };

export type ERC721PrivateTransferGasEstimateInputs =
  PrivateTransferGasEstimateInputs & {
    erc721AmountRecipients: RailgunNFTAmountRecipient[];
  };

export type ShieldGasEstimateInputs = {
  network: NetworkName;
  shieldPrivateKey: string;
  erc20AmountRecipients: RailgunERC20AmountRecipient[] | undefined;
  erc721AmountRecipients: RailgunNFTAmountRecipient[] | undefined;
  fromWalletAddress: string;
};

export type BaseTokenShieldGasEstimateInputs = {
  network: NetworkName;
  shieldPrivateKey: string;
  erc20AmountRecipient: RailgunERC20AmountRecipient;
  railgunWalletAddress: string;
  fromWalletAddress: string;
};

export type RGShieldGasEstimateInputs =
  | ShieldGasEstimateInputs
  | BaseTokenShieldGasEstimateInputs;

export type UnshieldGasEstimateInputs = GasEstimateInputs;

export const unshieldGasEstimate = async (
  inputs: UnshieldGasEstimateInputs
) => {
  const {
    network,
    railgunWalletID,
    encryptionKey,
    erc20AmountRecipients,
    erc721AmountRecipients,
    originalGasDetails,
    feeTokenDetails,
    sendWithPublicWallet,
  } = inputs;

  const { gasEstimate } = await gasEstimateForUnprovenUnshield(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    erc20AmountRecipients ?? [],
    erc721AmountRecipients ?? [],
    originalGasDetails,
    feeTokenDetails,
    sendWithPublicWallet
  );

  return gasEstimate;
};

export const shieldGasEstimate = async (inputs: ShieldGasEstimateInputs) => {
  const {
    network,
    shieldPrivateKey,
    erc20AmountRecipients,
    erc721AmountRecipients,
    fromWalletAddress,
  } = inputs;

  const { gasEstimate } = await gasEstimateForShield(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    shieldPrivateKey,
    erc20AmountRecipients ?? [],
    erc721AmountRecipients ?? [],
    fromWalletAddress
  );
  return gasEstimate;
};

export const allShieldGasEstimate = async (
  inputs: ShieldGasEstimateInputs | BaseTokenShieldGasEstimateInputs
) => {
  const { network, shieldPrivateKey, fromWalletAddress } = inputs;
  if ("erc20AmountRecipient" in inputs) {
    const { erc20AmountRecipient, railgunWalletAddress } = inputs;
    const { gasEstimate } = await gasEstimateForShieldBaseToken(
      TXIDVersion.V2_PoseidonMerkle,
      network,
      railgunWalletAddress,
      shieldPrivateKey,
      erc20AmountRecipient,
      fromWalletAddress
    );
    return gasEstimate;
  } else {
    const { erc20AmountRecipients, erc721AmountRecipients } = inputs;
    const { gasEstimate } = await gasEstimateForShield(
      TXIDVersion.V2_PoseidonMerkle,
      network,
      shieldPrivateKey,
      erc20AmountRecipients ?? [],
      erc721AmountRecipients ?? [],
      fromWalletAddress
    );
    return gasEstimate;
  }
};

export const privateTransferGasEstimate = async (
  inputs: PrivateTransferGasEstimateInputs
) => {
  const {
    network,
    encryptionKey,
    sendWithPublicWallet,
    railgunWalletID,
    memoText,
    erc20AmountRecipients,
    erc721AmountRecipients,
    feeTokenDetails,
  } = inputs;

  const originalGasDetails = await getOriginalGasDetailsForTransaction(
    network,
    sendWithPublicWallet
  );
  console.log("originalGasDetails: ", originalGasDetails);

  const { gasEstimate } = await gasEstimateForUnprovenTransfer(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    memoText,
    erc20AmountRecipients ?? [],
    erc721AmountRecipients ?? [],
    originalGasDetails,
    feeTokenDetails,
    sendWithPublicWallet
  );

  return gasEstimate;
};
```

Gas - Populate

```typescript
import {
  TXIDVersion,
  type NetworkName,
  type RailgunERC20AmountRecipient,
  type RailgunNFTAmountRecipient,
  type RailgunPopulateTransactionResponse,
  type TransactionGasDetails,
} from "@railgun-community/shared-models";
import {
  populateProvedTransfer,
  populateProvedUnshield,
  populateShield,
} from "@railgun-community/wallet";
import { handleApprovals } from "./0_approvals";
import type { Wallet, HDNodeWallet } from "ethers";
import { shieldGasEstimate } from "./1_gas";
import { getGasDetailsForTransaction, getShieldSignature } from "../util";

export type PrivateTransferPopulateInputs = {
  network: NetworkName;
  railgunWalletID: string;
  overallBatchMinGasPrice: bigint;
  transactionGasDetails: TransactionGasDetails;
  sendWithPublicWallet: boolean;
  erc20AmountRecipients: RailgunERC20AmountRecipient[] | undefined;
  erc721AmountRecipients: RailgunNFTAmountRecipient[] | undefined;
  broadcasterFeeERC20AmountRecipient: RailgunERC20AmountRecipient | undefined;
  showSenderAddressToRecipient: boolean;
  memoText: string | undefined;
};

export type ERC20PrivateTransferPopulateInputs =
  PrivateTransferPopulateInputs & {
    erc20AmountRecipients: RailgunERC20AmountRecipient[];
  };
export type ERC721PrivateTransferPopulateInputs =
  PrivateTransferPopulateInputs & {
    erc721AmountRecipients: RailgunNFTAmountRecipient[];
  };

export type UnshieldPopulateInputs = {
  network: NetworkName;
  railgunWalletID: string;
  erc20AmountRecipients: RailgunERC20AmountRecipient[] | undefined;
  erc721AmountRecipients: RailgunNFTAmountRecipient[] | undefined;
  overallBatchMinGasPrice: bigint;
  transactionGasDetails: TransactionGasDetails;
  sendWithPublicWallet: boolean;
  broadcasterFeeERC20AmountRecipient: RailgunERC20AmountRecipient | undefined;
};

export type ShieldPopulateInputs = {
  network: NetworkName;
  wallet: Wallet | HDNodeWallet;
  erc20AmountRecipients: RailgunERC20AmountRecipient[] | undefined;
  erc721AmountRecipients: RailgunNFTAmountRecipient[] | undefined;
};

export const privateTransferPopulateTransaction = async (
  inputs: PrivateTransferPopulateInputs
): Promise<RailgunPopulateTransactionResponse> => {
  const {
    network,
    railgunWalletID,
    showSenderAddressToRecipient,
    memoText,
    erc20AmountRecipients,
    erc721AmountRecipients,
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    transactionGasDetails,
  } = inputs;
  const populateResponse = await populateProvedTransfer(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    showSenderAddressToRecipient,
    memoText,
    erc20AmountRecipients ?? [],
    erc721AmountRecipients ?? [],
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    transactionGasDetails
  );
  return populateResponse;
};

export const unshieldPopulateTransaction = async (
  inputs: UnshieldPopulateInputs
) => {
  const {
    network,
    railgunWalletID,
    erc20AmountRecipients,
    erc721AmountRecipients,
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    transactionGasDetails,
  } = inputs;
  const populateResponse = await populateProvedUnshield(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    erc20AmountRecipients ?? [],
    erc721AmountRecipients ?? [], // nftAmountRecipients
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    transactionGasDetails
  );

  return populateResponse;
};

export const shieldPopulateTransaction = async (
  inputs: ShieldPopulateInputs
) => {
  const { network, erc20AmountRecipients, erc721AmountRecipients, wallet } =
    inputs;
  const shieldPrivateKey = await getShieldSignature(wallet);
  const sendWithPublicWallet = true;

  await handleApprovals({
    network,
    wallet,
    erc20AmountRecipients,
    erc721AmountRecipients,
  });

  const gasEstimate = await shieldGasEstimate({
    erc20AmountRecipients,
    erc721AmountRecipients,
    network,
    shieldPrivateKey,
    fromWalletAddress: wallet.address,
  });

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

  const { transaction, nullifiers } = await populateShield(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    shieldPrivateKey,
    erc20AmountRecipients ?? [],
    erc721AmountRecipients ?? [],
    transactionGasDetails
  );
  return {
    gasEstimate,
    transactionGasDetails,
    transaction,
    nullifiers,
  };
};
```

Proof - Universal

```typescript
import {
  TXIDVersion,
  type NetworkName,
  type RailgunERC20AmountRecipient,
  type RailgunNFTAmountRecipient,
} from "@railgun-community/shared-models";
import {
  generateTransferProof,
  generateUnshieldProof,
} from "@railgun-community/wallet";

export type GenerateProofInputs = {
  encryptionKey: string;
  network: NetworkName;
  railgunWalletID: string;
  erc20mountRecipients: RailgunERC20AmountRecipient[] | undefined;
  erc721mountRecipients: RailgunNFTAmountRecipient[] | undefined;
  overallBatchMinGasPrice: bigint;
  sendWithPublicWallet: boolean;
  broadcasterFeeERC20AmountRecipient: RailgunERC20AmountRecipient | undefined;
};

export type PrivateTransferGenerateProofInputs = GenerateProofInputs & {
  showSenderAddressToRecipient: boolean;
  memoText: string | undefined;
};

export type UnshieldGenerateProofInputs = GenerateProofInputs;

export const privateTransferGenerateProof = async (
  inputs: PrivateTransferGenerateProofInputs
) => {
  const progressCallback = (progress: number) => {
    // Handle proof progress (show in UI).
    // Proofs can take 20-30 seconds on slower devices.
    console.log("Private Transfer Proof progress: ", progress);
  };

  const {
    network,
    railgunWalletID,
    encryptionKey,
    showSenderAddressToRecipient,
    memoText,
    erc20mountRecipients,
    erc721mountRecipients,
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
  } = inputs;

  await generateTransferProof(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    showSenderAddressToRecipient,
    memoText,
    erc20mountRecipients ?? [],
    erc721mountRecipients ?? [],
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    progressCallback
  );
};

export const unshieldGenerateProof = async (
  inputs: UnshieldGenerateProofInputs
) => {
  const progressCallback = (progress: number) => {
    // Handle proof progress (show in UI).
    // Proofs can take 20-30 seconds on slower devices.
    console.log("Unshield Proof progress: ", progress);
  };

  const {
    network,
    railgunWalletID,
    encryptionKey,
    erc20mountRecipients,
    erc721mountRecipients,
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
  } = inputs;
  await generateUnshieldProof(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    erc20mountRecipients ?? [],
    erc721mountRecipients ?? [],
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    progressCallback
  );
};
```


# Getting Started

How to set up RAILGUN Wallet SDK for your crypto wallet

### Installation

You may install the Wallet SDK and Shared Models with `npm` or `yarn`. We are currently making major improvements to the latest versions of the SDKs. For now, please use the stable versions listed below.

```bash
npm install @railgun-community/wallet@10.4.0 @railgun-community/shared-models@7.6.1 ethers@6.13.1 snarkjs @types/snarkjs
```

*-or-*

```bash
yarn add @railgun-community/wallet@10.4.0 @railgun-community/shared-models@7.6.1 ethers@6.13.1 snarkjs @types/snarkjs
```

### Next steps

In the following steps, we'll cover Wallet SDK initialization:

1. [Setting Up Environment Constants](/developer-guide/wallet/getting-started/1.-setting-up-environment-constants)&#x20;
2. [Setting up networks and RPC providers](/developer-guide/wallet/getting-started/2.-setting-up-networks-and-rpc-providers)
3. [Set up Database](/developer-guide/wallet/getting-started/3.-set-up-database)
4. [Build a persistent store for artifact downloads](/developer-guide/wallet/getting-started/4.-build-a-persistent-store-for-artifact-downloads)
5. [Start the RAILGUN Privacy Engine](/developer-guide/wallet/getting-started/5.-start-the-railgun-privacy-engine)
6. [Load the prover for each platform](/developer-guide/wallet/getting-started/6.-load-a-groth16-prover-for-each-platform)
7. [Set up a debug logger](/developer-guide/wallet/getting-started/7.-set-up-a-debug-logger)
8. [Connect engine network providers](/developer-guide/wallet/getting-started/8.-connect-engine-network-providers.)
9. [Set up Waku](/developer-guide/wallet/getting-started/9.-waku-optional)


# 1. Setting Up Environment Constants

Set up environment constants and variables by importing necessary modules and utilizing environment variables to ensure proper configuration.&#x20;

Key components include:

* **TEST\_NETWORK**: Specifies the network used for testing, currently set to Polygon.
* **TEST\_RPC\_URL**: Retrieves the RPC URL from an environment variable to connect to the RAILGUN test blockchain network.
* **TEST\_TOKEN**: Represents the wrapped address of the base token for the test network, derived from the network configuration.
* **TEST\_NFT\_ADDRESS and TEST\_NFT\_SUBID**: Provide identifiers for testing NFT functionality.
* **TEST\_MNEMONIC**: Defines a test mnemonic phrase for RAILGUN operations, using a default if not specified in the environment variables.
* **TEST\_ENCRYPTION\_KEY**: Offers a placeholder encryption key for development and testing, emphasizing its unsuitability for production.

```typescript
import { NETWORK_CONFIG, NetworkName } from "@railgun-community/shared-models";
import dotenv from "dotenv";
dotenv.config();

if (process.env.RAILGUN_TEST_RPC == null) {
  throw new Error("No TEST RPC URL configured.");
}

/**
 * The network used for testing purposes.
 * Currently set to the Polygon network.
 * @constant {NetworkName}
 */
export const TEST_NETWORK = NetworkName.Polygon;

/**
 * The RPC URL for RAILGUN test environment.
 * This constant retrieves the URL from the RAILGUN_TEST_RPC environment variable.
 * Used for connecting to the test blockchain network for RAILGUN operations.
 */
export const TEST_RPC_URL = `${process.env.RAILGUN_TEST_RPC}`;

/**
 * The address of the wrapped base token for the test network.
 * This constant is derived from the network configuration for the test network.
 * @type {string}
 */
export const TEST_TOKEN = NETWORK_CONFIG[TEST_NETWORK].baseToken.wrappedAddress;

/**
 * The Ethereum address of a test NFT (Non-Fungible Token) contract.
 * This constant is used for testing purposes related to NFT functionality.
 * The actual address is abbreviated as "0x....".
 */
export const TEST_NFT_ADDRESS = "0x....";

/**
 * The sub-identifier for the test NFT.
 * This value is used to uniquely identify a specific NFT within a collection for testing purposes.
 * @constant {string}
 */
export const TEST_NFT_SUBID = "1";

/**
 * Test mnemonic phrase for RAILGUN.
 * Uses the environment variable RAILGUN_TEST_MNEMONIC if available,
 * otherwise falls back to a default test mnemonic.
 * @remarks This should only be used for testing purposes.
 */
export const TEST_MNEMONIC =
  process.env.RAILGUN_TEST_MNEMONIC ??
  "test test test test test test test test test test test junk";

/**
 * A constant representing a test encryption key for development purposes.
 * This is a 64-character hexadecimal string (32 bytes) that can be used as a placeholder
 * or default encryption key in test environments.
 *
 * @remarks
 * Do not use this key in production environments as it is a publicly known value.
 */
export const TEST_ENCRYPTION_KEY =
  "0101010101010101010101010101010101010101010101010101010101010101";
```


# 2. Setting up networks and RPC providers

Next, you will need to load an RPC provider for each network/chain that you intend to run private transactions on.

The `loadProvider` function loads the RailgunProxyContract & RelayAdaptContract and returns the RAILGUN fees for the provided network.

Additionally, it no longer automatically kicks off a merkletree balance scan. This is necessary to know the private balances of the available RAILGUN wallets in the local database (set up in Step 1). Calling refreshBalances is required to 'update' balances. Implementation of a polling system has now been passed onto the 'consumer wallet' to prevent any performance bottlenecks.&#x20;

We will set up an `onMerkletreeScanCallback` function (to monitor the scan's progress and status) and an `onBalanceUpdateCallback` function (to get notifications of the private balances of each available wallet) in the [Balance and Sync Callbacks](/developer-guide/wallet/private-balances/balance-and-sync-callbacks) section.

We will discuss how to initiate additional merkletree scans to fetch latest private balances in the [Updating Balances](/developer-guide/wallet/private-balances/updating-balances) section.

To skip merkletree syncs, set `skipMerkletreeScans` to **true** in `startRailgunEngine` from Step 1.

```typescript
import { JsonRpcProvider, Wallet } from "ethers";
import { TEST_MNEMONIC, TEST_RPC_URL } from "./constants";

/**
 * Creates and returns a provider and wallet for JSON-RPC interactions.
 *
 * This utility function initializes a JSON-RPC provider with a test URL
 * and creates a wallet from a test mnemonic phrase. Both the provider
 * and wallet are returned as an object.
 *
 * @returns An object containing the initialized provider and wallet
 * @property {JsonRpcProvider} provider - The JSON-RPC provider instance
 * @property {Wallet} wallet - The wallet instance created from the test mnemonic
 *
 * @example
 * const { provider, wallet } = getProviderWallet();
 * // Now use provider for RPC calls and wallet for signing transactions
 */
export const getProviderWallet = () => {
  const provider = new JsonRpcProvider(TEST_RPC_URL);
  const wallet = Wallet.fromPhrase(TEST_MNEMONIC, provider);

  return {
    provider,
    wallet,
  };
};
```

The return value, `feesSerialized`, contains the following fields:&#x20;

* `deposit`: hex string, representing shielding fee in basis points. Current default is 25bp (0.25% fee).
* `withdraw`: hex string, representing unshielding fee in basis points. Current default is 25bp.
* `nft`: hex string, representing fee for NFT shielding/unshielding. Current default is 0bp.

Store these to inform the user of the RAILGUN fee associated with their transaction.


# 3. Set Up Database

Set up functions for different environments (Node.js, web browsers, and React Native) to manage LevelDB databases:

* **Node.js Setup**: Utilizes `leveldown` for creating databases. Use `createNodeDatabase(path)` to instantiate a LevelDown database at the given file path.

  ```typescript
  const db = createNodeDatabase('./path/to/database');
  ```
* **Web Browser Setup**: Leverages `level-js` for browser-based databases. Use `createWebDatabase(path)` to initialize a LevelDB database, offering basic functionality through the `LevelDB` instance.

  ```typescript
  const db = createWebDatabase('./data/mydb');
  ```
* **React Native Setup**: Employs `leveldown-nodejs-mobile` for creating databases on React Native platforms. Use the asynchronous `createNativeDatabase(path)` function, which returns a promise that resolves to a `LeveldownNodejsMobile` database instance.

  ```typescript
  const db = await createNativeDatabase('/path/to/database');

  ```

````typescript
// Setup for Node builds.
import LevelDownDB from "leveldown";
/**
 * Creates a LevelDown database instance at the specified path.
 *
 * @param dbLocationPath - The file system path where the database will be created
 * @returns A LevelDown database instance
 *
 * @example
 * ```typescript
 * const db = createNodeDatabase('./path/to/database');
 * ```
 */
export const createNodeDatabase = (dbLocationPath: string) => {
  console.log("Creating local database at path: ", dbLocationPath);
  const db = LevelDownDB(dbLocationPath);
  return db;
};

// Setup for Web builds.
import LevelDB from "level-js";
/**
 * Creates a new web database instance at the specified location path
 * @param dbLocationPath - The file system path where the database will be created
 * @returns A new LevelDB database instance
 * @remarks This function initializes a new LevelDB database at the specified path
 * and provides basic database functionality through the returned instance.
 * @example
 * const db = createWebDatabase('./data/mydb');
 */
export const createWebDatabase = (dbLocationPath: string) => {
  indexedDB.databases;
  console.log("Creating local database at path: ", dbLocationPath);
  const db = new LevelDB(dbLocationPath);
  return db;
};

// Setup for React Native builds.

// @ts-ignore
import LeveldownNodejsMobile from "leveldown-nodejs-mobile";
/**
 * Creates a native database instance using LeveldownNodejsMobile.
 *
 * @param dbLocationPath - The file system path where the database will be stored
 * @returns A Promise that resolves to a new LeveldownNodejsMobile database instance
 *
 * @example
 * ```typescript
 * const db = await createNativeDatabase('/path/to/database');
 * ```
 */
export const createNativeDatabase = async (dbLocationPath: string) => {
  return new LeveldownNodejsMobile(dbLocationPath);
};
````


# 4. Build a persistent store for artifact downloads

The RAILGUN proof generation system uses large artifact files. Because of the size and number of these files, which cover a variety of use cases, we recommend against packaging them directly into your application. This would increase the application size by 50MB+.

Instead, Wallet SDK will automatically download these artifact files when required and persist them for further use. The ArtifactStore module manages downloads and persistence of these assets.

As a dApp developer, you only need to provide a file storage mechanism that the ArtifactStore will use to 1) check file existence, 2) store assets, 3) retrieve assets.

````typescript
import { ArtifactStore } from "@railgun-community/wallet";
import fs from "fs";

/**
 * Creates a path for a download directory by joining a documents directory path
 * with a specified path.
 *
 * @param documentsDir - The base directory path for documents.
 * @param path - The specific path to append to the documents directory.
 * @returns A string representing the combined path.
 *
 * @example
 * ```typescript
 * const downloadDirPath = createDownloadDirPath('/path/to/documents', 'downloads');
 * console.log(downloadDirPath); // Output: '/path/to/documents/downloads'
 * ```
 */
const createDownloadDirPath = (documentsDir: string, path: string) => {
  return `${documentsDir}/${path}`;
};

/**
 * Creates an artifact store for managing file operations in a specified directory.
 *
 * @param documentsDir - The base directory path where artifacts will be stored
 * @returns An ArtifactStore instance with methods for file operations:
 *   - getFile: Reads a file from the artifact store
 *   - storeFile: Writes data to a file in the artifact store, creating directories as needed
 *   - fileExists: Checks if a file exists in the artifact store
 *
 * @example
 * ```typescript
 * const artifactStore = createArtifactStore('/path/to/documents');
 * const fileData = await artifactStore.getFile('path/to/file');
 * await artifactStore.storeFile('dir/name', 'dir/name/file.txt', 'file content');
 * const exists = await artifactStore.fileExists('path/to/file');
 * ```
 */
export const createArtifactStore = (documentsDir: string): ArtifactStore => {
  const getFile = async (path: string) => {
    return fs.promises.readFile(createDownloadDirPath(documentsDir, path));
  };

  const storeFile = async (
    dir: string,
    path: string,
    item: string | Uint8Array<ArrayBufferLike>
  ) => {
    await fs.promises.mkdir(createDownloadDirPath(documentsDir, dir), {
      recursive: true,
    });
    await fs.promises.writeFile(
      createDownloadDirPath(documentsDir, path),
      item
    );
  };

  const fileExists = (path: string): Promise<boolean> => {
    return new Promise((resolve) => {
      fs.promises
        .access(createDownloadDirPath(documentsDir, path))
        .then(() => resolve(true))
        .catch(() => resolve(false));
    });
  };

  return new ArtifactStore(getFile, storeFile, fileExists);
};
````

Use `createArtifactStore` when calling `startRailgunEngine` from Step 1.


# 5. Start the RAILGUN Privacy Engine

Please first see [Getting Started](/developer-guide/wallet/getting-started) for installation instructions.

The [RAILGUN Engine SDK](/developer-guide/engine/engine-overview) is a dependency of Wallet SDK. It contains complex scanning technologies, cryptography, transaction wrappers, and contract interfaces. Wallet SDK, in turn, simplifies and abstracts the intricate functionalities provided by this library.

The Engine requires a LevelDOWN compatible database for storing encrypted wallets, like `level-js`.

```
yarn add level-js @types/level-js
```

#### Initialize Engine

The `initializeEngine` function is responsible for setting up the engine with parameters related to wallet management, database storage, artifact handling, and Private Proof of Innocence (POI) verification.

**Key Components**

* **Wallet Source**: Specifies a name for your wallet implementation, which is encrypted and visible in the private transaction history. It defaults to "quickstart demo".
* **Database Storage**: Uses a LevelDOWN compatible database (`./engine.db` by default) for storing encrypted wallets. The `createNodeDatabase` function sets up this database.
* **Artifact Storage**: Manages persistent storage for downloading large artifact files, utilizing `createArtifactStore`.
* **POI Nodes**: An array of node URLs used to verify that funds are not from undesirable sources, with a default test aggregator node provided.
* **Engine Debugging**: Optionally forwards debug logs to a logger for development purposes.
* **Shutdown Handling**: Sets up a handler to properly stop the engine when the process is terminated, ensuring resources are cleanly released.

These configurations are adjustable by providing arguments when calling `initializeEngine`, tailoring it to specific application requirements.&#x20;

````typescript
// main.ts
import {
  startRailgunEngine,
  stopRailgunEngine,
} from "@railgun-community/wallet";
import { createNodeDatabase } from "./1_database";
import { createArtifactStore } from "./2_artifact-storage"; // We'll get to this in Step 2: Build a persistent store
import type { POIList } from "@railgun-community/shared-models";
import Level from "level-js";

/**
 * Initializes the RAILGUN engine with the specified configuration.
 *
 * This function sets up the RAILGUN privacy engine with necessary configurations for wallet management,
 * database storage, artifact handling, and Private Proof of Innocence (POI) verification.
 *
 * @remarks
 * - The engine requires a database for storing encrypted wallets
 * - Artifact files are downloaded and stored in a specified directory
 * - POI node URLs are used for verifying that funds are not from undesirable sources
 *
 * @returns A Promise that resolves when the engine is successfully initialized
 *
 * @example
 * ```typescript
 * await initializeEngine();
 * ```
 *
 * @see https://docs.railgun.org for more information about RAILGUN privacy system
 */
/**
 * Initializes the RAILGUN Engine with provided configuration.
 *
 * @param args - Optional initialization parameters
 * @param args.walletSource - Name for your wallet implementation. Maximum of 16 characters, lowercase.
 * @param args.dbPath - Path to LevelDOWN compatible database for storing encrypted wallets
 * @param args.artifactsPath - Persistent storage path for required Engine artifact files
 * @param args.ppoiNodes - Array of Private POI aggregator node URLs
 *
 * @remarks
 * - The engine requires a database for storing encrypted wallets
 * - Artifact files are downloaded and stored in a specified directory
 * - POI node URLs are used for verifying that funds are not from undesirable sources
 *
 * This function configures and starts the RAILGUN Engine with appropriate settings for
 * wallet identification, database storage, artifact management, and Private POI verification.
 * It also sets up a shutdown handler to properly stop the engine when the process is terminated.
 *
 * Private POI (Private Proof of Innocence) provides cryptographic assurance that funds
 * entering the RAILGUN smart contract are not from known undesirable sources. For more
 * information see: https://docs.railgun.org/wiki/assurance/private-proofs-of-innocence
 *
 * @returns Promise that resolves when the engine is initialized
 *
 * @example
 * ```typescript
 * await initializeEngine();
 * ```
 *
 * @see https://docs.railgun.org for more information about RAILGUN privacy system
 */
export const initializeEngine = async (args?: {
  walletSource?: string;
  dbPath?: string;
  artifactsPath?: string;
  ppoiNodes?: string[];
}): Promise<void> => {
  // Name for your wallet implementation.
  // Encrypted and viewable in private transaction history.
  // Maximum of 16 characters, lowercase.
  const walletSource = args?.walletSource ?? "quickstart demo";

  // LevelDOWN compatible database for storing encrypted wallets.
  const dbPath = args?.dbPath ?? "./engine.db";

  //   const db = new Level(dbPath);
  const db = createNodeDatabase(dbPath);

  // Whether to forward Engine debug logs to Logger.
  const shouldDebug = true;

  // Persistent store for downloading large artifact files required by Engine.
  const artifactPath = args?.artifactsPath ?? "artifacts-directory";
  const artifactStore = createArtifactStore(artifactPath);

  // Whether to download native C++ or web-assembly artifacts.
  // True for mobile. False for nodejs and browser.
  const useNativeArtifacts = false;

  // Whether to skip merkletree syncs and private balance scans.
  // Only set to TRUE in shield-only applications that don't
  // load private wallets or balances.
  const skipMerkletreeScans = false;

  // Array of aggregator node urls for Private Proof of Innocence (Private POI), in order of priority.
  // Only one is required. If multiple urls are provided, requests will fall back to lower priority aggregator nodes if primary request fails.
  // Please reach out in the RAILGUN builders groups for information on the public aggregator nodes run by the community.
  //
  // Private POI is a tool to give cryptographic assurance that funds
  // entering the RAILGUN smart contract are not from a known list
  // of transactions or actors considered undesirable by respective wallet providers.
  // For more information: https://docs.railgun.org/wiki/assurance/private-proofs-of-innocence
  // (additional developer information coming soon).

  // This is a public aggregator node run by the community. Depending on your requirements you may want to run your own node, or use a different node.
  const poiNodeURLs: string[] = args?.ppoiNodes ?? [
    "https://ppoi.fdi.network",
  ];

  // Add a custom list to check Proof of Innocence against.
  // Leave blank to use the default list for the aggregator node provided.
  const customPOILists: POIList[] = [];

  // Set to true if you would like to view verbose logs for private balance and TXID scans
  const verboseScanLogging = true;

  await startRailgunEngine(
    walletSource,
    db,
    shouldDebug,
    artifactStore,
    useNativeArtifacts,
    skipMerkletreeScans,
    poiNodeURLs,
    customPOILists,
    verboseScanLogging
  );
  process.on("SIGINT", async (sigint) => {
    console.log("EXIT DETECTED", sigint);
    await stopRailgunEngine();
    process.exit(0);
  });
};
````


# 6. Load a Groth16 prover for each platform

Once the RAILGUN Engine is initialized, you will set a Groth16 Prover.

***NOTE*****:** A Prover is only necessary for applications that intend to generate proofs, which are necessary to send private transactions (private transfers, private cross-contract calls, and unshielding). Shield-only applications can safely skip this step.

There is a distinct Groth16 prover for node/browser (snarkjs) and a different prover for mobile (native prover). Both provers generate Groth16 snarks using different assembly processes that are compatible with different platforms.

1. Node.js and browser uses a web assembly implementation: [snarkjs](https://github.com/iden3/snarkjs).
2. Mobile apps use a C++ implementation: [@railgun-community/native-prover](https://github.com/Railgun-Community/native-prover).

````typescript
import { getProver, SnarkJSGroth16 } from "@railgun-community/wallet";
import { groth16 } from "snarkjs";

/**
 * Sets up the Groth16 proving system for the Node.js environment.
 *
 * This function configures the Railgun prover to use the SnarkJS Groth16 implementation.
 * It should be called before performing any zero-knowledge proof operations in a Node.js context.
 *
 * @remarks
 * The function uses the `getProver()` method to obtain the current prover instance
 * and sets the SnarkJS Groth16 implementation on it.
 *
 * @returns A Promise that resolves when the setup is complete
 *
 * @example
 * ```typescript
 * await setupNodeGroth16();
 * // Now the Railgun prover is configured to use Groth16 in Node.js
 * ```
 */
export const setupNodeGroth16 = async (): Promise<void> => {
  // @ts-ignore
  getProver().setSnarkJSGroth16(groth16 as SnarkJSGroth16);
};
````


# 7. Set up a debug logger

Wallet SDK includes verbose logging which can be helpful for debugging. These logs are suppressed by default but can be forwarded to any logging implementation you desire.

```typescript
import { setLoggers } from "@railgun-community/wallet";

/**
 * Sets up custom loggers for the engine.
 *
 * This function configures the engine to use custom logging functions that
 * prefix messages with timestamps and appropriate labels.
 *
 * - For standard logs: Prefixes with "Engine log: [timestamp]"
 * - For error logs: Prefixes with "Engine error: [timestamp]"
 *
 * The configured loggers will output to the console using console.log and console.error.
 *
 * @example
 * // Set up custom engine loggers
 * setEngineLoggers();
 *
 */
export const setEngineLoggers = () => {
  const logMessage = (msg: any) => {
    console.log(`Engine log: ${new Date()} `, msg);
  };
  const logError = (_msg: any) => {
    // console.error(`Engine error: ${new Date()} `, msg);
  };

  setLoggers(logMessage, logError);
};
```


# 8. Connect engine network providers.

#### Loading Engine Provider

The `loadEngineProvider` function is designed to initialize a provider for the specified test network within a TypeScript application. By default, it utilizes a local development provider but offers flexibility to switch to custom URLs for external providers.

**Parameters:**

* `providerUrl`: Optional. The provider URL to be used. Defaults to `http://127.0.0.1:8600`.

**Functionality**

1. **Configuration:** The function constructs a `FallbackProviderJsonConfig`, setting up the network with a default or custom provider URL.

**Usage**

Use this function to load a network provider by adapting to local or external resources with ease:

```typescript
// Load with the default local provider
await loadEngineProvider();

// Load with a specified provider URL
await loadEngineProvider("http://my-custom-provider:8600");
```

The `getProviderInfo` function aids by structuring the provider's necessary details like priority and performance metrics, ensuring seamless provider management.

```typescript
import {
  FallbackProviderJsonConfig,
  NETWORK_CONFIG,
  NetworkName,
} from "@railgun-community/shared-models";
import { loadProvider } from "@railgun-community/wallet";
import { TEST_NETWORK } from "./utils/0_constants";

// forked function, replace with your own provider
/**
 * Loads an Engine provider with the specified URL.
 *
 * This function initializes a provider for the test network using either:
 * 1. A local development provider (default: http://127.0.0.1:8600)
 * 2. External providers that can be uncommented and configured
 *
 * The provider is configured with fallback options, priorities, and weights
 * to ensure reliable connectivity.
 *
 * @param providerUrl - The URL of the primary provider, defaults to "http://127.0.0.1:8600"
 * @returns A Promise that resolves when the provider is successfully loaded
 *
 * @example
 * // Load with default local provider
 * await loadEngineProvider();
 *
 * // Load with custom provider URL
 * await loadEngineProvider("http://my-custom-provider:8600");
 */
export const loadEngineProvider = async (
  providerUrl = "http://127.0.0.1:8600"
) => {
  const TEST_PROVIDERS_JSON: FallbackProviderJsonConfig = {
    chainId: NETWORK_CONFIG[TEST_NETWORK].chain.id,
    providers: [
      // The following is an example of how to use a forked setup

      getProviderInfo(providerUrl),
      // These should be uncommented and filled in with your preferred providers.
      // The above is for using FORK only, do not mix them.
      //   {
      //     provider: "https://cloudflare-eth.com/",
      //     priority: 3,
      //     weight: 2,
      //     maxLogsPerBatch: 1,
      //   },
      //   {
      //     provider: "https://rpc.ankr.com/eth",
      //     priority: 2,
      //     weight: 2,
      //     maxLogsPerBatch: 1,
      //   },
    ],
  };

  const pollingInterval = 1000 * 60 * 5; // 5 min

  await loadProvider(TEST_PROVIDERS_JSON, TEST_NETWORK, pollingInterval);
};

export const getProviderInfo = (providerUrl: string) => {
  return {
    provider: providerUrl,
    priority: 3,
    weight: 2,
    maxLogsPerBatch: 1,
  };
};
```


# 9. Waku (optional)

### Imports

```typescript
import {
  NETWORK_CONFIG,
  type CachedTokenFee,
  type FeeTokenDetails,
  type NetworkName,
  type SelectedBroadcaster,
  type TransactionGasDetails,
} from "@railgun-community/shared-models";
import { calculateBroadcasterFeeERC20Amount } from "@railgun-community/wallet";
const wakuModule = import("@railgun-community/waku-broadcaster-client-node");
// let wakuModule: any;

let WakuBroadcasterClient: any;
let BroadcasterTransaction: any;

let waku: any; 
```

### Setup Waku&#x20;

```typescript
export const initializeBroadcasters = async (network: NetworkName) => {
  if (!waku) {
    waku = await wakuModule;
  }
  WakuBroadcasterClient = waku.WakuBroadcasterClient;
  BroadcasterTransaction = waku.BroadcasterTransaction;

  const { chain } = NETWORK_CONFIG[network];
  const options = {};
  const callback = (chain: any, status: any) => {
    if (status !== "Connected") {
      console.log(`WAKU ${chain.id}:${chain.type} ${status}`);
    }
  };
  const debugLogger = {
    log: (_msg: string) => {
      console.log(msg);
    },
    error: (error: Error) => {
      console.error(error);
    },
  };
  WakuBroadcasterClient.start(chain, options, callback, debugLogger);
};

```

### Utility Functions

```typescript
export const getBroadcasterFeeInfo = async (
  network: NetworkName,
  tokenAddress: string
): Promise<SelectedBroadcaster> => {
  const { chain } = NETWORK_CONFIG[network];
  return await WakuBroadcasterClient.findBestBroadcaster(
    chain,
    tokenAddress,
    true
  );
};

// TODO: write out broadcaster transaction cost calculation function

export const getBroadcasterFeeRecipientDetails = async (
  selectedBroadcaster: SelectedBroadcaster,
  estimatedGasDetails: TransactionGasDetails,
  feeTokenDetails: FeeTokenDetails
) => {
  const broadcasterFeeAmountDetails = calculateBroadcasterFeeERC20Amount(
    feeTokenDetails,
    estimatedGasDetails
  );

  const broadcasterFeeERC20AmountRecipient = {
    tokenAddress: broadcasterFeeAmountDetails.tokenAddress,
    amount: broadcasterFeeAmountDetails.amount,
    recipientAddress: selectedBroadcaster.railgunAddress,
  };

  return broadcasterFeeERC20AmountRecipient;
};

export const getFeeTokenDetails = async (
  network: NetworkName,
  tokenAddress: string,
  sendWithPublicWallet: boolean = true
) => {
  if (sendWithPublicWallet) {
    return {
      broadcaster: undefined,
      feeTokenDetails: undefined,
    };
  }
  const broadcaster = await getBroadcasterFeeInfo(network, tokenAddress);
  const feeTokenDetails: FeeTokenDetails = {
    tokenAddress: broadcaster.tokenAddress,
    feePerUnitGas: BigInt(broadcaster.tokenFee.feePerUnitGas),
  };

  return {
    broadcaster,
    feeTokenDetails,
  };
};

export const getBroadcasterDetails = async (
  estimatedGasDetails: TransactionGasDetails,
  selectedBroadcaster: SelectedBroadcaster | undefined,
  feeTokenDetails: FeeTokenDetails | undefined
) => {
  if (!selectedBroadcaster || !feeTokenDetails) {
    return undefined;
  }
  // gets a broadcaster from the waku client based on tokenAddress

  // const { broadcaster, feeTokenDetails } = await getFeeTokenDetails(
  //   network,
  //   tokenAddress
  // );

  const feeRecipientDetails = await getBroadcasterFeeRecipientDetails(
    selectedBroadcaster,
    estimatedGasDetails,
    feeTokenDetails
  );

  const broadcasterFeeERC20AmountRecipient = {
    tokenAddress: feeRecipientDetails.tokenAddress,
    amount: feeRecipientDetails.amount,
    recipientAddress: feeRecipientDetails.recipientAddress,
  };

  return {
    selectedBroadcaster,
    broadcasterFeeERC20AmountRecipient,
  };
};

```


# Private Wallets

Create or load an existing RAILGUN Wallet

There are two types of RAILGUN Wallets available with Wallet SDK:

1. To send private transactions and spend tokens, create a [RAILGUN Wallet](/developer-guide/wallet/private-wallets/railgun-wallets).
2. To only view transaction history, create a [View-Only Wallet](/developer-guide/wallet/private-wallets/view-only-wallets).


# Encryption Keys (new)

hash & encryption blocks from ORG

The Database Encryption Key `encryptionKey` is a 32-byte hexadecimal string (length 64).

```typescript
const encryptionKey: string = '0101010101010101010101010101010101010101010101010101010101010101';
```

This key is used to safeguard the user’s mnemonic and wallet keys from attackers. We recommend an encryption key that is generated with entropy or `pbkdf2` hash from a strong user-provided password or PIN.

Keep the encryption key extremely safe, as it grants access to the user's wallets and mnemonics. On Mobile iOS, consider storing the Encryption Key in [Secure Enclave](https://support.apple.com/guide/security/secure-enclave-sec59b0b31ff/web).

#### Example: Create and store encryption key to local storage

```typescript
// hash-service.ts
import { pbkdf2 } from "@railgun-community/wallet";
import { Pbkdf2Response } from "@railgun-community/shared-models";

/**
 * Type definition for hashed password data.
 *
 * @property {string} secret - The hashed password or secret.
 * @property {string} salt - The salt used in the password hashing process.
 * @property {number} iterations - The number of iterations used in the hashing algorithm.
 */
type HashPasswordString = {
  secret: string;
  salt: string;
  iterations: number;
};

/**
 * Generates a salted hash from a secret string using PBKDF2 algorithm.
 *
 * @param {object} params - The parameters for hashing
 * @param {string} params.secret - The secret string to be hashed
 * @param {string} params.salt - The salt used in the hashing process
 * @param {number} params.iterations - Number of iterations for the PBKDF2 algorithm
 * @returns {Promise<Pbkdf2Response>} A promise that resolves to the hashing result
 */
export const hashPasswordString = async ({
  secret,
  salt,
  iterations,
}: HashPasswordString): Promise<Pbkdf2Response> => {
  return pbkdf2(secret, salt, iterations);
};
```

#### Example: Get encryption key from local storage

````typescript
// encryption.ts
import { getRandomBytes } from "@railgun-community/wallet";
import { hashPasswordString } from "./hash-service";
import fs from "fs";

/**
 * Asynchronously stores data in a JSON file.
 *
 * This function takes an object and persists it to the filesystem
 * as a JSON file with the provided filename with a `.json` extension.
 * The data is wrapped in an object with a `data` property before being stored.
 *
 * @param filename - The name of the file where the data will be stored (without extension)
 * @param data - The data to be stored, typically an object like {hashPasswordStored, salt}
 * @returns A Promise that resolves when the data has been written to the file
 *
 * @example
 * ```typescript
 * await storeData('credentials', { hashPasswordStored: '123abc', salt: 'xyz789' });
 * // Creates credentials.json with the content: {"data":{"hashPasswordStored":"123abc","salt":"xyz789"}}
 * ```
 */
export const storeData = async (filename: string, data: any): Promise<void> => {
  // incoming object will be
  // {hashPasswordStored, salt}
  fs.writeFileSync(`${filename}.json`, JSON.stringify({ data }));
};

/**
 * Asynchronously reads and parses JSON data from a file.
 *
 * @param filename - The name of the file to read (without the .json extension)
 * @returns A Promise that resolves to the parsed data property from the JSON file
 * @throws Will throw an error if the file cannot be read or if the JSON is invalid
 *
 * @example
 * ```typescript
 * const data = await getData('config');
 * console.log(data); // Outputs the 'data' property from config.json
 * ```
 */
export const getData = async (filename: string): Promise<any> => {
  return JSON.parse(fs.readFileSync(`${filename}.json`, "utf8")).data;
};

/**
 * Generates and stores encryption keys based on user-provided password.
 *
 * This function creates a secure encryption key from the user's password by:
 * 1. Generating a random salt
 * 2. Deriving an encryption key from the password and salt using 100,000 iterations
 * 3. Generating a separate password hash for storage using 1,000,000 iterations for extra security
 * 4. Storing the salt and the hashed password in local storage
 *
 * @param password - The user-provided password to generate encryption keys from
 * @returns A Promise that resolves with the derived encryption key
 *
 * @example
 * const encryptionKey = await setEncryptionKeyFromPassword("userPassword123");
 */
export const setEncryptionKeyFromPassword = async (
  password: string
): Promise<string> => {
  // Desired `password` comes from user input

  const salt = getRandomBytes(16); // Generate salt
  const [encryptionKey, hashPasswordStored] = await Promise.all([
    hashPasswordString({ secret: password, salt, iterations: 100000 }), // Generate hash from password and salt
    hashPasswordString({ secret: password, salt, iterations: 1000000 }), // Generate hash for stored password. Use more iterations for the stored value.
  ]);

  await Promise.all([
    await storeData("hashPasswordStored", hashPasswordStored), // Save `hashPasswordStored` to local storage
    await storeData("salt", salt), // Save `salt` to local storage
  ]);

  return encryptionKey;
};

/**
 * Validates the user's password and returns an encryption key if valid.
 *
 * This function performs two primary operations:
 * 1. Validates the provided password against a previously stored hash
 * 2. Generates an encryption key based on the password if validation succeeds
 *
 * @param {string} password - The user-provided password to validate
 * @returns {Promise<string>} A promise that resolves to the encryption key if the password is correct
 * @throws {Error} Throws an error if the password is incorrect
 *
 * @remarks
 * The function uses different iteration counts for generating the encryption key (100,000)
 * versus validating the password (1,000,000), which provides a balance between
 * security and performance.
 */
export const getEncryptionKeyFromPassword = async (
  password: string
): Promise<string> => {
  //   `password` comes from user input
  const [storedPasswordHash, storedSalt] = await Promise.all([
    getData("hashPasswordStored"), // Fetch the previously stored password hash from local storage
    getData("salt"), //   ..., // Fetch the previously stored `salt` from local storage
  ]);
  console.log("STORED DATA", storedPasswordHash, storedSalt);
  const [encryptionKey, hashPassword] = await Promise.all([
    hashPasswordString({
      secret: password,
      salt: storedSalt,
      iterations: 100000,
    }), // Same iterations as when generated, i.e. 100000
    hashPasswordString({
      secret: password,
      salt: storedSalt,
      iterations: 1000000,
    }), // Same iterations as when generated, i.e. 1000000
  ]);
  if (storedPasswordHash !== hashPassword) {
    throw new Error("Incorrect password.");
  }
  return encryptionKey;
};
````


# Encryption Keys

The Database Encryption Key `encryptionKey` is a 32-byte hexadecimal string (length 64).

```typescript
const encryptionKey: string = '0101010101010101010101010101010101010101010101010101010101010101';
```

This key is used to safeguard the user’s mnemonic and wallet keys from attackers. We recommend an encryption key that is generated with entropy or `pbkdf2` hash from a strong user-provided password or PIN.

Keep the encryption key extremely safe, as it grants access to the user's wallets and mnemonics. On Mobile iOS, consider storing the Encryption Key in [Secure Enclave](https://support.apple.com/guide/security/secure-enclave-sec59b0b31ff/web).

#### Example: Create and store encryption key to local storage

```typescript
// hash-service.ts
import { pbkdf2 } from '@railgun-community/wallet';
import { Pbkdf2Response } from '@railgun-community/shared-models';

export const hashPasswordString = async ({ secret, salt, iterations }): Promise<Pbkdf2Response> => {
  return pbkdf2(secret, salt, iterations);
},
```

```typescript
import { getRandomBytes } from '@railgun-community/wallet';
import { hashPasswordString } from './hash-service';

export const setEncryptionKeyFromPassword = async (password: string): Promise<string> => {
  // Desired `password` comes from user input

  const salt = getRandomBytes(16); // Generate salt
  const [encryptionKey, hashPasswordStored] = await Promise.all([
    hashPasswordString(password, salt, 100000), // Generate hash from password and salt
    hashPasswordString(password, salt, 1000000), // Generate hash for stored password. Use more iterations for the stored value.
  ]);

  await Promise.all([
    ..., // Save `hashPasswordStored` to local storage
    ..., // Save `salt` to local storage
  ]);

  return encryptionKey;
};
```

#### Example: Get encryption key from local storage

```typescript
import { hashPasswordString } from './hash-service';

export const getEncryptionKeyFromPassword = async (password: string): Promise<string> => {
  // `password` comes from user input
  
  const [storedPasswordHash, storedSalt] = await Promise.all([
    ..., // Fetch the previously stored password hash from local storage
    ..., // Fetch the previously stored `salt` from local storage
  ]);
  
  const [encryptionKey, hashPassword] = await Promise.all([
    hashPasswordString(password, storedSalt, 100000), // Same iterations as when generated, i.e. 100000
    hashPasswordString(password, storedSalt, 1000000), // Same iterations as when generated, i.e. 1000000
  ]);

  if (hashPasswordStored !== hashPassword) {
    throw new Error('Incorrect password.');
  }

  return encryptionKey;
}
```


# RAILGUN Wallets

RAILGUN Wallets are created using an [encryption key](/developer-guide/wallet/private-wallets/encryption-keys) and mnemonic. They will be encrypted and stored into the LevelDOWN database, which you initialized in [Step 1 of the Getting Started](/developer-guide/wallet/getting-started/5.-start-the-railgun-privacy-engine) section.&#x20;

Wallets must be generated using a 12- or 24- word mnemonic. In order to add a RAILGUN wallet to the local database, you can either import using a previously generated mnemonic or create a new mnemonic using ethers.js.

#### Example: Generate mnemonic using [ethers.js](https://docs.ethers.io/v5/)

```typescript
import { Mnemonic, randomBytes } from "ethers";

/**
 * Creates a new mnemonic phrase using 16 bytes of entropy.
 *
 * A mnemonic phrase is a human-readable representation of a cryptographic seed,
 * typically used to derive private keys in cryptocurrency wallets.
 *
 * @returns A string containing the mnemonic phrase with trimmed whitespace
 * @example
 * const mnemonic = createMnemonic();
 * // mnemonic = "word1 word2 word3 ... word12"
 */
export const createMnemonic = (): string => {
  const mnemonic = Mnemonic.fromEntropy(randomBytes(16)).phrase.trim();
  return mnemonic;
};
```

You may provide an optional `creationBlockNumberMap` which contains the block numbers for each chain when the wallet was created. This mapping will optimize private balance scans, especially for newly created wallets.&#x20;

#### Example: Create local RAILGUN Wallet from mnemonic

```typescript
import {
  NETWORK_CONFIG,
  NetworkName,
  type RailgunWalletInfo,
} from "@railgun-community/shared-models";
import { createRailgunWallet, loadWalletByID } from "@railgun-community/wallet";
import { Mnemonic, randomBytes } from "ethers";
import {
  getEncryptionKeyFromPassword,
  setEncryptionKeyFromPassword,
} from "./encryption";
import { TEST_ENCRYPTION_KEY, TEST_NETWORK } from "../utils/constants";

/**
 * Creates a new mnemonic phrase using 16 bytes of entropy.
 *
 * A mnemonic phrase is a human-readable representation of a cryptographic seed,
 * typically used to derive private keys in cryptocurrency wallets.
 *
 * @returns A string containing the mnemonic phrase with trimmed whitespace
 * @example
 * const mnemonic = createMnemonic();
 * // mnemonic = "word1 word2 word3 ... word12"
 */
export const createMnemonic = (): string => {
  const mnemonic = Mnemonic.fromEntropy(randomBytes(16)).phrase.trim();
  return mnemonic;
};

export const getEncryptionKey = async (password: string) => {
  const encryptionKey =
    (await getEncryptionKeyFromPassword(password).catch(async (err) => {
      // check for no such file or directory error.
      if (err.code === "ENOENT") {
        console.log("No such file or directory");
        // try to create a new wallet
        return await setEncryptionKeyFromPassword(password);
      }
    })) ?? TEST_ENCRYPTION_KEY;

  return encryptionKey;
};

/**
 * Creates a private RAILGUN wallet using a password and mnemonic phrase.
 *
 * This function generates an encryption key from the provided password,
 * then creates a new RAILGUN wallet using the mnemonic phrase and the
 * appropriate deployment block for the test network. After creation,
 * it loads the wallet by its ID to verify and return the wallet information.
 *
 * @param password - The password to encrypt the wallet
 * @param mnemonic - The mnemonic phrase (seed phrase) to generate the wallet
 * @returns A Promise resolving to the RAILGUN wallet information
 * @throws Will throw an error if wallet creation or loading fails
 */
export const createPrivateWallet = async (
  password: string,
  mnemonic: string
): Promise<RailgunWalletInfo> => {

  const encryptionKey = await getEncryptionKey(password);

  const { deploymentBlock } = NETWORK_CONFIG[TEST_NETWORK];
  const creationBlockMap = {
    [TEST_NETWORK]: deploymentBlock,
  };
  const railgunWalletInfo = await createRailgunWallet(
    encryptionKey,
    mnemonic,
    creationBlockMap
  );
  const walletInfo = await loadWalletByID(
    encryptionKey,
    railgunWalletInfo.id,
    false
  );
  return walletInfo;
};

/**
 * Loads a RAILGUN wallet from storage using a password and wallet ID.
 *
 * @param password - User's password to decrypt the wallet
 * @param walletId - Unique identifier of the RAILGUN wallet to load
 * @returns A Promise resolving to the wallet information (RailgunWalletInfo)
 *
 * @remarks
 * This function retrieves the encryption key derived from the password,
 * then loads the wallet from storage using the encryption key and wallet ID.
 * The third parameter to loadWalletByID is set to false to indicate this is not a viewing wallet.
 */
export const loadPrivateWallet = async (
  password: string,
  walletId: string
): Promise<RailgunWalletInfo> => {
  const encryptionKey = await getEncryptionKey(password);
  const walletInfo = await loadWalletByID(encryptionKey, walletId, false);
  return walletInfo;
};
```

After Railgun Wallets are loaded into the local DB for the first time using `createRailgunWallet`, You will want to store the field `railgunWallet.id` and reuse the `encryptionKey` in order to load it again. On subsequent launches of your dApp, instead of calling `createRailgunWallet` again, you must use the `loadWalletByID` function to instantly reload the Railgun Wallet. The `railgunWallet.id` field will additionally be used to reference the wallet when scanning balances, requesting transaction history, or creating new transactions.

#### Example: Load stored RAILGUN Wallet from ID

```typescript


const password = 'some secret phrase'; // See `Encryption Keys` section
const id = 'known wallet id'; // Previously stored when local RAILGUN wallet was created
const railgunWalletInfo = await loadPrivateWallet(password, id);
```


# View-Only Wallets

[RAILGUN Wallets](/developer-guide/wallet/private-wallets/railgun-wallets) (created with mnemonic) have both spending and viewing keys. View-Only wallets lack the spending key, so they cannot create transactions, but they have access to the wallet viewing key that can access balances and transaction history.&#x20;

View-Only wallets are created using a shareable viewing key. Only the original wallet owner, who has access to the RAILGUN Wallet (and mnemonic) can generate a shareable key.

#### Example: Generate shareable key and associated View-Only wallet

```typescript
import { 
    getWalletShareableViewingKey, 
    createViewOnlyRailgunWallet, 
} from '@railgun-community/wallet';

const encryptionKey = ...; // See `Encryption Keys` section

const railgunWalletID = ...; // Stored after RAILGUN wallet creation

const shareableViewingKey = await getWalletShareableViewingKey(railgunWalletID);

// Current block numbers for each chain when wallet was first created.
// If unknown, provide undefined.
const creationBlockNumberMap: Optional<MapType<number>> = undefined;

const viewOnlyWalletInfo = await createViewOnlyRailgunWallet(
  encryptionKey, 
  shareableViewingKey,
  creationBlockNumberMap,
);
const id = viewOnlyWalletInfo.id;
```

View-Only wallets may be loaded like RAILGUN Wallets, using `loadWalletByID`.

#### Example: Load stored RAILGUN Wallet by its ID

```typescript
import { loadWalletByID } from '@railgun-community/wallet';

const encryptionKey = ...; // See `Encryption Keys` section
const id = ...; // Previously stored when local RAILGUN wallet was created
const viewOnlyWalletInfo = await loadWalletByID(
  encryptionKey, 
  id, 
  true, // isViewOnlyWallet
);
```


# Private Balances

### Overview&#x20;

RAILGUN private (shielded) balances are built through a registry of [UTXOs](https://www.investopedia.com/terms/u/utxo.asp) (unspent transaction outputs), similar to Bitcoin. The primary difference is that the RAILGUN UTXO list is encrypted with the sender and receiver’s keys, meaning that no outsider can view the contents of the UTXO.&#x20;

The entire list of RAILGUN UTXOs makes up a [merkle tree data structure](https://en.wikipedia.org/wiki/Merkle_tree), which is a structured list of hashes which is used to validate the balance state during transactions.&#x20;

While generating the most up-to-date merkle tree, each commitment leaf (which represent a transaction) must be synced and decrypted in order to build a RAILGUN wallet’s private balance. This syncing process can take a few minutes. Once the sync is complete, UTXO token balances are aggregated into a user’s balance.


# Balance and Sync Callbacks

RAILGUN private balances update continuously as new merkle tree commitments are received.&#x20;

This process involves two callback events: `onMerkletreeScanCallback` and `onBalanceUpdateCallback`. You can set these callbacks right after the Engine initialization from [Step 1 of the Getting Started](/developer-guide/wallet/getting-started/5.-start-the-railgun-privacy-engine) section.

#### Example: Set callbacks for scan progress and balance updates

```typescript
import {
  setOnUTXOMerkletreeScanCallback,
  setOnTXIDMerkletreeScanCallback,
  setOnBalanceUpdateCallback,
  refreshBalances,
} from "@railgun-community/wallet";
import {
  delay,
  MerkletreeScanUpdateEvent,
  NETWORK_CONFIG,
  NetworkName,
  RailgunBalancesEvent,
  RailgunWalletBalanceBucket,
} from "@railgun-community/shared-models";
import { TEST_NETWORK } from "../utils/constants";
import { Wallet } from "ethers";


import dotenv from "dotenv";
dotenv.config();

const BALANCE_POLLER_INTERVAL = 1000 * 60 * 1; // 1 min

/**
 * Example Callback function that gets invoked during a private balance scan process.
 * This function handles updates on the progress and status of the UTXO Merkletree scan.
 *
 * @param eventData - The event data containing information about the scan progress
 * @param eventData.progress - The current progress of the scan (typically a number between 0 and 1)
 * @param eventData.scanStatus - The current status of the scan process
 *
 */
const onUTXOMerkletreeScanCallback = (eventData: MerkletreeScanUpdateEvent) => {
  // Will get called throughout a private balance scan.
  // Handle updates on scan progress and status here, i.e. progress bar or loading indicator in the UI.
  console.log("UTXO scan update:", eventData.progress, eventData.scanStatus);
};

/**
 * Example Callback function for handling Merkle tree scan updates during a private balance scan.
 * This function is invoked with status updates throughout the scanning process.
 *
 * @param {MerkletreeScanUpdateEvent} eventData - The event data containing scan progress information
 * @param {number} eventData.progress - The progress percentage of the scan (0-100)
 * @param {string} eventData.scanStatus - The current status of the scan
 *
 */
const onTXIDMerkletreeScanCallback = (eventData: MerkletreeScanUpdateEvent) => {
  // Will get called throughout a private balance scan.
  // Handle updates on scan progress and status here, i.e. progress bar or loading indicator in the UI.
  console.log("TXID scan update:", eventData.progress, eventData.scanStatus);
};

/**
 * Callback function triggered when a RAILGUN wallet's balance is updated.
 *
 * This function is called at the end of a private balance scan for a RAILGUN wallet
 * for each txidVersion and balanceBucket. The function logs balance updates and
 * stores the updated balances in a cache.
 *
 * @param balancesFormatted - The updated balances event containing:
 *   - txidVersion: Currently only V2_PoseidonMerkle, with V3_PoseidonMerkle planned for future
 *   - chain: The blockchain network
 *   - railgunWalletID: Unique identifier for the RAILGUN wallet
 *   - balanceBucket: Category of funds based on the Private POI system status:
 *     - "Spendable": Funds available for use in private DeFi interactions
 *     - "ShieldBlocked": Funds that are blocked from being shielded
 *     - "ShieldPending": Funds in the process of being shielded
 *     - "ProofSubmitted": Funds for which proofs have been submitted
 *     - "MissingInternalPOI": Funds missing internal proof of innocence
 *     - "MissingExternalPOI": Funds missing external proof of innocence
 *     - "Spent": Funds that have been spent
 *   - erc20Amounts: Array of ERC20 token amounts in the wallet
 *   - nftAmounts: Array of NFT amounts in the wallet
 */
const onBalanceUpdateCallback = (balancesFormatted: RailgunBalancesEvent) => {
  // Will get called at the end of a private balance scan for a RAILGUN wallet
  // for each txidVersion and balanceBucket (explained below).
  // RailgunBalancesEvent includes:
  // *txidVersion: TXIDVersion;
  // chain: Chain;
  // railgunWalletID: string;
  // *balanceBucket: RailgunWalletBalanceBucket;
  // erc20Amounts: RailgunERC20Amount[];
  // nftAmounts: RailgunNFTAmount[];
  // *txidVersion: Currently, there is only the V2_PoseidonMerkle txidVersion. In the future,
  // with the launch of V3_PoseidonMerkle, there will be options to migrate balances
  // from V2 to V3.
  // *balanceBucket: With the Private Proof of Innocence system, balances are categorized
  // into "Spendable", "ShieldBlocked", "ShieldPending", "ProofSubmitted", "MissingInternalPOI",
  // "MissingExternalPOI", and "Spent". As funds move through the Private POI system, as
  // explained here: https://docs.railgun.org/wiki/assurance/private-proofs-of-innocence,
  // they will automatically end up in the "Spendable" category, which is when they are
  // able to be used in private DeFi interactions.
  // Handle updates on the private token balances of each available RAILGUN wallet here.
  console.log("Balances updated:", balancesFormatted.balanceBucket);
  if (balancesFormatted.erc20Amounts.length > 0) {
    console.log("ERC20 Balances: ", balancesFormatted.erc20Amounts);
  }
  balanceCache.set(balancesFormatted.balanceBucket, balancesFormatted);
};

/**
 * Sets up callbacks for balance-related operations.
 *
 * This function configures three callbacks:
 * 1. UTXO Merkletree scanning callback
 * 2. TXID Merkletree scanning callback
 * 3. Balance update callback
 *
 * These callbacks are essential for tracking and managing wallet balance updates
 * and scanning operations in the Railgun privacy system.
 *
 * @returns {void}
 */
export const setupBalanceCallbacks = () => {
  setOnUTXOMerkletreeScanCallback(onUTXOMerkletreeScanCallback);
  setOnTXIDMerkletreeScanCallback(onTXIDMerkletreeScanCallback);
  setOnBalanceUpdateCallback(onBalanceUpdateCallback);
};

/**
 * Polls for balance updates for specified RAILGUN wallets at regular intervals.
 *
 * This function starts the private balance scan for all provided RAILGUN wallet IDs.
 * It runs in the background and triggers the appropriate callbacks as the scan progresses.
 * When the scan completes, it calls the onBalanceUpdateCallback.
 * After completion, it waits for a specified interval before polling again recursively.
 *
 * @param walletIds - Array of RAILGUN wallet IDs to poll balances for
 * @returns A Promise that resolves when the current polling iteration is complete
 *
 * @remarks
 * - Uses the TEST_NETWORK configuration to determine the chain
 * - Automatically retries on error
 * - Will resolve the balanceLoadedPromise with "Loaded Balances" if it exists
 * - Waits for BALANCE_POLLER_INTERVAL milliseconds between polling iterations
 */
export const runBalancePoller = async (walletIds: string[]) => {
  // Run this function to start the private balance scan for all RAILGUN wallets.
  // This function will run in the background and call the callbacks above as the scan progresses.
  // It will also call the onBalanceUpdateCallback when the scan is complete.

  const chain = NETWORK_CONFIG[TEST_NETWORK].chain;
  console.log("Running balance poller... on chain", chain);
  try {
    // scan for all wallets.

    await refreshBalances(chain, walletIds);
  } catch (error) {
    console.error("BALANCE REFRESH ERROR", error);
    await refreshBalances(chain, walletIds);
  }
  console.log("Balance poller complete. Waiting for next poll...");
  if (balanceLoadedPromise != null) {
    balanceLoadedPromise("Loaded Balances");
    balanceLoadedPromise = undefined;
  }
  await delay(BALANCE_POLLER_INTERVAL);
  runBalancePoller(walletIds);
};

let balanceLoadedPromise: ((value: unknown) => void) | null | undefined =
  undefined;

/**
 * Waits for balances to be loaded by returning a Promise that resolves
 * when the balanceLoadedPromise is resolved elsewhere in the application.
 *
 * @returns {Promise<void>} A Promise that resolves when balances are loaded
 *
 * @example
 * await waitForBalancesLoaded();
 * // Now you can safely access balances
 */
export const waitForBalancesLoaded = async () => {
  return new Promise((resolve) => {
    balanceLoadedPromise = resolve;
  });
};

/**
 * Cache for storing RAILGUN wallet balance events.
 * This map uses {@link RailgunWalletBalanceBucket} as keys and stores {@link RailgunBalancesEvent} as values.
 * The cache helps reduce redundant balance lookups by storing the most recent balance events for each wallet bucket.
 */
export const balanceCache = new Map<
  RailgunWalletBalanceBucket,
  RailgunBalancesEvent
>();

/**
 * Retrieves the spendable balances from the balance cache.
 *
 * This function accesses the RAILGUN wallet's spendable balances from the cache.
 * Spendable balances are funds that are available for immediate use in transactions.
 *
 * @returns {Object} The spendable balances retrieved from the balance cache.
 *
 * @example
 * const balances = getSpendableBalances();
 * console.log(balances);
 */
export const getSpendableBalances = () => {
  return balanceCache.get(RailgunWalletBalanceBucket.Spendable);
};

/**
 * Displays the spendable balances of ERC20 tokens available to the wallet.
 *
 * This function retrieves spendable balances using the `getSpendableBalances()` function
 * and logs each ERC20 token balance to the console. If no balances are found,
 * it logs a message indicating that no spendable balances were found.
 *
 * @example
 * // Display all spendable token balances in the wallet
 * displaySpendableBalances();
 *
 * @returns {void}
 */
export const displaySpendableBalances = () => {
  const balances = getSpendableBalances();

  if (balances) {
    for (const erc20Amount of balances.erc20Amounts) {
      console.log("ERC20 Balance: ", erc20Amount);
    }
  } else {
    console.log("No spendable balances found.");
  }
};
```


# Updating Balances

In certain scenarios, you may want to manually refresh all private balances.

#### Example: Manually refresh balances

<pre class="language-typescript"><code class="lang-typescript"><strong>import { NETWORK_CONFIG, NetworkName } from '@railgun-community/shared-models';
</strong>import { refreshBalances } from '@railgun-community/wallet';

const { chain } = NETWORK_CONFIG[NetworkName.Ethereum];
const railgunWalletID = '...'; // Obtained after a previous call to `createRailgunWallet`

// Optional filter to only scan the provided wallet IDs. Leave undefined to scan all wallets.
const walletIdFilter = [railgunWalletID];

try {
    await refreshBalances(chain, walletIdFilter);
    // During scan, `onMerkletreeScanCallback` callback from the previous section will trigger.
    // When scan is completed, `onBalanceUpdateCallback` callback from the previous section will trigger.
    // This may take a few minutes on first sync, but only a few seconds on subsequent calls.
} catch (err) {
    // Scan failed to run.
}
</code></pre>


# QuickSync

The RAILGUN contributors have built a mechanism called “QuickSync“ that runs on Graph Protocol. It speeds up the private balance block-syncing process by several orders of magnitude.&#x20;

QuickSync is automatically active during private balance scans and doesn’t require any configuration.


# Transactions

### Private Transactions

RAILGUN enables encrypted private transactions through [Shielding](/developer-guide/wallet/transactions/shielding). Once assets are shielded into the RAILGUN contract, there are several options:&#x20;

1. [Transfer ](/developer-guide/wallet/transactions/private-transfers)tokens privately to another RAILGUN wallet.
2. Use a [cross-contract call](/developer-guide/wallet/transactions/cross-contract-calls) to interact privately with an external smart contract.
3. [Unshield](/developer-guide/wallet/transactions/unshielding) tokens into any public wallet.

These transactions achieve a level of privacy, as their contents cannot be read by an external observer. For example, private transfers show no details of the tokens or amounts sent[ in Etherscan](https://etherscan.io/tx/0x37c555c4add7b4372a234d34e387ad1da6f81fd3c7b860d951f02279cd8e4727).

### Broadcasters

In order to achieve full anonymity, RAILGUN uses a network of Broadcasters to submit transactions.

Broadcasters are external wallets that sign transactions on a user's behalf. Broadcasters pay gas fees for users, and they receive ERC-20 tokens as fees for the service.

If you want to send transactions through a Broadcaster, use [@railgun-community/waku-broadcaster-client](https://github.com/Railgun-Community/waku-broadcaster-client)


# Transaction Utils

Functions used throughout the generation of all types of Railgun Interactions.

```typescript
// 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);
});

```


# Shielding

How to shield tokens into a RAILGUN Wallet

Shield transactions are executed publicly, using a public wallet.&#x20;

Under the hood, the shield action sends ERC-20 tokens into the RAILGUN contract, where they are associated with a RAILGUN Wallet and a private balance. Once shielded, the owning RAILGUN Wallet can initiate [private transfers](/developer-guide/wallet/transactions/private-transfers) and [cross-contract calls](/developer-guide/wallet/transactions/cross-contract-calls) to interact with external smart contracts.

***NOTE***: Do *not* shield rebase tokens into RAILGUN, as these are not supported. For a list of rebase tokens, see [Rebase Token List (Coingecko)](https://www.coingecko.com/en/categories/rebase-tokens).


# Shield ERC-20 tokens

Shield one or more ERC-20 tokens into RAILGUN private balances in a single transaction

### Imports

```typescript
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.

```typescript
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.

```typescript

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

```typescript
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();
};
```


# 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](https://weth.io/) 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

```typescript
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.

```typescript
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.

```typescript
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.

```typescript
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();
};
```


# Shield NFTs

Shield one or many ERC-721 tokens into RAILGUN private balances in a single transaction. ERC-1155 not supported at this time.

### Imports

```typescript
import {
  NETWORK_CONFIG,
  TXIDVersion,
  type NetworkName,
  type RailgunNFTAmountRecipient,
} from "@railgun-community/shared-models";
import {
  gasEstimateForShield,
  populateShield,
} from "@railgun-community/wallet";
import { type Wallet, type HDNodeWallet, Contract, ZeroAddress } from "ethers";
import {
  getGasDetailsForTransaction,
  getShieldSignature,
  serializeERC721Transfer,
} from "../util";
import {
  TEST_NETWORK,
  TEST_NFT_ADDRESS,
  TEST_NFT_SUBID,
} from "../../utils/constants";
import { getProviderWallet } from "../../utils/provider";
```

### Gas Estimate

The  `erc721ShieldGasEstimate` function calculates the estimated gas cost required for shielding a given amount of ERC-721 tokens. It uses the network and wallet information to derive a shield signature from the sender's wallet address, and the intended token recipients to compute the gas estimate.

```typescript
export const erc721ShieldGasEstimate = async (
  network: NetworkName,
  wallet: Wallet | HDNodeWallet,
  erc721AmountRecipients: RailgunNFTAmountRecipient[]
) => {
  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,
    [],
    erc721AmountRecipients, // nftAmountRecipients
    fromWalletAddress
  );

  return gasEstimate;
};
```

### Populate Transaction

The `erc721PopulateShieldTransaction` function constructs a transaction that authorizes and transfers the ERC-721 tokens to the RAILGUN shield. It checks if each token's transfer approval has been granted to the network's proxy contract and processes the transactions accordingly. It then calculates the gas details and populates the transaction with necessary privacy protocol data before returning the transaction information.

```typescript
export const erc721PopulateShieldTransaction = async (
  network: NetworkName,
  wallet: Wallet | HDNodeWallet,
  erc721AmountRecipients: RailgunNFTAmountRecipient[],
  sendWithPublicWallet: boolean
) => {
  const spender = NETWORK_CONFIG[network].proxyContract;

  for (const amountRecipient of erc721AmountRecipients) {
    const contract = new Contract(
      amountRecipient.nftAddress,
      [
        "function getApproved(uint256 tokenId) view returns (address)",
        "function approve(address to, uint256 tokenId) external returns (bool)",
      ],
      wallet
    );
    const allowance = await contract.getApproved(amountRecipient.tokenSubID);
    if (allowance === ZeroAddress) {
      throw new Error("ERC721 Not minted.");
    }
    if (allowance.toLowerCase() == spender.toLowerCase()) {
      console.log("already have enough allowance");
      continue;
    }

    const tx = await contract.approve(spender, amountRecipient.tokenSubID);
    await tx.wait();
  }

  const gasEstimate = await erc721ShieldGasEstimate(
    network,
    wallet,
    erc721AmountRecipients
  );

  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,
    [],
    erc721AmountRecipients,
    gasDetails
  );

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

### Example Usage

Finally, the `TEST_shieldERC721` function demonstrates how to use these processes by shielding an example NFT to a specified RAILGUN wallet address. It showcases assembling the transaction, sending it with the wallet, and handling the transaction receipt.

```typescript
export const TEST_shieldERC721 = async (railgunWalletAddress: string) => {
  const { wallet } = getProviderWallet();

  const nftAddress = TEST_NFT_ADDRESS;
  const tokenSubID = TEST_NFT_SUBID;
  const erc721AmountRecipients = [
    serializeERC721Transfer(nftAddress, tokenSubID, railgunWalletAddress),
  ];

  const { gasEstimate, gasDetails, transaction, nullifiers } =
    await erc721PopulateShieldTransaction(
      TEST_NETWORK,
      wallet,
      erc721AmountRecipients,
      true
    );

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


# Private Transfers

*Private Transfers* *require you to "Generate Proof" and, optionally, make use of "Broadcasters": See* [*UX for Private Transactions*](/developer-guide/wallet/transactions/ux-private-transactions)

Transfers are encrypted “send” transactions, executed anonymously through the RAILGUN Privacy System. In a single transaction, a user can transfer multiple ERC-20 tokens privately to any other RAILGUN wallet.

Like all private RAILGUN transactions, transfers are typically executed through a Broadcaster, which guarantees the anonymity of both the sender and receiving wallets.

### Memos

Transfers have an optional memo field which can contain text and emojis. This field is encrypted with the note’s ephemeral keys and therefore can only be decrypted by the sender and receiver.

There is no maximum length on the memo field, but as the memo is stored (encrypted) on-chain, its length will impact the amount of gas used in the transaction.


# Private ERC-20 Transfers

*Private Transfers* *require you to "Generate Proof" and, optionally, make use of "Relayers": See* [*UX for Private Transactions*](/developer-guide/wallet/transactions/ux-private-transactions)

RAILGUN users may transfer multiple ERC-20 tokens to other RAILGUN users in a single transaction, in a fully encrypted and privacy-preserving way.

### Imports

```typescript

import {
  calculateGasPrice,
  NetworkName,
  TXIDVersion,
  type FeeTokenDetails,
  type RailgunERC20AmountRecipient,
  type RailgunWalletInfo,
  type TransactionGasDetails,
} from "@railgun-community/shared-models";
import {
  gasEstimateForUnprovenTransfer,
  generateTransferProof,
  populateProvedTransfer,
} from "@railgun-community/wallet";
import {
  getGasDetailsForTransaction,
  getOriginalGasDetailsForTransaction,
  serializeERC20Transfer,
} from "../util";
import { TEST_NETWORK, TEST_TOKEN } from "../../utils/constants";
import { getProviderWallet } from "../../utils/provider";
import {
  getBroadcasterDetails,
  getBroadcasterFeeRecipientDetails,
  getFeeTokenDetails,
} from "../../waku/waku";
```

### Gas Estimate

```typescript
export const erc20PrivateTransferGasEstimate = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  erc20AmountRecipient: RailgunERC20AmountRecipient[],
  sendWithPublicWallet: boolean = true,
  feeTokenDetails: FeeTokenDetails | undefined = undefined,
  memoText: string | undefined = undefined
) => {
  const originalGasDetails = await getOriginalGasDetailsForTransaction(
    network,
    sendWithPublicWallet
  );
  console.log("originalGasDetails: ", originalGasDetails);
  const { gasEstimate } = await gasEstimateForUnprovenTransfer(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    memoText,
    erc20AmountRecipient,
    [], // nftAmountRecipients
    originalGasDetails,
    feeTokenDetails,
    sendWithPublicWallet
  );

  const estimatedGasDetails = { ...originalGasDetails, gasEstimate };

  return {
    gasEstimate,
    estimatedGasDetails,
    originalGasDetails,
  };
};

```

### Generate Proof

```typescript
export const erc20PrivateTransferGenerateProof = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  tokenAmountRecipients: RailgunERC20AmountRecipient[],
  overallBatchMinGasPrice: bigint,
  showSenderAddressToRecipient: boolean = true,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined,
  memoText: string | undefined = undefined
) => {
  const progressCallback = (progress: number) => {
    // Handle proof progress (show in UI).
    // Proofs can take 20-30 seconds on slower devices.
    console.log("Private ERC20 Transfer Proof progress: ", progress);
  };
  // GENERATES RAILGUN SPENDING PROOF
  await generateTransferProof(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    showSenderAddressToRecipient,
    memoText,
    tokenAmountRecipients,
    [], // nftAmountRecipients
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    progressCallback
  );
};

```

### Populate Transaction

```typescript
export const erc20PrivateTransferPopulateTransaction = async (
  network: NetworkName,
  railgunWalletID: string,
  tokenAmountRecipients: RailgunERC20AmountRecipient[],
  overallBatchMinGasPrice: bigint,
  transactionGasDetails: TransactionGasDetails,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined,
  showSenderAddressToRecipient: boolean = true,
  memoText: string | undefined = undefined
) => {
  const populateResponse = await populateProvedTransfer(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    showSenderAddressToRecipient,
    memoText,
    tokenAmountRecipients,
    [], // nftAmountRecipients
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    transactionGasDetails
  );

  return populateResponse;
};

```

### Example Usage

```typescript
export const TEST_PrivateTransfer = async (
  encryptionKey: string,
  railgunWalletInfo: RailgunWalletInfo,
  memoText: string | undefined, // optional memo text for the transfer
  sendWithPublicWallet: boolean = true
) => {
  console.log("TEST_PrivateTransfer");
  const { wallet } = getProviderWallet();
  // get gas estimate,
  // generate proof,
  // populate tx

  const erc20AmountRecipients: RailgunERC20AmountRecipient[] = [
    serializeERC20Transfer(
      TEST_TOKEN, // WETH
      1n,
      railgunWalletInfo.railgunAddress
    ),
  ];

  // dependent if !sendWithPublicWallet
  const { broadcaster, feeTokenDetails } = await getFeeTokenDetails(
    TEST_NETWORK,
    TEST_TOKEN,
    sendWithPublicWallet
  );

  const { gasEstimate, originalGasDetails, estimatedGasDetails } =
    await erc20PrivateTransferGasEstimate(
      encryptionKey,
      TEST_NETWORK,
      railgunWalletInfo.id,
      erc20AmountRecipients,
      sendWithPublicWallet,
      feeTokenDetails,
      memoText // optional memo text for the transfer
    );

  const transactionGasDetails = await getGasDetailsForTransaction(
    TEST_NETWORK,
    gasEstimate,
    sendWithPublicWallet, // true if using public wallet
    wallet
  );

  // only do this if !sendWithPublicWallet
  const broadcasterDetails = await getBroadcasterDetails(
    estimatedGasDetails,
    broadcaster,
    feeTokenDetails
  );

  console.log("Private ERC20 TX gasEstimate: ", gasEstimate);
  const overallBatchMinGasPrice = calculateGasPrice(transactionGasDetails);

  // generate proof
  await erc20PrivateTransferGenerateProof(
    encryptionKey,
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc20AmountRecipients,
    overallBatchMinGasPrice /* overallBatchMinGasPrice */,
    true /* showSenderAddressToRecipient */,
    sendWithPublicWallet /*true if using public wallet*/,
    broadcasterDetails?.broadcasterFeeERC20AmountRecipient /* pass the broadcaster fee recipient */,
    memoText /* memoText */
  );

  // populate tx
  const transaction = await erc20PrivateTransferPopulateTransaction(
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc20AmountRecipients,
    overallBatchMinGasPrice,
    transactionGasDetails,
    sendWithPublicWallet,
    broadcasterDetails?.broadcasterFeeERC20AmountRecipient /* pass the broadcaster fee recipient */,
    true /* showSenderAddressToRecipient */,
    memoText /* memoText (optional) */
  );
  console.log("ERC20 transaction: ", transaction);
The
};

```


# Private NFT Transfers

*Private Transfers* *require you to "Generate Proof" and, optionally, make use of "Broadcasters": See* [*UX for Private Transactions*](/developer-guide/wallet/transactions/ux-private-transactions)

RAILGUN users may transfer multiple ERC-721 tokens to other RAILGUN users in a single transaction, in a fully encrypted and privacy-preserving way.

ERC-1155 token transfers are not yet supported.

### Imports

```typescript
import {
  calculateGasPrice,
  TXIDVersion,
  type NetworkName,
  type RailgunERC20AmountRecipient,
  type RailgunNFTAmountRecipient,
  type RailgunWalletInfo,
  type TransactionGasDetails,
} from "@railgun-community/shared-models";
import {
  getGasDetailsForTransaction,
  getOriginalGasDetailsForTransaction,
  serializeERC721Transfer,
} from "../util";
import {
  gasEstimateForUnprovenTransfer,
  generateTransferProof,
  populateProvedTransfer,
} from "@railgun-community/wallet";
import {
  TEST_NETWORK,
  TEST_NFT_ADDRESS,
  TEST_NFT_SUBID,
} from "../../utils/constants";
import { getProviderWallet } from "../../utils/provider";
```

### Gas Estimate

```typescript
export const erc721PrivateTransferGasEstimate = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  erc721AmountRecipients: RailgunNFTAmountRecipient[],
  sendWithPublicWallet: boolean = true,
  feeTokenDetails: RailgunERC20AmountRecipient | undefined = undefined,
  memoText: string | undefined = undefined
) => {
  const originalGasDetails = await getOriginalGasDetailsForTransaction(
    network,
    sendWithPublicWallet
  );
  console.log("originalGasDetails: ", originalGasDetails);

  const { gasEstimate } = await gasEstimateForUnprovenTransfer(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    memoText,
    [],
    erc721AmountRecipients,
    originalGasDetails,
    feeTokenDetails,
    sendWithPublicWallet
  );

  return gasEstimate;
};
```

### Generate Proof

```typescript
export const erc721PrivateTransferGenerateProof = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  erc721AmountRecipients: RailgunNFTAmountRecipient[],
  overallBatchMinGasPrice: bigint,
  showSenderAddressToRecipient: boolean = true,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined,
  memoText: string | undefined = undefined
) => {
  const progressCallback = (progress: number) => {
    // Handle proof progress (show in UI).
    // Proofs can take 20-30 seconds on slower devices.
    console.log("Private ERC721 Transfer Proof progress: ", progress);
  };

  await generateTransferProof(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    showSenderAddressToRecipient,
    memoText,
    [],
    erc721AmountRecipients,
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    progressCallback
  );
};
```

### Populate Transaction

```typescript
export const erc721PrivateTransferPopulateTransaction = async (
  network: NetworkName,
  railgunWalletID: string,
  erc721AmountRecipients: RailgunNFTAmountRecipient[],
  overallBatchMinGasPrice: bigint,
  transactionGasDetails: TransactionGasDetails,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined,
  showSenderAddressToRecipient: boolean = true,
  memoText: string | undefined = undefined
) => {
  const populateResponse = await populateProvedTransfer(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    showSenderAddressToRecipient,
    memoText,
    [], // erc20AmountRecipients
    erc721AmountRecipients,
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    transactionGasDetails
  );

  return populateResponse;
};
```

### Example Usage

```typescript
export const TEST_ERC721PrivateTransfer = async (
  encryptionKey: string,
  railgunWalletInfo: RailgunWalletInfo
) => {
  const { wallet } = getProviderWallet();

  const nftAddress = TEST_NFT_ADDRESS;
  const tokenSubID = TEST_NFT_SUBID;
  const erc721AmountRecipients = [
    serializeERC721Transfer(
      nftAddress,
      tokenSubID,
      railgunWalletInfo.railgunAddress
    ),
  ];

  const gasEstimate = await erc721PrivateTransferGasEstimate(
    encryptionKey,
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc721AmountRecipients
  );

  console.log("Private ERC721 TX gasEstimate: ", gasEstimate);

  await erc721PrivateTransferGenerateProof(
    encryptionKey,
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc721AmountRecipients,
    1n
  );

  const transactionGasDetails = await getGasDetailsForTransaction(
    TEST_NETWORK,
    gasEstimate,
    true,
    wallet
  );

  const overallBatchMinGasPrice = calculateGasPrice(transactionGasDetails);

  const transaction = await erc721PrivateTransferPopulateTransaction(
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc721AmountRecipients,
    overallBatchMinGasPrice,
    transactionGasDetails
  );
  console.log("ERC721 transaction: ", transaction);
  // send private ERC721 tx via self-signed tx or broadcaster.
};
```


# Cross-Contract Calls

*Cross-Contract Calls require you to "Generate Proof" and, optionally, make use of "Broadcasters": See UX for Private Transactions* [*UX for Private Transactions* ](/developer-guide/wallet/transactions/ux-private-transactions)

Cross Contract calls are the primary method for RAILGUN to access external DeFi protocols and smart contracts. Any number of ordered contract calls can be wired together and executed against a private balance. These cross-contract interactions occur in the generalized RAILGUN Relay Adapt contract, which facilitates a number of serial multi-calls in a single block.

### How it Works

First, (1) an unshield is executed against the private balance, moving tokens temporarily into the Relay Adapt contract. Then, (2) a multi-call processes any number of contract calls against the balance. Finally, (3) the result is shielded back into the user’s private balance. This all occurs in a single block and one transaction.

For example, Railway Wallet has integrated 0x API to facilitate private swaps. This is done through two serial cross-contract calls: First, the exact swap token amount is approved for spending. Second, the 0x token swap is performed. The contract spend approval is a failsafe operation on each swap transaction. It ensures that the swap amount is guaranteed to be approved during the execution block.

In the case of a contract call failure, the entire call will revert.

### RAILGUN Cookbook and Recipes

The RAILGUN Cookbook simplifies forming these cross-contract transactions. Please see our guide on [RAILGUN Cookbook](/developer-guide/cookbook/cookbook-overview) and check the verified recipes that are available in the [Cookbook SDK (Github)](https://github.com/Railgun-Community/cookbook).

The "Recipes" in the Cookbook contain structured calls that integrate directly into the Wallet SDK. Contributors are working on verified Recipes for DEX Swaps, Asset Vaults, Liquidity Provisioning (LP), Stablecoin Minting, Liquid Staking, and more.

### Call Structure

The structure of these transactions is very similar to RAILGUN [transfers](/developer-guide/wallet/transactions/private-transfers) and [unshields](/developer-guide/wallet/transactions/unshielding), with 3 new fields.&#x20;

**crossContractCallsSerialized**: `{to, data, value}` fields make up our array of crossContractCalls, which interact with external smart contracts through a series of ordered calls.

**relayAdaptUnshieldERC20Amounts**: The exact amounts that should be unshielded to process in this transaction. Make sure only to unshield the exact token amount that is required, as there is an unshield fee associated with this value (like any unshield transaction).

**relayAdaptShieldERC20Addresses**: A list of token addresses that are the result of the cross contract calls, to shield into private balance after the calls are complete. If its balance is 0, nothing will be shielded. These are the only tokens that will be shielded back, so make sure the list is complete. Any tokens that are not listed in this array and shielded during the transaction will not be recoverable.

***NOTE***: If using an existing Cookbook Recipe or writing your own for custom cross-contract calls, the Recipe will output these fields for you.

#### Example: Format 0x API private swap for cross-contract calls with Cookbook

See [Use your zkApp privately](/developer-guide/cookbook/use-your-zkapp-privately).

#### Example: Format 0x API private swap for cross-contract calls without Cookbook

```typescript
// Tokens to swap through 0x API.
const sellToken = {
    address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI
    amount: BigInt('0x100000000'),
}
const buyTokenAddress = '0xdac17f958d2ee523a2206206994597c13d831ec7'; // USDT

// Amount of sell token will be modified by initial unshield, fee of 0.25%.
const sellTokenAmountAfterUnshieldFee = 
    sellToken.amount * (10000n - unshieldFeeBasisPoints) / 10000n;

// Approve the exact amount to swap with 0x.
const erc20 = new Contract(sellToken.address, erc20ABI, provider);
const transactionApprove0x = await erc20.populateTransaction.approve(
    quote.allowanceTarget,
    sellTokenAmountAfterUnshieldFee,
);

// Fetch quote data from 0x API.
const quote = await get0xAPIQuote(
    sellToken.address, 
    buyTokenAddress, 
    sellTokenAmountAfterUnshieldFee,
);
const transaction0xSwap = {
    to: quote.to,
    value: quote.value,
    data: quote.data,
};

const crossContractCalls: ContractTransaction[] = [
    transactionApprove0x,
    transaction0xSwap,
];

const relayAdaptUnshieldERC20Amounts = [sellToken];

// In case there's slippage or dust leftover, 
// make sure to shield the sellToken as well as buyToken.
const relayAdaptShieldERC20Recipients: RailgunERC20Recipient = [{
    tokenAddress: sellToken.address,
    recipientAddress: railgunWallet.getAddress(),
},
{
    tokenAddress: buyTokenAddress,
    recipientAddress: railgunWallet.getAddress(),
}];
```

### Approvals

By default, you should assume that the Relay Adapt contract is not approved to spend any tokens. Because anyone can execute transactions against this contract, a user could potentially reset all spend allowances to 0 in any transaction.

However, because we execute cross-contract transactions inside a multi-call, we're safe from outside interactions during the execution block.

It's recommended to always approve the exact amount that you require in your transaction, as the initial step of the cross-contract calls. (As in the example above).

***NOTE***: When using the Cookbook, all Recipes will include the necessary approval call, so there is no additional code needed.

### Impact of Unshield Fee

Because of the unshield fee, the amount of tokens in the transaction will be 0.25% less than the amount unshielded. This is very important if transacting a specific value, like for a swap.

For example, if we want to swap 100 DAI for wETH, the transaction will look like this:&#x20;

1. Unshield 100 DAI (`relayAdaptUnshieldERC20Amounts`), with a 0.25 DAI fee.
2. Cross contract call: approve 99.75 DAI for swap spending.
3. Cross contract call: swap 99.75 DAI for wETH.
4. Shield wETH and any leftover DAI dust into private balance (`relayAdaptShieldERC20Addresses`).&#x20;

***NOTE***: The Cookbook automatically calculates the unshield fees when using an existing Recipe or you write your own cross-contract calls as a Recipe.

### Imports

```typescript
import {
  calculateGasPrice,
  NETWORK_CONFIG,
  NetworkName,
  TXIDVersion,
  type RailgunERC20Amount,
  type RailgunERC20AmountRecipient,
  type RailgunERC20Recipient,
  type RailgunNFTAmount,
  type RailgunNFTAmountRecipient,
  type RailgunWalletInfo,
  type TransactionGasDetails,
} from "@railgun-community/shared-models";
import {
  gasEstimateForUnprovenCrossContractCalls,
  generateCrossContractCallsProof,
  populateProvedCrossContractCalls,
} from "@railgun-community/wallet";
import {
  getGasDetailsForTransaction,
  getOriginalGasDetailsForTransaction,
  serializeERC20RelayAdaptUnshield,
  serializeERC20Transfer,
} from "../util";
import { TEST_NETWORK, TEST_TOKEN } from "../../utils/constants";
import { getProviderWallet } from "../../utils/provider";
import { Contract, type ContractTransaction } from "ethers";
```

### Gas Estimate

```typescript
export const crossContractGasEstimate = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  erc20AmountUnshieldAmounts: RailgunERC20Amount[],
  erc721AmountUnshieldAmounts: RailgunNFTAmount[],
  erc20ShieldRecipients: RailgunERC20Recipient[],
  erc721AmountShieldRecipients: RailgunNFTAmountRecipient[],
  crossContractCalls: ContractTransaction[],
  minGasLimit: bigint, // provided by user, or cookbook output.
  sendWithPublicWallet: boolean = true,
  feeTokenDetails: RailgunERC20AmountRecipient | undefined = undefined
) => {
  const originalGasDetails = await getOriginalGasDetailsForTransaction(
    network,
    sendWithPublicWallet
  );
  console.log("CrossContract: originalGasDetails: ", originalGasDetails);
  const { gasEstimate } = await gasEstimateForUnprovenCrossContractCalls(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    erc20AmountUnshieldAmounts,
    erc721AmountUnshieldAmounts,
    erc20ShieldRecipients,
    erc721AmountShieldRecipients,
    crossContractCalls,
    originalGasDetails,
    feeTokenDetails,
    sendWithPublicWallet,
    minGasLimit
  );
  return gasEstimate;
};
```

### Proof Generation

```typescript
export const crossContractGenerateProof = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  erc20AmountUnshieldAmounts: RailgunERC20Amount[],
  erc721AmountUnshieldAmounts: RailgunNFTAmount[],
  erc20AmountShieldRecipients: RailgunERC20Recipient[],
  erc721AmountShieldRecipients: RailgunNFTAmountRecipient[],
  crossContractCalls: ContractTransaction[],
  overallBatchMinGasPrice: bigint,
  minGasLimit: bigint,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined
) => {
  const progressCallback = (progress: number) => {
    // Handle proof progress (show in UI).
    // Proofs can take 20-30 seconds on slower devices.
    console.log("CrossContract Call Proof progress: ", progress);
  };
  // GENERATES RAILGUN SPENDING PROOF
  await generateCrossContractCallsProof(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    erc20AmountUnshieldAmounts,
    erc721AmountUnshieldAmounts,
    erc20AmountShieldRecipients,
    erc721AmountShieldRecipients,
    crossContractCalls,
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    minGasLimit,
    progressCallback
  );
};
```

### Populate Transaction

```typescript
export const crossContractCallsPopulateTransaction = async (
  network: NetworkName,
  railgunWalletID: string,
  erc20AmountUnshieldAmounts: RailgunERC20Amount[],
  erc721AmountUnshieldAmounts: RailgunNFTAmount[],
  erc20AmountShieldRecipients: RailgunERC20Recipient[],
  erc721AmountShieldRecipients: RailgunNFTAmountRecipient[],
  crossContractCalls: ContractTransaction[],
  transactionGasDetails: TransactionGasDetails,
  overallBatchMinGasPrice: bigint,
  sendWithPublicWallet: boolean = true,

  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined
) => {
  const populateResponse = await populateProvedCrossContractCalls(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    erc20AmountUnshieldAmounts,
    erc721AmountUnshieldAmounts,
    erc20AmountShieldRecipients,
    erc721AmountShieldRecipients,
    crossContractCalls,
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    transactionGasDetails
  );

  return populateResponse;
};
```

### Example Usage

```typescript
export const TEST_CrossContractCalls = async (
  encryptionKey: string,
  railgunWalletInfo: RailgunWalletInfo
) => {
  console.log("TEST_CrossContractCalls");
  const { wallet, provider } = getProviderWallet();
  // get gas estimate,
  // generate proof,
  // populate tx

  const TEST_AMOUNT = 1n;

  const erc20AmountUnshieldAmounts: RailgunERC20Amount[] = [
    serializeERC20RelayAdaptUnshield(
      TEST_TOKEN, // WETH
      TEST_AMOUNT
    ),
  ];

  const erc20AmountShieldRecipients: RailgunERC20Recipient[] = [
    serializeERC20Transfer(
      TEST_TOKEN, // WETH
      1n,
      railgunWalletInfo.railgunAddress
    ),
  ];
  const { wrappedAddress } = NETWORK_CONFIG[TEST_NETWORK].baseToken;
  const wethContract = new Contract(
    wrappedAddress,
    [
      "function deposit() payable",
      "function withdraw(uint256)",
      "function balanceOf(address) view returns (uint256)",
    ],
    provider
  );

  const unwrap = await wethContract.withdraw.populateTransaction(TEST_AMOUNT);
  const crossContractCalls: ContractTransaction[] = [
    {
      to: unwrap.to,
      data: unwrap.data, // unwrapeth
      value: 0n,
    },
  ];

  const minGasLimit = 2_500_000n; // high estimate but should be enough.
  // const overallBatchMinGasPrice = 1n;
  const gasEstimate = await crossContractGasEstimate(
    encryptionKey,
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc20AmountUnshieldAmounts,
    [],
    erc20AmountShieldRecipients,
    [],
    crossContractCalls,
    minGasLimit,
    true
  );

  console.log("Private CrossContract TX gasEstimate: ", gasEstimate);

  const transactionGasDetails = await getGasDetailsForTransaction(
    TEST_NETWORK,
    gasEstimate,
    true,
    wallet
  );
  const overallBatchMinGasPrice = calculateGasPrice(transactionGasDetails);

  // generate proof
  await crossContractGenerateProof(
    encryptionKey,
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc20AmountUnshieldAmounts,
    [],
    erc20AmountShieldRecipients,
    [],
    crossContractCalls,
    overallBatchMinGasPrice,
    minGasLimit,
    true
  );

  // populate tx
  const transaction = await crossContractCallsPopulateTransaction(
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc20AmountUnshieldAmounts,
    [],
    erc20AmountShieldRecipients,
    [],
    crossContractCalls,
    transactionGasDetails,
    overallBatchMinGasPrice,
    true
  );
  console.log("CrossContractCall transaction: ", transaction);
  // send private ERC20 tx.
  // submission via self-signed or public-broadcaster not shown.
};
```


# Unshielding

*Unshielding requires you to "Generate Proof" and, optionally, make use of "Relayers": See UX for Private Transactions* [*UX for Private Transactions* ](/developer-guide/wallet/transactions/ux-private-transactions)

Unshield *(*&#x6F;r Withdraw) transactions remove tokens from the RAILGUN contract. These encrypted transactions are commonly executed through a Relayer, which guarantees user anonymity.&#x20;

Unshield actions incur 0.25% fees, based on the amount of tokens unshielded.&#x20;


# Unshield ERC-20 tokens

*Unshielding requires you to "Generate Proof" and, optionally, make use of "Relayers": See UX for Private Transactions* [*UX for Private Transactions* ](/developer-guide/wallet/transactions/ux-private-transactions)

Unshield multiple ERC-20 tokens into a public wallet in a single transaction.

### Imports

```typescript
import {
  calculateGasPrice,
  NetworkName,
  TXIDVersion,
  type RailgunERC20AmountRecipient,
  type RailgunWalletInfo,
  type TransactionGasDetails,
} from "@railgun-community/shared-models";
import {
  gasEstimateForUnprovenUnshield,
  generateUnshieldProof,
  populateProvedUnshield,
} from "@railgun-community/wallet";
import {
  getGasDetailsForTransaction,
  getOriginalGasDetailsForTransaction,
  serializeERC20Transfer,
} from "../util";
import { TEST_NETWORK, TEST_TOKEN } from "../../utils/constants";
import { getProviderWallet } from "../../utils/provider";
```

### Gas Estimate

```typescript
export const erc20UnshieldGasEstimate = async (
  network: NetworkName,
  railgunWalletID: string,
  encryptionKey: string,
  erc20AmountRecipients: RailgunERC20AmountRecipient[]
) => {
  const sendWithPublicWallet = true;
  const originalGasDetails = await getOriginalGasDetailsForTransaction(
    network,
    sendWithPublicWallet
  );
  // dont setup broadcaster connection for simplicity.
  const feeTokenDetails = undefined;
  console.log("unshield originalGasDetails: ", originalGasDetails);

  const { gasEstimate } = await gasEstimateForUnprovenUnshield(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    erc20AmountRecipients,
    [], // nft amount recipients
    originalGasDetails,
    feeTokenDetails,
    sendWithPublicWallet
  );

  return gasEstimate;
};
```

### Generate Proof

```typescript
export const erc20UnshieldGenerateProof = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  tokenAmountRecipients: RailgunERC20AmountRecipient[],
  overallBatchMinGasPrice: bigint,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined
) => {
  const progressCallback = (progress: number) => {
    // Handle proof progress (show in UI).
    // Proofs can take 20-30 seconds on slower devices.
    console.log("Unshield ERC20 Proof progress: ", progress);
  };
  await generateUnshieldProof(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    tokenAmountRecipients,
    [], // nft amount recipients
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    progressCallback
  );
};

```

### Populate Transaction

```typescript

export const erc20UnshieldPopulateTransaction = async (
  network: NetworkName,
  railgunWalletID: string,
  tokenAmountRecipients: RailgunERC20AmountRecipient[],
  overallBatchMinGasPrice: bigint,
  transactionGasDetails: TransactionGasDetails,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined
) => {
  const populateResponse = await populateProvedUnshield(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    tokenAmountRecipients,
    [], // nftAmountRecipients
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    transactionGasDetails
  );

  return populateResponse;
};

```

### Example Usage

```typescript
export const TEST_ERC20Unshield = async (
  encryptionKey: string,
  railgunWalletInfo: RailgunWalletInfo
) => {
  console.log("TEST_ERC20Unshield");
  const { wallet } = getProviderWallet();
  const erc20AmountRecipients: RailgunERC20AmountRecipient[] = [
    serializeERC20Transfer(
      TEST_TOKEN, // WETH
      1n,
      wallet.address
    ),
  ];

  const gasEstimate = await erc20UnshieldGasEstimate(
    TEST_NETWORK,
    railgunWalletInfo.id,
    encryptionKey,
    erc20AmountRecipients
  );

  console.log("ERC20 UNSHIELD gasEstimate: ", gasEstimate);

  // generate unshield proof
  await erc20UnshieldGenerateProof(
    encryptionKey,
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc20AmountRecipients,
    1n
  );

  const transactionGasDetails = await getGasDetailsForTransaction(
    TEST_NETWORK,
    gasEstimate,
    true,
    wallet
  );

  const overallBatchMinGasPrice = await calculateGasPrice(
    transactionGasDetails
  );

  // populate tx

  const transaction = await erc20UnshieldPopulateTransaction(
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc20AmountRecipients,
    overallBatchMinGasPrice,
    transactionGasDetails
  );

  console.log("ERC20 UNSHIELD transaction", transaction);
  // send unshield tx
  // submission via self-signed tx or public-broadcaster
};
```


# Unshield base token

*Unshielding requires you to "Generate Proof" and, optionally, make use of "Relayers": See UX for Private Transactions* [*UX for Private Transactions* ](/developer-guide/wallet/transactions/ux-private-transactions)

Unshield only the base token for a network (eg. ETH) from shielded wETH in a single transaction.&#x20;

Base tokens cannot be shielded by themselves, as they are not ERC-20 tokens. They must be shielded as a wrapped token, eg. [wETH](https://weth.io/) 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.

### Imports

```typescript
import {
  calculateGasPrice,
  TXIDVersion,
  type NetworkName,
  type RailgunERC20Amount,
  type RailgunERC20AmountRecipient,
  type RailgunWalletInfo,
  type TransactionGasDetails,
} from "@railgun-community/shared-models";
import { TEST_NETWORK, TEST_TOKEN } from "../../utils/constants";
import {
  getGasDetailsForTransaction,
  getOriginalGasDetailsForTransaction,
} from "../util";
import {
  gasEstimateForUnprovenUnshieldBaseToken,
  generateUnshieldBaseTokenProof,
  populateProvedUnshieldBaseToken,
} from "@railgun-community/wallet";
import { getProviderWallet } from "../../utils/provider";
```

### Gas Estimate

```typescript
export const ethUnshieldGasEstimate = async (
  network: NetworkName,
  railgunWalletID: string,
  encryptionKey: string,
  erc20AmountRecipients: RailgunERC20Amount,
  destinationAddress: string
) => {
  const sendWithPublicWallet = true;
  const originalGasDetails = await getOriginalGasDetailsForTransaction(
    network,
    sendWithPublicWallet
  );

  // dont setup broadcaster connection for simplicity.
  const feeTokenDetails = undefined;
  console.log("unshield ETH originalGasDetails: ", originalGasDetails);

  const { gasEstimate } = await gasEstimateForUnprovenUnshieldBaseToken(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    destinationAddress,
    railgunWalletID,
    encryptionKey,
    erc20AmountRecipients,
    originalGasDetails,
    feeTokenDetails,
    sendWithPublicWallet
  );

  return gasEstimate;
};

```

### Generate Proof

```typescript
export const ethUnshieldGenerateProof = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  destinationAddress: string,
  tokenAmountRecipients: RailgunERC20Amount,
  overallBatchMinGasPrice: bigint,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined
) => {
  const progressCallback = (progress: number) => {
    // Handle proof progress (show in UI).
    // Proofs can take 20-30 seconds on slower devices.
    console.log("Unshield ETH Proof progress: ", progress);
  };

  await generateUnshieldBaseTokenProof(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    destinationAddress,
    railgunWalletID,
    encryptionKey,
    tokenAmountRecipients,
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    progressCallback
  );
};

```

### Populate Transaction

```typescript
export const ethUnshieldPopulateTransaction = async (
  network: NetworkName,
  railgunWalletID: string,
  destinationAddress: string,
  tokenAmountRecipients: RailgunERC20Amount,
  overallBatchMinGasPrice: bigint,
  transactionGasDetails: TransactionGasDetails,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined
) => {
  const populateResponse = await populateProvedUnshieldBaseToken(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    destinationAddress,
    railgunWalletID,
    tokenAmountRecipients,
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    transactionGasDetails
  );
  return populateResponse;
};
```

### Example Usage

```typescript
export const TEST_ETHUnshield = async (
  encryptionKey: string,
  railgunWalletInfo: RailgunWalletInfo
) => {
  const { wallet } = getProviderWallet();

  //
  const wrappedERC20Amount: RailgunERC20Amount = {
    tokenAddress: TEST_TOKEN, // wETH
    amount: 1n, //
  };

  // gas estimate
  const gasEstimate = await ethUnshieldGasEstimate(
    TEST_NETWORK,
    railgunWalletInfo.id,
    encryptionKey,
    wrappedERC20Amount,
    wallet.address
  );

  console.log("ETH UNSHIELD gasEstimate: ", gasEstimate);

  // generate eth unshield proof
  await ethUnshieldGenerateProof(
    encryptionKey,
    TEST_NETWORK,
    railgunWalletInfo.id,
    wallet.address,
    wrappedERC20Amount,
    1n // originalBatchMinGasPrice
  );
  const transactionGasDetails = await getGasDetailsForTransaction(
    TEST_NETWORK,
    gasEstimate,
    true,
    wallet
  );

  const overallBatchMinGasPrice = await calculateGasPrice(
    transactionGasDetails
  );

  // populate tx
  const transaction = await ethUnshieldPopulateTransaction(
    TEST_NETWORK,
    railgunWalletInfo.id,
    wallet.address,
    wrappedERC20Amount,
    overallBatchMinGasPrice,
    transactionGasDetails
  );
  console.log("ETH UNSHIELD transaction", transaction);
};
```


# Unshield NFTs

*Unshielding requires you to "Generate Proof" and, optionally, make use of "Relayers": See UX for Private Transactions* [*UX for Private Transactions* ](/developer-guide/wallet/transactions/ux-private-transactions)

Unshield multiple ERC-721 tokens into a public wallet in a single transaction.

### Imports

```typescript
import {
  calculateGasPrice,
  TXIDVersion,
  type NetworkName,
  type RailgunERC20AmountRecipient,
  type RailgunNFTAmountRecipient,
  type RailgunWalletInfo,
  type TransactionGasDetails,
} from "@railgun-community/shared-models";
import {
  getGasDetailsForTransaction,
  getOriginalGasDetailsForTransaction,
  serializeERC721Transfer,
} from "../util";
import {
  gasEstimateForUnprovenUnshield,
  generateUnshieldProof,
  populateProvedUnshield,
} from "@railgun-community/wallet";
import {
  TEST_NETWORK,
  TEST_NFT_ADDRESS,
  TEST_NFT_SUBID,
} from "../../utils/constants";
import { getProviderWallet } from "../../utils/provider";
```

### Gas Estimate

```typescript
export const erc721UnshieldGasEstimate = async (
  network: NetworkName,
  railgunWalletID: string,
  encryptionKey: string,
  erc721AmountRecipients: RailgunNFTAmountRecipient[]
) => {
  const sendWithPublicWallet = true;
  const originalGasDetails = await getOriginalGasDetailsForTransaction(
    network,
    sendWithPublicWallet
  );
  // dont setup broadcaster connection for simplicity.
  const feeTokenDetails = undefined;
  console.log("unshield originalGasDetails: ", originalGasDetails);
  const { gasEstimate } = await gasEstimateForUnprovenUnshield(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    [], // erc20AmountRecipients
    erc721AmountRecipients,
    originalGasDetails,
    feeTokenDetails,
    sendWithPublicWallet
  );

  return gasEstimate;
};

```

### Generate Proof

```typescript
export const erc721UnshieldGenerateProof = async (
  encryptionKey: string,
  network: NetworkName,
  railgunWalletID: string,
  erc721AmountRecipients: RailgunNFTAmountRecipient[],
  overallBatchMinGasPrice: bigint,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined
) => {
  const progressCallback = (progress: number) => {
    // Handle proof progress (show in UI).
    // Proofs can take 20-30 seconds on slower devices.
    console.log("Unshield ERC721 Proof progress: ", progress);
  };

  await generateUnshieldProof(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    encryptionKey,
    [],
    erc721AmountRecipients, // nft amount recipients
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    progressCallback
  );
};
```

### Populate Transaction

```typescript
export const erc721UnshieldPopulateTransaction = async (
  network: NetworkName,
  railgunWalletID: string,
  erc721AmountRecipients: RailgunNFTAmountRecipient[],
  overallBatchMinGasPrice: bigint,
  transactionGasDetails: TransactionGasDetails,
  sendWithPublicWallet: boolean = true,
  broadcasterFeeERC20AmountRecipient:
    | RailgunERC20AmountRecipient
    | undefined = undefined
) => {
  const populateResponse = await populateProvedUnshield(
    TXIDVersion.V2_PoseidonMerkle,
    network,
    railgunWalletID,
    [], // erc20AmountRecipients
    erc721AmountRecipients,
    broadcasterFeeERC20AmountRecipient,
    sendWithPublicWallet,
    overallBatchMinGasPrice,
    transactionGasDetails
  );

  return populateResponse;
};
```

### Example Usage

```typescript
export const TEST_ERC721Unshield = async (
  encryptionKey: string,
  railgunWalletInfo: RailgunWalletInfo
) => {
  console.log("TEST_ERC721Unshield");
  const { wallet } = getProviderWallet();
  const erc721AmountRecipients: RailgunNFTAmountRecipient[] = [
    serializeERC721Transfer(TEST_NFT_ADDRESS, TEST_NFT_SUBID, wallet.address),
  ];

  const gasEstimate = await erc721UnshieldGasEstimate(
    TEST_NETWORK,
    railgunWalletInfo.id,
    encryptionKey,
    erc721AmountRecipients
  );

  console.log("ERC721 UNSHIELD gasEstimate");

  // generate erc721 unshield proof
  await erc721UnshieldGenerateProof(
    encryptionKey,
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc721AmountRecipients,
    1n
  );

  const transactionGasDetails = await getGasDetailsForTransaction(
    TEST_NETWORK,
    gasEstimate,
    true,
    wallet
  );

  const overallBatchMinGasPrice = await calculateGasPrice(
    transactionGasDetails
  );

  const transaction = await erc721UnshieldPopulateTransaction(
    TEST_NETWORK,
    railgunWalletInfo.id,
    erc721AmountRecipients,
    overallBatchMinGasPrice,
    transactionGasDetails
  );

  console.log("ERC721 UNSHIELD transaction", transaction);
  // send unshield tx
  // submission via self-signed tx or public-broadcaster
};

```


# UX: Private Transactions

Proof Generation and Broadcaster are crucial pieces of the private transaction process. These steps each require complex interactions, and the user experience (UX) design is critical to ensure that private transactions are simplified for users.

### Order of Operations

There is a typical order of operations to every private transaction. The first two actions (***#1*** gas estimate and ***#2*** Broadcaster fee calculation) occur automatically. They should refresh any time a transaction's parameters change.&#x20;

***#3*** Generate Proof should occur after some action by the user, typically through a button. It's recommended to show a loading indicator through the proof generation process, as it can take up to 30 seconds depending on the device and the complexity of the transaction.

***#4*** Populate and ***#5*** Submit should occur on user submission.&#x20;

It is recommended as a two-part process with user interaction needed to begin ***#3*** and ***#4*** because of the time proof generation takes.

> Please note that each step can throw errors, so it's best practice to wrap calls in a try/catch in order to report the error message to the user.

#### 1. Gas Estimation&#x20;

First, we estimate the gas for a transaction.&#x20;

Gas Estimates must be re-estimated when any of the gas-estimate parameters change. Each parameter can impact the number of UTXO inputs and outputs, thereby changing the amount of gas to execute the transaction.

#### **2.** Broadcaster **Fee Calculation**

Next, the gas estimate is used to calculate the fee that is paid to a Broadcaster. Broadcasters typically charge small premiums on top of gas, and they accept a variety of ERC-20 tokens as payment.&#x20;

See [Broadcasters](/developer-guide/wallet/broadcasters) for connecting to the Broadcaster network and calculating the Broadcaster fee.

#### 3. Generate Proof for Transaction

This uses the [Groth16 Prover](/developer-guide/wallet/getting-started/6.-load-a-groth16-prover-for-each-platform) to build a zero-knowledge proof of a user's private transaction. Automatically downloads circuit assets if necessary.

The challenge here is for the user experience — a Broadcaster Fee expires after a certain period (usually around 3 minutes). Each Broadcaster broadcasts its current fees and expirations. If the fee expires, the transaction will need to be proved again to be valid.

#### 4. Populate Proved Transaction

This is where we generate the actual encrypted transaction.&#x20;

The "populate" method for each private transaction will validate the last generated proof and wrap it into a transaction. It then generates an encrypted PopulatedTransaction that is serialized for easy submission through a Broadcaster.

#### **5. Submit Transaction**&#x20;

Finally, [submit the transaction through a Broadcaster.](/developer-guide/wallet/broadcasters)

### Self-Signing Transactions

It is possible to use an owned public wallet to self-sign a transaction, rather than using a Broadcaster. While the contents of the transaction will be encrypted, the transaction itself will be associated with the self-signing public wallet on the blockchain.

In the self-signing flow, we can skip the ***#2*** Broadcaster Fee calculation entirely, and sign/send the serialized transaction to the blockchain like any other public transaction without submitting to a Broadcaster.

In order to retain anonymity, users can seed a new public wallet with gas using a [Base Token Unshield](/developer-guide/wallet/transactions/unshielding/unshield-base-token), so that none of their previous public transactions are associated with the new wallet. As long as the public wallet is only used to sign private transactions, this same level of anonymoty is achieved as when using a Broadcaster.


# Broadcasters

The Broadcaster network, forward private transactions on behalf of users and covering the associated gas costs. Users pay a small premium on top of the gas cost from their private balance to the Broadcaster's private balance in exchange for the service.

Broadcasters are only needed for transactions that originate from a 0zk address (i.e. not needed for [Shield](/developer-guide/wallet/transactions/shielding) transactions). Through Broadcaster, private transactions within the RAILGUN privacy system remain unlinked to public addresses, ensuring anonymity even during interactions with smart contracts.

Broadcasters broadcast their fees and accept transactions via the [Waku Network](https://docs.railgun.org/wiki/learn/privacy-system/community-relayers#waku-network) (as opposed to using HTTP requests) so that nobody can snoop on a user's IP address when submitting a transaction.

Use the [@railgun-community/waku-broadcaster-client](https://github.com/Railgun-Community/waku-broadcaster-client) package to find Broadcasters and submit transactions anonymously.

For web projects install:

```
npm install @railgun-community/waku-broadcaster-client-web
```

For mobile projects install:

```
npm install @railgun-community/waku-broadcaster-client-node
```

### Imports

```typescript
import {
  NETWORK_CONFIG,
  type CachedTokenFee,
  type FeeTokenDetails,
  type NetworkName,
  type SelectedBroadcaster,
  type TransactionGasDetails,
} from "@railgun-community/shared-models";
import { calculateBroadcasterFeeERC20Amount } from "@railgun-community/wallet";
const wakuModule = import("@railgun-community/waku-broadcaster-client-node");

let WakuBroadcasterClient: any;
let BroadcasterTransaction: any;

let waku: any;
```

### Initialize the broadcaster client

```typescript
export const initializeBroadcasters = async (network: NetworkName) => {
  if (!waku) {
    waku = await wakuModule;
  }
  WakuBroadcasterClient = waku.WakuBroadcasterClient;
  BroadcasterTransaction = waku.BroadcasterTransaction;

  const { chain } = NETWORK_CONFIG[network];
  const options = {};
  const callback = (chain: any, status: any) => {
    if (status !== "Connected") {
      console.log(`WAKU ${chain.id}:${chain.type} ${status}`);
    }
  };
  const debugLogger = {
    log: (_msg: string) => {
      console.log(msg);
    },
    error: (error: Error) => {
      console.error(error);
    },
  };
  WakuBroadcasterClient.start(chain, options, callback, debugLogger);
};
```

The initialized RailgunWakuBroadcasterClient will continue monitoring the connection to the Broadcaster network throughout the app session. It will call the `statusCallback` function with updates, and you must wait to use a Broadcaster until the status is `Connected`.

### Finding a Broadcaster

When you are connected to the Broadcaster network and you are ready to submit a [private transaction](/developer-guide/wallet/transactions), you must first select a token from the user's [private balance](/developer-guide/wallet/private-balances/balance-and-sync-callbacks) to pay the Broadcaster fee (likely from user input).

Next, we need to find the best Broadcaster based on the user's selected fee token and the fee amounts that the Broadcasters are broadcasting.

```typescript
export const getBroadcasterFeeInfo = async (
  network: NetworkName,
  tokenAddress: string
): Promise<SelectedBroadcaster> => {
  const { chain } = NETWORK_CONFIG[network];
  return await WakuBroadcasterClient.findBestBroadcaster(
    chain,
    tokenAddress,
    true
  );
};
```

If no Broadcaster is available that accepts fees in the selected token, then the `findBestBroadcaster` function will return undefined.

### Calculating the Broadcaster Fee

Once we have a `SelectedBroadcaster`, we need to calculate the total Broadcaster fee for our transaction based on the current gas price, gas estimate of the transaction, and the fee that the Broadcaster charges.

```typescript
export const getBroadcasterFeeRecipientDetails = async (
  selectedBroadcaster: SelectedBroadcaster,
  estimatedGasDetails: TransactionGasDetails,
  feeTokenDetails: FeeTokenDetails
) => {
  const broadcasterFeeAmountDetails = calculateBroadcasterFeeERC20Amount(
    feeTokenDetails,
    estimatedGasDetails
  );

  const broadcasterFeeERC20AmountRecipient = {
    tokenAddress: broadcasterFeeAmountDetails.tokenAddress,
    amount: broadcasterFeeAmountDetails.amount,
    recipientAddress: selectedBroadcaster.railgunAddress,
  };

  return broadcasterFeeERC20AmountRecipient;
};
```

### Submit via Broadcaster

```typescript
import {
  RailgunPopulateTransactionResponse
} from '@railgun-community/shared-models';

const populateResponse: RailgunPopulateTransactionResponse = ...; // See the examples in the "Transactions" section for getting this value
const overallBatchMinGasPrice: bigint = ...; // See the examples in the "Transactions" section for getting this value

const selectedBroadcaster = ...; // Same as examples above
const chain: Chain = ...; // Same as examples above
const useRelayAdapt = ...; // Same as examples above
const nullifiers: string[] = populateResponse.nullifiers ?? [];

const broadcasterTransaction = await BroadcasterTransaction.create(
  populateResponse.transaction.to,
  populateResponse.transaction.data,
  selectedBroadcaster.railgunAddress,
  selectedBroadcaster.tokenFee.feesID,
  chain,
  nullifiers,
  overallBatchMinGasPrice,
  useRelayAdapt,
);
const txHash = await broadcasterTransaction.send();
```


# Broadcasters (DELETE)

### Imports

```typescript
import {
  NETWORK_CONFIG,
  type CachedTokenFee,
  type FeeTokenDetails,
  type NetworkName,
  type SelectedBroadcaster,
  type TransactionGasDetails,
} from "@railgun-community/shared-models";
import { calculateBroadcasterFeeERC20Amount } from "@railgun-community/wallet";
const wakuModule = import("@railgun-community/waku-broadcaster-client-node");
// let wakuModule: any;

let WakuBroadcasterClient: any;
let BroadcasterTransaction: any;

let waku: any; 
```

### Setup Connection to broadcaster network.&#x20;

```typescript
export const initializeBroadcasters = async (network: NetworkName) => {
  if (!waku) {
    waku = await wakuModule;
  }
  WakuBroadcasterClient = waku.WakuBroadcasterClient;
  BroadcasterTransaction = waku.BroadcasterTransaction;

  const { chain } = NETWORK_CONFIG[network];
  const options = {};
  const callback = (chain: any, status: any) => {
    if (status !== "Connected") {
      console.log(`WAKU ${chain.id}:${chain.type} ${status}`);
    }
  };
  const debugLogger = {
    log: (_msg: string) => {
      console.log(msg);
    },
    error: (error: Error) => {
      console.error(error);
    },
  };
  WakuBroadcasterClient.start(chain, options, callback, debugLogger);
};

```

### Utility Functions

```typescript
export const getBroadcasterFeeInfo = async (
  network: NetworkName,
  tokenAddress: string
): Promise<SelectedBroadcaster> => {
  const { chain } = NETWORK_CONFIG[network];
  return await WakuBroadcasterClient.findBestBroadcaster(
    chain,
    tokenAddress,
    true
  );
};

export const getBroadcasterFeeRecipientDetails = async (
  selectedBroadcaster: SelectedBroadcaster,
  estimatedGasDetails: TransactionGasDetails,
  feeTokenDetails: FeeTokenDetails
) => {
  const broadcasterFeeAmountDetails = calculateBroadcasterFeeERC20Amount(
    feeTokenDetails,
    estimatedGasDetails
  );

  const broadcasterFeeERC20AmountRecipient = {
    tokenAddress: broadcasterFeeAmountDetails.tokenAddress,
    amount: broadcasterFeeAmountDetails.amount,
    recipientAddress: selectedBroadcaster.railgunAddress,
  };

  return broadcasterFeeERC20AmountRecipient;
};

export const getFeeTokenDetails = async (
  network: NetworkName,
  tokenAddress: string,
  sendWithPublicWallet: boolean = true
) => {
  if (sendWithPublicWallet) {
    return {
      broadcaster: undefined,
      feeTokenDetails: undefined,
    };
  }
  const broadcaster = await getBroadcasterFeeInfo(network, tokenAddress);
  const feeTokenDetails: FeeTokenDetails = {
    tokenAddress: broadcaster.tokenAddress,
    feePerUnitGas: BigInt(broadcaster.tokenFee.feePerUnitGas),
  };

  return {
    broadcaster,
    feeTokenDetails,
  };
};

export const getBroadcasterDetails = async (
  estimatedGasDetails: TransactionGasDetails,
  selectedBroadcaster: SelectedBroadcaster | undefined,
  feeTokenDetails: FeeTokenDetails | undefined
) => {
  if (!selectedBroadcaster || !feeTokenDetails) {
    return undefined;
  }
  // gets a broadcaster from the waku client based on tokenAddress

  // const { broadcaster, feeTokenDetails } = await getFeeTokenDetails(
  //   network,
  //   tokenAddress
  // );

  const feeRecipientDetails = await getBroadcasterFeeRecipientDetails(
    selectedBroadcaster,
    estimatedGasDetails,
    feeTokenDetails
  );

  const broadcasterFeeERC20AmountRecipient = {
    tokenAddress: feeRecipientDetails.tokenAddress,
    amount: feeRecipientDetails.amount,
    recipientAddress: feeRecipientDetails.recipientAddress,
  };

  return {
    selectedBroadcaster,
    broadcasterFeeERC20AmountRecipient,
  };
};

```


# Cookbook Overview

RAILGUN Cookbook: Convert your dApp into a zkApp

The [RAILGUN C](https://github.com/Railgun-Community/quickstart)[ookbook](https://github.com/Railgun-Community/cookbook) is an open-source project developed by RAILGUN contributors.&#x20;

RAILGUN Cookbook enables web3 developers to build simple zkApps that are compatible with RAILGUN private balances on Ethereum, Arbitrum, Polygon and more.&#x20;

With Cookbook, users can activate your smart contract privately and anonymously.

### Use cases

RAILGUN Cookbook converts a series of smart contract calls into a "Recipe" that can have both ERC-20 and NFT inputs/outputs. There are a variety of use cases that are possible – all entirely private and anonymous through the RAILGUN Privacy System:

* *PRIVATE* Vault Deposits&#x20;
* *PRIVATE* Stablecoin Minting
* *PRIVATE* Liquidity Provisioning (LP)
* *PRIVATE* Liquid Staking
* *PRIVATE* Wrapping/Unwrapping tokens
* *+* many more

### Get the Recipe Builder

```bash
yarn add @railgun-community/cookbook@2.6.0
```

### Links

***Git repo***: <https://github.com/Railgun-Community/cookbook><br>


# Recipe Guide: Write a zkApp

dApp -> zkApp in 3 easy steps

*Hey there*, enterprising Web3 chef.&#x20;

Welcome to the exciting new world of RAILGUN zkApps.

With RAILGUN, you can give users the gift of privacy for nearly any decentralized app that runs via smart contracts. Cookbook makes it easy — numerous automated tests and code-validation help to ensure that users' funds will be safe every "Step" of the way.

### How it works

The RAILGUN Cookbook gives you the tools to write a "Recipe" to convert your dApp into a zkApp — [private and fully anonymous](https://docs.railgun.org/wiki/learn/privacy-system) for users. Your Recipe can interact with any smart contract using your private balance in [RAILGUN](https://docs.railgun.org/wiki/learn/overview).&#x20;

A Recipe contains a series of smart contact calls that are combined into a multi-call, which is actuated using [Wallet SDK](/developer-guide/wallet/private-wallets), or any RAILGUN-compatible wallet.


# "Step" — A smart contract call

Recipes are composed of "Steps," which are enclosed smart contract calls. Every Step has a set of inputs that correspond to a set of outputs: Spent tokens, Output tokens, and Fees. Spent tokens and Fees are eradicated during the Step, and Output tokens will be passed into the next Step as inputs.

For example, a simple [0x Exchange Swap Step](https://github.com/Railgun-Community/cookbook/blob/main/src/steps/swap/zero-x/zero-x-swap-step.ts) will contain a Sell Token as an input, and the outputs will include Sell Token (Spent), Buy Token (Output), and no associated Fees.

### Automatic Validation

Step inputs and outputs are automatically validated to ensure that each input has associated outputs that represent the total value. There is an exception for tokens that are generated mid-Step — new token values become unvalidated Output tokens — they are simply passed to the next Step as inputs.

**Annotated Example:** ERC20 spender Approval

```typescript
//
// Note that each Step/Recipe must assume a clean slate: Since it will
//   get executed in a public setting (the RAILGUN Relay Adapt Contract),
//   developers should assume that the contract does NOT have 
//   approval to spend tokens with any token contract. 
// 
// Each Recipe should approve their tokens fresh for each user.
//

export class ApproveERC20SpenderStep extends Step {
  // Name and description, which appear in the recipe's stepOutputs for clarity.
  // `hasNonDeterministicOutput` is used to validate this step - if it has 
  // deterministic outputs, this means that the outputs are guaranteed, no matter 
  // if the underlying assets change in price. Examples of non-deterministic 
  // Steps include Swaps (with slippage) and Adding/Removing LP
  // (where liquidity rates are based on moving asset prices).
  readonly config = {
    name: 'Approve ERC20 Spender',
    description: 'Approves ERC20 for spender contract.',
    hasNonDeterministicOutput: false,
  };

  // Private variables passed into the constructor.
  private readonly spender: Optional<string>;
  private readonly tokenInfo: RecipeERC20Info;
  private readonly amount: Optional<BigNumber>;

  // Note that amount is an optional parameter here. If unset, 
  // the Step will assume the maximum for approval. This is because 
  // some steps don't have deterministic input amounts 
  // (such as after a swap with slippage). 
  // For non-deterministic amounts, we'll auto-approve the maximum,
  // so we don't mistakenly underestimate the actual amount.
  constructor(
    spender: Optional<string>,
    tokenInfo: RecipeERC20Info,
    amount?: BigNumber,
  ) {
    super();
    this.spender = spender;
    this.tokenInfo = tokenInfo;
    this.amount = amount;
  }

  protected async getStepOutput(
    input: StepInput,
  ): Promise<UnvalidatedStepOutput> {
    // If we can't find the spender contract, create a null output with no
    // populatedTransactions.
    if (!this.spender || this.tokenInfo.isBaseToken) {
      return createNoActionStepOutput(input);
    }

    // These are the input parameters, passed into the step.
    const { erc20Amounts, networkName } = input;

    // This helper function filters the input erc20 amounts for a token whose address 
    // matches tokenInfo (non-case-sensitive), which is not yet approved for spending.
    // It also validates the expected balance of this token from prior Steps, 
    // if `this.amount` is provided.
    const { erc20AmountForStep, unusedERC20Amounts } =
      this.getValidInputERC20Amount(
        erc20Amounts,
        erc20Amount =>
          compareERC20Info(erc20Amount, this.tokenInfo) &&
          erc20Amount.approvedSpender !== this.spender,
        this.amount,
      );

    const contract = new ERC20Contract(erc20AmountForStep.tokenAddress);
    
    // If there's no amount provided, we'll approve the maximum.
    const approveAmount = this.amount ?? maxBigNumberForTransaction();

    // This is the final populated contract call, generated with the ERC20 ABI.
    const crossContractCalls: ContractTransaction[] = [];
    crossContractCalls.push(
      await contract.createSpenderApproval(this.spender, approveAmount),
    );
    
    // Convert the selected input erc20 amount into the final spender-approved 
    // amount, which will be an output of this Step.
    // In a later Step in a full Recipe, we can call `getValidInputERC20Amount` 
    // (like above) to check that this input token is approved.
    const approvedERC20Amount: StepOutputERC20Amount = {
      tokenAddress: erc20AmountForStep.tokenAddress,
      decimals: erc20AmountForStep.decimals,
      isBaseToken: erc20AmountForStep.isBaseToken,
      expectedBalance: minBigNumber(
        approveAmount,
        erc20AmountForStep.expectedBalance,
      ),
      minBalance: minBigNumber(approveAmount, erc20AmountForStep.minBalance),
      approvedSpender: this.spender,
    };

    // Return the approved amount, as well as any unused erc20 amounts.
    // These outputs will get validated against the input amounts for this Step,
    // ensuring that no input tokens are missing in the outputs.
    // If any tokens are "spent" during the Step, you should add them 
    // to the spentERC20Amounts array.
    // Any token fees paid during the call should go in feeERC20AmountRecipients, 
    // including a string identifier for the recipient.
    return {
      crossContractCalls,
      spentERC20Amounts: [],
      outputERC20Amounts: [approvedERC20Amount, ...unusedERC20Amounts],
      spentNFTs: [],
      outputNFTs: input.nfts,
      feeERC20AmountRecipients: [],
    };
  }
```


# "Recipe" — Steps in series

Recipes combine Steps into functional, complex actions. Most integrations will require 1-2 Recipes, and a number of Steps for each Recipe. Steps are generic building blocks, making them multi-purpose and reusable for various Recipes. Upon execution (`recipe.getRecipeOutput(recipeInput)`), the Cookbook automatically sandwiches the Recipe's transactions inside of [Unshield](https://docs.railgun.org/wiki/learn/unshielding-tokens) and [Shield](https://docs.railgun.org/wiki/learn/shielding-tokens) calls, calculating the associated fees with each Step, and providing the developer with a formatted list of Steps, their outputs, and the final array of [populated transactions](https://docs.ethers.org/v5/api/utils/transactions/).

As an example, a simple 0x Exchange Swap call has a pre-requisite: the Sell Token must be approved for spending by the 0x contract. So, the [0x Swap Recipe](https://github.com/Railgun-Community/cookbook/blob/main/src/recipes/swap/zero-x-swap-recipe.ts) has two Steps: (1) Approve sell token, (2) Swap sell token for buy token. The full Recipe uses a Step called [ApproveERC20SpenderStep](https://github.com/Railgun-Community/cookbook/blob/main/src/steps/token/erc20/approve-erc20-spender-step.ts), which is a common Step among most integrations.

> Note that each Recipe must assume a clean slate – since it's executed in a public setting (the RAILGUN Relay Adapt Contract), developers should assume that the Relay Adapt contract does not have approval to spend tokens with any token contract. This is why the [ApproveERC20SpenderStep](https://github.com/Railgun-Community/cookbook/blob/main/src/steps/token/erc20/approve-erc20-spender-step.ts) is a basic requirement for nearly every Recipe.

**Annotated Example:** Beefy Vault Deposit

```typescript
export class BeefyDepositRecipe extends Recipe {
  // Simple name and description.
  readonly config = {
    name: 'Beefy Vault Deposit',
    description:
      'Auto-approves and deposits tokens into a yield-bearing Beefy Vault.',
  };

  // Private variable passed into the constructor.
  protected readonly vaultID: string;

  // Recipe constructors should contain details about the enclosed tokens 
  // (ERC20 or NFTs), but they should not contain specific amounts.
  // Input amounts are passed into `getInternalSteps()`, calculated for each
  // run based on prior Steps (which typically include Unshield and its Fees).
  constructor(vaultID: string) {
    super();
    this.vaultID = vaultID;
  }

  // A required implementation for all Recipes, which designates the networks
  // where the recipe is possible.
  // Switch statements highly recommended here.
  protected supportsNetwork(networkName: NetworkName): boolean {
    return BeefyAPI.supportsNetwork(networkName);
  }

  // This async call returns the steps to build this recipe.
  // These steps will be housed inside of a cross-contract call, sandwiched
  // by Unshield and Shield steps. 
  // The firstInternalStepInput contains the network details and erc20 amounts 
  // (adjusted for fees) that will be passed into the first Step of this Recipe.
  // Most Recipes will calculate amounts (for fees - or expected output values) 
  // which depend on the input amounts.
  protected async getInternalSteps(
    firstInternalStepInput: StepInput,
  ): Promise<Step[]> {
    const { networkName } = firstInternalStepInput;

    // Async Beefy API call to get Vault contract addresses and current rates.
    const vault = await BeefyAPI.getBeefyVaultForID(this.vaultID, networkName);
    const spender = vault.vaultContractAddress;
    
    // ERC20 token to approve before Beefy Deposit.
    const depositERC20Info: RecipeERC20Info = {
      tokenAddress: vault.depositERC20Address,
      decimals: vault.depositERC20Decimals,
    };
    
    // Two Steps in this Recipe - (1) Approval then (2) Deposit
    return [
      new ApproveERC20SpenderStep(spender, depositERC20Info),
      new BeefyDepositStep(vault),
    ];
  }
}
```


# "Combo Meal" — 2+ Recipes

Combo Meals are the final frontier — every zkApp Chef's dream. They combine Recipes into very complex interactions, made 100% safe for execution against a private balance using the Cookbook.

For example, there is a [Combo Meal](https://github.com/Railgun-Community/cookbook/blob/main/src/combo-meals/liquidity-vault/uni-v2-like-add-liquidity-beefy-deposit-combo-meal.ts) that combines an "Add Liquidity" Recipe for Uniswap, with a "Deposit Vault" Recipe for Beefy. This gives a user the ability to add liquidity for a token pair on Uniswap, gain the LP token for that pair, and then deposit the LP token into a Beefy Vault to earn yield.

This all occurs in a single validated transaction call, saving network fees and making the user experience simple and delightful.

**Annotated Example:** Uniswap V2-Like (Add Liquidity) and Beefy Vault (Deposit LP Token)

```typescript
export class UniV2LikeAddLiquidity_BeefyDeposit_ComboMeal extends ComboMeal {
  // Simple name and description. Note that the name of the Uniswap clone is set
  // in the constructor.
  readonly config = {
    name: '[NAME] Add Liquidity + Beefy Vault Deposit Combo Meal',
    description:
      'Adds liquidity to a [NAME] Pool and deposits the LP tokens into a Beefy Vault.',
  };

  // Private variables set during the constructor.
  // In this case, it's a store for the two Recipes that we will combine together.
  private readonly uniV2LikeAddLiquidityRecipe: UniV2LikeAddLiquidityRecipe;
  private readonly beefyDepositRecipe: BeefyDepositRecipe;

  constructor(
    uniswapV2Fork: UniswapV2Fork,
    erc20InfoA: RecipeERC20Info,
    erc20InfoB: RecipeERC20Info,
    slippagePercentage: number,
    vaultID: string,
    provider: BaseProvider,
  ) {
    super();

    // The Recipes are fully initialized here. Note that 
    // input amounts are never passed into Recipe/Combo constructors.
    this.uniV2LikeAddLiquidityRecipe = new UniV2LikeAddLiquidityRecipe(
      uniswapV2Fork,
      erc20InfoA,
      erc20InfoB,
      slippagePercentage,
      provider,
    );
    this.beefyDepositRecipe = new BeefyDepositRecipe(vaultID);

    const forkName = UniV2LikeSDK.getForkName(uniswapV2Fork);
    this.config.name = `${forkName} Add Liquidity + Beefy Vault Deposit Combo Meal`;
    this.config.description = `Adds liquidity to a ${forkName} Pool and deposits the LP tokens into a Beefy Vault.`;
  }

  // This is a helper function that helps a user know how much of "ERC20 B" to unshield
  // for a given amount of "ERC20 A" (A and B make up the liquidity pair).
  // Accuracy is important, as the unshielding operation incurs a small % fee.
  getAddLiquidityAmountBForUnshield(
    networkName: NetworkName,
    targetUnshieldERC20AmountA: RecipeERC20Amount,
  ) {
    return this.uniV2LikeAddLiquidityRecipe.getAddLiquidityAmountBForUnshield(
      networkName,
      targetUnshieldERC20AmountA,
    );
  }

  // Returns the two Recipes that create this "Combo Meal." The steps of each 
  // Recipe will be strung together in series, and sandwiched altogether 
  // by an unshield and shield call. The outputs from the 1st Recipe will not 
  // get automatically re-shielded - they will get passed into the 2nd Recipe 
  // as inputs for its first step.
  // In this way, we can create in a single transaction call, minimizing fees and 
  // building a complex DeFi behavior as a sum of simple parts.
  protected async getRecipes(): Promise<Recipe[]> {
    return [this.uniV2LikeAddLiquidityRecipe, this.beefyDepositRecipe];
  }
}
```


# Use your zkApp privately

"Cook a Recipe" anonymously with RAILGUN Quickstart

Given a full Recipe and its inputs, [RAILGUN Wallet SDK](/developer-guide/wallet/transactions/cross-contract-calls) will generate a [zero-knowledge proof](https://docs.railgun.org/wiki/learn/privacy-system/zero-knowledge-cryptography) and a final serialized transaction for the RAILGUN Relay Adapt contract.

This final transaction can be submitted to the blockchain by any wallet, including a [Broadcaster](/developer-guide/wallet/broadcasters).

{% code title="cook-recipe-example.ts" %}

```typescript
import {
  RecipeERC20Info,
  ZeroXSwapRecipe,
  RecipeInput,
  RecipeOutput
} from '@railgun-community/cookbook';
import {
  NetworkName,
} from '@railgun-community/shared-models';

const sellERC20Info: RecipeERC20Info = {
  tokenAddress: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI
  decimals: BigInt(18)
}

const buyERC20Info: RecipeERC20Info = {
  tokenAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT
  decimals: BigInt(18)
}

const slippagePercentage = 0.05;

const swap = new ZeroXSwapRecipe(sellERC20Info, buyERC20Info, slippagePercentage);

// Inputs that will be unshielded from private balance.
const unshieldERC20Amounts = [
  { 
    ...sellERC20Info,
    amount: BigInt('0x10'), // hexadecimal amount
  }
];

const recipeInput: RecipeInput = { NetworkName.Ethereum, unshieldERC20Amounts };
const {
  crossContractCalls,
  erc20AmountRecipients,
  nftRecipients,
  feeERC20AmountRecipients,
  minGasLimit,
} = await swap.getRecipeOutput(recipeInput);

// Outputs to re-shield after the Recipe multicall.
const shieldERC20Addresses = erc20AmountRecipients.map(({tokenAddress}) => tokenAddress);

// RAILGUN Wallet will generate a [unshield -> call -> re-shield] 
// transaction enclosing the Recipe multicall.

const { gasEstimate } = await gasEstimateForUnprovenCrossContractCalls(
    ...
    unshieldERC20Amounts,
    ...
    shieldERC20Addresses,
    ...
    crossContractCalls,
    ...
)
await generateCrossContractCallsProof(
    ...
    unshieldERC20Amounts,
    ...
    shieldERC20Addresses,
    ...
    crossContractCalls,
    ...
)
const { transaction } = await populateProvedCrossContractCalls(
    ...
    unshieldERC20Amounts,
    ...
    shieldERC20Addresses,
    ...
    crossContractCalls,
    ...
);

// Submit transaction to RPC.
await wallet.sendTransaction(transaction);

// Note: use @railgun-community/waku-broadcaster-client to submit 
// through a Broadcaster instead of signing with your own wallet.
```

{% endcode %}


# Engine Overview

RAILGUN Engine SDK for advanced RAILGUN integrations

***Full documentation coming soon.***

The RAILGUN Engine SDK is primarily for wallet developers that wish to enable private balances and transactions through RAILGUN.

If you are a wallet developer that would like to integrate RAILGUN, please reach out to <mail@railgun.org>.

### Links

*Git repo*: [https://github.com/Railgun-Community/engine](https://github.com/Railgun-Community/quickstart)


# Account Abstraction Overview

This document assumes a base level knowledge of Solidity/EVM and zkSNARKs.

*The purpose of this document is to accelerate the on-boarding process for developers looking to build advanced applications that require directly interacting with lower level functions within RAILGUN, there are less guardrails in place when building these applications. If your application can be implemented as a multicall you should instead go to the* [*cookbook overview*](https://docs.railgun.org/developer-guide/cookbook/cookbook-overview)*.*

RAILGUN private balances are a form of ZK based Account Abstraction that pre-dates ERC-4337.

Rather than requiring users to sign transactions with their own wallet and revealing addresses, transaction verification happens through a SNARK Proof verifying against a shielded Merkle Tree of balances. This enables interesting utility like RAILGUN [gasless transactions](https://docs.railgun.org/wiki/learn/using-shielded-tokens/gasless-transactions), where private transactions can be sent using stablecoins or any other token accepted by Relayers as opposed to the chain’s native gas token like ETH.

This utility from ZK Account Abstraction can be extended in even more interesting directions than just gasless transactions such as:

* Private voting using RAILGUN balances, enabling anonymous voting based on ERC-20s or NFTs
* ZK Payments infrastructure to support use cases like private bounty or salary payments
* Sealed transfer of tokens between 2 users at a pre-agreed upon price or condition


# Getting started with the contracts

All contract code can be viewed in the [contracts repo](https://github.com/railgun-privacy/contract). Deployment addresses (including testnet deployments) can be viewed in the [deployments repo](https://github.com/railgun-community/deployments).

To setup a local development environment:

1. Clone the contracts repo (`git clone git@github.com:railgun-privacy/contract.git`)
2. Install dependencies (`cd contract && npm install`)
3. Run a local development network in a new terminal window (`cd contract && npx hardhat node`)
4. Deploy contracts to development network (`npx hardhat deploy:test --network localhost`)

After running the `deploy:test` hardhat script you should see something like the following printed to your terminal:

```json
{
  "delegator": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707",
  "governorRewardsImplementation": "",
  "governorRewardsProxy": "",
  "implementation": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
  "proxy": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788",
  "proxyAdmin": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6",
  "rail": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0",
  "staking": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9",
  "testERC20": "0x202CCe504e04bEd6fC0521238dDf04Bc9E8E15aB",
  "testERC721": "0xf4B146FbA71F41E0592668ffbF264F1D186b2Ca8",
  "treasuryImplementation": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853",
  "treasuryProxy": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
  "voting": "0x0165878A594ca255338adfa4d48449f69242Eb8F",
  "weth9": "0x8198f5d8F8CfFE8f9C413d98a0A55aEB8ab9FbB7",
  "relayAdapt": "0x0355B7B8cb128fA5692729Ab3AAa199C1753f726"
}
```

You will primarily be interacting with the `proxy` contract which is a EIP1967 `Transparent Upgradeable Proxy` for the `implementation` contract. Functions on the proxy are only accessible when called by the`proxyAdmin` contract so function selector clashes are not an issue.


# Wallets

You will need to use a RAILGUN wallet to synchronize state and access data for your application. You can use the [Wallet](/developer-guide/engine-1/wallets) and [Engine](https://docs.railgun.org/developer-guide/engine/engine-overview) SDKs for this purpose.


# State Structure

Core state is maintained using 2 data structures in the [`Commitments`](https://github.com/Railgun-Privacy/contract/blob/2afd011854daf35aad155a91d6bf2397e39ca90d/contracts/logic/Commitments.sol) contract:

1. An accumulator, in this case an incremental Merkle Tree.
2. A nullifier boolean mapping.

These values can be accessed through the [`merkleRoot`](https://github.com/Railgun-Privacy/contract/blob/2afd011854daf35aad155a91d6bf2397e39ca90d/contracts/logic/Commitments.sol#L39), [`rootHistory`](https://github.com/Railgun-Privacy/contract/blob/2afd011854daf35aad155a91d6bf2397e39ca90d/contracts/logic/Commitments.sol#L58), and [`nullifiers`](https://github.com/Railgun-Privacy/contract/blob/2afd011854daf35aad155a91d6bf2397e39ca90d/contracts/logic/Commitments.sol#L27) variables respectively.

New state objects (UTXOs) are appended to the Merkle Tree and emitted in contract events. Consumed state is invalidated by adding adding the nullifier to the mapping ensuring that old state can't be used twice without compromising privacy.

The contract maintains a mapping of the current and historical Merkle Tree roots so that proofs can be created against the Merkle Tree.

The RAILGUN contract verifies a a set of transaction circuits (JoinSplit with varying input and output UTXO counts) which are the only circuits allowed to write state changes.

External contracts have two primary means of adding functionality on top of the private state:

1. Using the Merkle Tree + nullifier set to generate read-only proofs.
2. Using the RAILGUN JoinSplit circuit with the adapt fields to add additional verification to state transitions.

These 2 ways of interacting with the core RAILGUN contracts can be combined to enable a number of use cases. Some examples are contained in the next section.


# Example Primitives

### Read Only Proofs

Read only proofs can either take a snapshot of the RAILGUN contract state at a particular point in time by saving the current Merkle root or allow proofs to be generated against the current contract state by checking if the proof is generated against a current or historical Merkle root.

SNARK validation will need to occur within your own smart contract, but you will pull the state data from the RAILGUN core contracts allowing you to prove arbitrary details over the RAILGUN state.

**Semaphores**

A basic proof of membership can be built on top of the RAILGUN account state with Read Only Proofs. A membership set can be constructed for eg. holders of an NFT within their RAILGUN private balance, or holders of a particular ERC20 token for NFT or token weighted voting.

The basic checks required for this construction are:

* Proof is generated against a current or historical Merkle root (or the snapshotted Merkle root if snapshots are being used)
* The UTXO is part of the Merkle Tree and the contents of the UTXO match your application parameters (eg. is an NFT from a certain collection or is a balance of a certain ERC20)
* The global nullifier hasn't been seen by the RAILGUN contract
* The application nullifier hasn't been seen by your application contract

**Limitations**

Snapshots of the nullifier set can't be taken in the current architecture. Spending a UTXO after a snapshot is taken will result in not being able to generate a proof for that UTXO.

For this reason it is preferable to generate proofs against the current state instead of a snapshot where possible. For private NFT voting you can avoid using snapshots by using the NFT ID in the application nullifier to prevent double-voting instead, but in private ERC20 voting snapshots are unavoidable.

### Conditional Transactions

Transactions can have additional validation rules attached to them by an external contract enabled by the adapt field. You will need to set 2 values on the transaction proof generated by a RAILGUN wallet to enable this: `adaptContract` and `adaptParams`. Setting the `adaptContract` field locks the transaction to only being valid if the address specified in `adaptContract` is `msg.sender` (see [here](https://github.com/Railgun-Privacy/contract/blob/2afd011854daf35aad155a91d6bf2397e39ca90d/contracts/logic/RailgunLogic.sol#L427)). The `adaptParams` field is a 32 byte field bound to the SNARK proof but isn't validated by the core RAILGUN contract letting you implement your own validation rules, likely a hash of the contract.

[Cookbook](https://docs.railgun.org/developer-guide/cookbook/cookbook-overview) uses a multicall contract that allows arbitrary contract calls to be bound to RAILGUN transactions through this method. You can see how this is implemented [here](https://github.com/Railgun-Privacy/contract/blob/2afd011854daf35aad155a91d6bf2397e39ca90d/contracts/adapt/Relay.sol#LL320).

**Atomic Swaps**

Conditional payments can be used to implement an atomic swap protocol. You can include the commitment hash for a UTXO in the `adaptParams` field. Your atomic swap settlement contract will need to accept a bundle of 2 (or more) transactions and validate that the UTXO commitment hash for each transaction is paid into by another transaction in the submitted bundle.


