"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 and Shield 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.

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 has two Steps: (1) Approve sell token, (2) Swap sell token for buy token. The full Recipe uses a Step called ApproveERC20SpenderStep, 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 is a basic requirement for nearly every Recipe.

Annotated Example: Beefy Vault Deposit

export class BeefyDepositRecipe extends Recipe {
  // Simple name and description.
  readonly config = {
    name: 'Beefy Vault Deposit',
      '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) {
    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),

Last updated