DEV Community

Max Daunarovich for Flow Blockchain

Posted on

Build on Flow | Learn FCL - 13. How to Modify the State of the Chain by Signing Transactions with Wallets

Overview

In this post we will learn:

  • Signing roles - payer, proposer and authorizers
  • What is a signing function and how to create one
  • How to sign a transaction with one of your wallets
  • How to sign a transaction with your private key

Preface

Once we have our wallet or private keys, we can start signing transactions. Woohoo!

For better understanding of how this works, you will need some extra knowledge.

The signing process on Flow requires 3 different parties to sign the transaction:

  • Proposer - this is the account, which proposes to include changes done by transaction into the blockchain. Proposer role can be filled by any account on Flow blockchain.
  • Payer is the account, who will pay the gas fees for transaction execution. Similar to proposer , this can be any account, who have enough FLOW tokens to cover the fee and is eager to do it.
  • Authorizers - this is a list of accounts, who authorize any changes in their accounts, done by transaction they are signing. These should be accounts, that want to make said changes. If there is only a single account, it should still be a list of one.

💡All of those roles can be filled by the same account! You don’t need to have 3 “actors” to sign transaction 😅

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. This time, though, we will extend our configuration with some new values to enable Wallet Discovery.

FCL Wallet Discovery allows you to connect to all known and registered wallets at once! If you are coming from Ethereum, you can think about it as Flow variant of WalletConnect sans multichain support 😅

Note that we are using slightly different way to specify config by passing in object instead of calling multiple .put calls:

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

// Configure FCL
config({
    // Use Testnet Access Node
  "accessNode.api": "https://rest-testnet.onflow.org",
  // We will also specify the network as some of the FCL parts need it to properly do it's work
  "flow.network": "testnet",
    // This will be the title of our DApp
  "app.detail.title": "Meow DApp",
    // This is just a kitten photo, we will use for the icon
  "app.detail.icon": "https://placekitten.com/g/200/200",
    // Next two will define where Wallet Discovery is located
  "discovery.wallet": "https://fcl-discovery.onflow.org/testnet/authn",
  "discovery.authn.endpoint":
    "https://fcl-discovery.onflow.org/api/testnet/authn",

    // We will also set alias for the contract we will use in this example
    "0xBasic": "0xafabe20e55e9ceb6"
});
Enter fullscreen mode Exit fullscreen mode

You can read more information about those and other configuration keys on FCL documentation page

Step 3 - Get Current Chain State

We’ve deployed a Basic contract at 0xafabe20e55e9ceb6. If you click on that link it will lead you to Flow View Source. You can see there is a single public variable counter, that we can read and a couple of methods to modify it. Let’s use the knowledge from previous articles and read it.

const readCounter = async () => {
  const cadence = `
    import Basic from 0xBasic

    pub fun main():UInt{
      return Basic.counter
    }
  `;
  const counter = await query({ cadence });
  console.log({ counter });
};
Enter fullscreen mode Exit fullscreen mode

This simple query looks exactly like what we’ve built before, except this time we are using 0xBasic alias for the address. FCL will use the value we set in the config - 0xafabe20e55e9ceb6 - and replace it. We are doing it this way, cause we will make calls to the contract from our transaction as well. So we have this value nice and handy in one place and we don’t need to constantly reference it or memorize it 😉

You should see some output in your console, similar to this (number can be different, as there are multiple users, who would want to alter it):

{counter: "19"}
Enter fullscreen mode Exit fullscreen mode

Step 4 - Modify Counter

We will use method mutate provided by FCL package to modify the state of the chain. In its most basic form mutate is very similar to query, but it’s using some hidden mechanisms in order to sign the transaction (we will demystify them in later article).

Let’s define another method shiftCounter, which we will be using to modify the value of the counter. If you go back to the Basic contract you will see that it defines two methods. We will use incrementCounterBy(_ step: UInt8)

We will need to pass UInt8 value as an argument to the transaction - we do it the same way we’ve done previously using the query method.

const shiftCounter = async (value) => {
    // Our Cadence code. Notice we are using alias here as well
  const cadence = `
    import Basic from 0xBasic

    transaction(shift: UInt8){
      prepare(signer: AuthAccount){
        Basic.incrementCounterBy(shift)
      }
    }
  `;

    // List of arguments
  const args = (arg, t) => [
        // We need to pass UInt8 value, but it should be a string, remember? :)
        arg(value.toString(), t.UInt8)
    ];

    // "mutate" method will return us transaction id
    const txId = await mutate({
    cadence,
    args,
    limit: 999
  });

  // We will use transaction id in order to "subscribe" to it's state change and get the details
  // of the transaction
  const txDetails = await tx(txId).onceSealed();
    return txDetails
};
Enter fullscreen mode Exit fullscreen mode

Wait, but what about those roles we’ve mentioned above. You see, mutate will populate all of them with the current authenticated user…

https://c.tenor.com/d-bHBk4UYs4AAAAC/who.gif

That’s part of the FCL and Wallet Discovery magic 🎇. If you don’t specify any role in that mutate method it will ask you to connect to the wallet and then use selected account as current user. Then it will use all the necessary params to fill in the roles in that interaction. Pretty cool, ha? 😎

Finally

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

(async () => {
  console.clear();
  // You can [un]comment this line to toggle Wallet Discovery
  unauthenticate();

    // Let's read value, currently set on contract 
  await readCounter();

  // Let's modify the state now
  const txDetails = await shiftCounter(13);
  console.log({ txDetails });

    // Now we can check that value is actually changed
  await readCounter();
})();
Enter fullscreen mode Exit fullscreen mode

When the code on the page will execute it will present you a popup, allowing to choose which wallet to use.

⚠️ You need to have Lilico installed in order for it to appear in the list. Consult one of our previous articles for details how to install and setup Lilico.

Pick Wallet

If you were to choose Lilico, you will be presented with following flow. First it will ask you to connect wallet to our dapp:
Connect Lilico

Then ask for confirmation. You can expand/collapse script pane to check the code of the transaction.

💡 Actually, you should ALWAYS check what you are signing. Even on Testnet. It’s a good habit, that will help you make your interaction with Flow more secure 😉

Lilico - Approve

Blocto will guide your through a similar process:
Use Blocto

And it also has a way to check the transaction code:
Blocto - Confirm

After all said and done you should see the following in your console (numbers might change):

{ counter: "234" }
{ txDetails: Object }
{ counter: "247" }
Enter fullscreen mode Exit fullscreen mode

Expanding txDetails will give your more information about the transaction:

blockId: "ad7605f10f9846dd1c5603723b1d12995a3152b19a1ca24e47a3a2e28cee0641"
status: 4
statusString: "SEALED"
statusCode: 0
errorMessage: ""
events: Array(3)
Enter fullscreen mode Exit fullscreen mode

https://media.giphy.com/media/3o7btNa0RUYa5E7iiQ/giphy.gif

Until next time! 👋

Resources

Other resources you might find useful:

Top comments (0)