Build on Flow | Learn FCL - 9. How to pass Optional and Path Arguments to Scripts

maxstalker

Max Daunarovich

Posted on June 22, 2022

Build on Flow | Learn FCL - 9. How to pass Optional and Path Arguments to Scripts

Overview

Last time we covered how to pass arguments in scripts, we’ve intentionally omitted Optionals and Paths arguments to lower the mental load. Those 2 are still important and quite common, so we will cover them in this tutorial.

After you follow and complete example covered in this post you will be able to:

  • pass any Optional argument, regardless of its base type
  • pass Path arguments

But first, let’s look at what they are!

Theory

Optionals are a bit like Schroedinger’s cat in programming: they can represent the presence or the absence of a value, when you check what’s inside. Optionals have two cases:

  • there is a value
  • there is nil (nothing)

One use case where you might find this useful (that comes to my mind) is when you want to pass a Dictionary {String: String?} and want to initialize some of the fields with empty values (for whatever reason 🤷‍♂️)

Paths arguments can be used when you want to use or access account storage in your script or transaction and doesn’t want to hardcode that value, but rather make it more flexible.

Let’s take a look at some common path /public/flowTokenVault - this is a path to the publicly available Capability to access the FLOW token Vault resource. With this specific Capability you can use it to get reference to that resource to read it’s balance or deposit more tokens into it. Not all Capabilities are equal though! Some might give your more functionality and others less.

Back to our capability. If you take a look at it you can see that it consists of two parts:

  • /public - this is called a domain.
  • /flowTokenVault - and this is an identifier

Technically / character is a separator, but I think it’s easier to see it this way 😉

There are only three valid domains: storageprivate, and public. In all of the scripts you will use public domain - since it won’t allow you to access private and storage areas of other accounts.

Let’s try to pass both of those into some scripts! 💪

Step 1 - Installation

Add "@onflow/fcl": "1.0.0" as your dependency

Step 2 - Setup

Just like the last time we will import necessary methods and setup FCL:

// Import methods from FCL
import { query, config } from "@onflow/fcl";

// Specify the API endpoint - this time we will use Mainnet
const api = "https://rest-mainnet.onflow.org";

// Configure FCL to use mainnet as the access node
config().put("accessNode.api", api);
Enter fullscreen mode Exit fullscreen mode

Step 3 - Pass Path arguments

We will try to pass 3 different paths - PublicPath, PrivatePath and StoragePath - each one for respective domain:

// Path Arguments
const passPathArgument = async () => {
  const cadence = `
    pub fun main(public: PublicPath, private: PrivatePath, storage: StoragePath): PrivatePath{
      // we can return any value, but let's return one of the Paths to see
      // how FCL decodes it into value
      return private
    }
  `;

  // Since we are not gonna use any of those for actual data access
  // we can construct any paths we like
  // this one will encode "/public/flowTokenVault"
  const publicPath = {
    domain: "public",
    identifier: "flowTokenVault"
  };

  // encode "/private/flowTokenVault"
  const privatePath = {
    domain: "private",
    identifier: "flowTokenVault"
  };

  // encode "/storage/flowTokenVault"
  const storagePath = {
    domain: "storage",
    identifier: "flowTokenVault"
  };

  // Notice that t.Path is not a function, but a constant!
  const args = (arg, t) => [
    arg(publicPath, t.Path),
    arg(privatePath, t.Path),
    arg(storagePath, t.Path)
  ];
  const result = await query({ cadence, args });
  console.log({ result });
};
Enter fullscreen mode Exit fullscreen mode

Please, note that t.Path is a constant, even though we are passing object 🤪

Step 4 - Pass Optional Integer

To pass Optional, we need to wrap the type into t.Optional() call. And that’s it 😊

// Optionals
const passOptionalIntegers = async () => {
  const cadence = `
    pub fun main(a: Int?): Int?{
      return a
    }
  `;

  // We will set value of our Int argument to null to check that script works correctly
  const a = null;
  const args = (arg, t) => [arg(a, t.Optional(t.Int))];

  const result = await query({ cadence, args });
  console.log({ result });
}; 
Enter fullscreen mode Exit fullscreen mode

Step 5 - Pass Other Optional Types

In similar fashion to our original post about arguments let’s pass other Optional types all at once:

const passMultiOptional = async () => {
  const cadence = `
    pub fun main(a: String?, b: Bool?, c: UFix64?, d: Address?): Address?{
      return d
    }
  `;

    // it's perfectly fine to pass non-nil values alongside "empty"
  const a = "Hello";
  const b = null;
  const c = null;
  const d = "0x01";

  // Note the types are the same as we specify in Cadence code
  const args = (arg, t) => [
    arg(a, t.Optional(t.String)),
    arg(b, t.Optional(t.Bool)),
    arg(c, t.Optional(t.UFix64)),
    arg(d, t.Optional(t.Address))
  ];

  const result = await query({ cadence, args });
  console.log({ result });

  showResult("multiple", result);
};
Enter fullscreen mode Exit fullscreen mode

Step 6 - Pass Optional Array and Array of Optionals

The difference between those two is in target for t.Optional() wrapper.

Here’s array of Optionals:

const passArrayOfOptinalStrings = async () => {
  const cadence = `
    pub fun main(a: [String?]): String?{
      return a[0]
    }
  `;

  const a = ["Hello", null];
  // Type of the argument is composed of t.Array, t.Optional and t.String
  const args = (arg, t) => [arg(a, t.Array(t.Optional(t.String)))];
  const result = await query({ cadence, args });
  console.log({ result }); //
};
Enter fullscreen mode Exit fullscreen mode

And this one for optional array of Strings:

const passOptionalArray = async () => {
  const cadence = `
    pub fun main(a: [String]?): String?{
      if let arr = a {
        return arr[0]
      }

      return nil
    }
  `;
  const a = null;
  // This time we will wrap our array in "t.Optional()" call
  const args = (arg, t) => [arg(a, t.Optional(t.Array(t.String)))];
  const optionalArray = await query({ cadence, args });
  console.log({ optionalArray }); //
};
Enter fullscreen mode Exit fullscreen mode

Step 7 - Pass Dictionary with Optional values

const passDictionaryOfOptionals = async () => {
  // In this example we will pass a Cadence Dictionary as argument
  // keys will be of type "String" and values will be Optionals of type "Int?"
  const cadence = `
    pub fun main(a: {String: Int?}): Int??{
      return a["amount"]
    }
  `;

  // Dictionaries should be represented as array of key/value pairs of respective types
  // Note that we shall pass numeric value as string here
  const a = [{ key: "amount", value: "42" }];
  // Dictionary type is composed out of t.Dictionary, t.String and t.Int for our case
  const args = (arg, t) => [
    arg(a, t.Dictionary({ key: t.String, value: t.Optional(t.Int) }))
  ];

  const result = await query({ cadence, args });
  console.log({ result });
};
Enter fullscreen mode Exit fullscreen mode

💡 Have you noticed that script in last example returns Double Optional Int?? ? That’s because value at amount key might not exist, so it will return us Maybe(Maybe(Int)) - you will need to unwrap it twice if you want to use it later in the code 😉

Finally

Let's add an IIFE at the end of the file and populate it with methods we have just defined:

(async () => {
    console.clear();

    // Path arguments
  await passPathArgument();

    // Optional arguments
  await passOptionalIntegers();
  await passMultiOptional();
  await passArrayOfOptinalStrings();
  await passOptionalArray();
  await passDictionaryOfOptionals();
})();

Enter fullscreen mode Exit fullscreen mode

Some sparkling 🎇 moments after you should see the output in the console log:

{result: Object}
    result: {
        domain: "private",
        identifier: "flowTokenVault"
    }
{result: null}
{result: "0x0000000000000001"}
{result: "Hello"}
{optionalArray: null}
{result: "42"}
Enter fullscreen mode Exit fullscreen mode

You did it - now those types are not a challenge to you! 💪

Hope you find information in this post useful and will get back for more 😉

Until next time! 👋

Resources

Other resources you might find useful:

💖 💪 🙅 🚩
maxstalker
Max Daunarovich

Posted on June 22, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related