💻
Developer Guide
  • Wallet SDK
    • Wallet Overview
    • Getting Started
      • 1. Start the RAILGUN Privacy Engine
      • 2. Build a persistent store for artifact downloads
      • 3. Load a Groth16 prover for each platform
      • 4. Add networks and RPC providers
      • 5. Set up a debug logger
    • Private Wallets
      • RAILGUN Wallets
      • View-Only Wallets
      • Encryption Keys
    • Private Balances
      • Balance and Sync Callbacks
      • Updating Balances
      • QuickSync
    • Transactions
      • Shielding
        • Shield ERC-20 tokens
        • Shield base token
        • Shield NFTs
      • Private Transfers
        • Private ERC-20 Transfers
        • Private NFT Transfers
      • Cross-Contract Calls
      • Unshielding
        • Unshield ERC-20 tokens
        • Unshield base token
        • Unshield NFTs
      • UX: Private Transactions
    • Broadcasters
  • Cookbook SDK
    • Cookbook Overview
    • Recipe Guide: Write a zkApp
      • "Step" — A smart contract call
      • "Recipe" — Steps in series
      • "Combo Meal" — 2+ Recipes
    • Use your zkApp privately
  • Engine SDK
    • Engine Overview
  • ZK Account Abstraction
    • Account Abstraction Overview
    • Getting started with the contracts
    • Wallets
    • State Structure
    • Example Primitives
Powered by GitBook
On this page
  1. Cookbook SDK
  2. Recipe Guide: Write a zkApp

"Recipe" — Steps in series

Previous"Step" — A smart contract callNext"Combo Meal" — 2+ Recipes

Last updated 1 year ago

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

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 has two Steps: (1) Approve sell token, (2) Swap sell token for buy token. The full Recipe uses a Step called , 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 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',
    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),
    ];
  }
}
Unshield
Shield
populated transactions
0x Swap Recipe
ApproveERC20SpenderStep
ApproveERC20SpenderStep