LogoLogo
  • Introduction to the Muse DAO
  • Developer Vision
  • DAPPS
    • Fast Dapp
      • Tutorials
        • Getting started
          • Make an ERC20 transfer app
      • Components
        • Contract Read
        • Contract Write
        • APICall
        • Events
        • Address Display
        • Block Number
        • Ethereum Balance
        • PleaseConnect (Request user connection)
        • Token Balance (ERC20)
        • WatchEvents
        • AirStack
        • Sweep NFT floor
        • Token amount (ERC20)
        • Token name (ERC20)
        • Uniswap tradebox
        • Moment (display date/time)
      • Variables
        • userAddress
        • ABIs
        • connectedChain
        • Passing arbitrary data in query URL parameters
        • location
        • Toast
      • Templates
        • ERC20 Token transfer
        • Nouns Auction
        • AAVE V3
        • Lido Staking page
        • DAI Saving Rate
        • ERC6551
        • Popular NFT collections buy button
        • Non Fungible Message
        • No Shit Sherlock
      • Style
    • Launch
      • How to Launch a project
      • How to buy from a Launch
      • How to sell into a Launch
      • FAQ
    • NFT20
      • Use Cases
      • Liquidity Incentives
      • Guides
        • Adding a project
          • Create Pool
      • Contracts
        • NFT20Factory
          • Source
        • NFT20Pair
          • Source
      • API
      • Fees
    • ETHCMD
    • RolodETH
    • The Very Nifty Gallery
    • CUDL Game
      • Pets
      • $CUDL
      • MILK
      • Bazaar
      • Battles and weapons
    • Feather NFT
    • Dreams of EVM NFT
    • Sudoswap,js
    • The Quest (Sword & Shield)
    • Royal Game
    • Space Goo
    • NFT API
    • Pepesea NFT Marketplace
  • Tokenomics
    • $MUSE
    • Treasury
  • Other resources
    • Governance
    • Links
Powered by GitBook
On this page
  • Getting started​
  • Let's build something together​
  • Conclusion​
  1. DAPPS
  2. Fast Dapp
  3. Tutorials

Getting started

PreviousTutorialsNextMake an ERC20 transfer app

Last updated 1 year ago

In this tutorial, you'll learn how to build an in 110 lines of code. This will be your very first app with Fast Dapp.

Getting started

Open the . Once you connect your wallet with Metamask or your favorite provider you'll see two panels. The right one is the preview: what your app will look like once published? and the right one is the editor where you write your app. You can update at anytime the preview by clicking the Render button at the top right of the editor.

Let's build something together

In this tutorial we'll build together a alternative. We'll list all the approvals an address made and enable the user to remove them in one click.

Getting the data

In order to all the aprovals made by an address user we'll use the component. The component fetches the events that were emitted on chain and has a callback function to display them.

In our case we'll pass the following parameters:

  • address to null as we want to get all approval events emitted from any contract

  • abi, we'll use the standard ERC20 ABI.

  • eventName to Approval

  • args is used for filtering the results. The first parameter is the account that approved the spending of the tokens. If your address doesn't have previous approvals you can change it to any address.

  • render our function that will display the data.

<Events
      address={null}
      abi={ABIs.ERC20}
      eventName="Approval"
      args={[userAddress]}
      render={
        (logs) => (
          logs.map((log) => (
            <div>{log.address} - {log.args.spender}</div>
          ))
        )
      }
/>

If you press render at the top right of the editor, you should see a loading indicator then a list of all the Approval events corresponding to your address.

As we retrieve all approvals events, we now need to only select the latest event of each ERC20 tokens (log.address) and spender (log.args.spender). For this, we'll write a more complex render function:

<Events
    address={null}
    abi={ABIs.ERC20}
    eventName="Approval"
    args={[userAddress]}
    render={
        function(logs) {
            approvals = [];
            logs.reverse().forEach(function (log) {
                 if (log.args.value != null && !approvals.find((aproval) => (aproval.args.spender == log.args.spender && aproval.address == log.address))) {
                    approvals.push(log);
                }
            });
            return (approvals.filter((approval) => parseInt(approval.args.value) != 0)
            .sort((a, b) => b.address - a.address)
            .map((approved) => (
                <div>{approved.address} - {approved.args.spender}</div>
            )))
        }
      }
/>

Now that we have clean data, let's display it in an HTML table:

<div class="overflow-x-auto">
  <table class="table">
    <thead>
      <tr>
        <th>Token</th>
        <th>Spender</th>
        <th>Amount</th>
      </tr>
    </thead>
    <tbody>
      <Events
        address={null}
        abi={ABIs.ERC20}
        eventName="Approval"
        args={[userAddress]}
        render={function (logs) {
          approvals = [];
          logs.reverse().forEach(function (log) {
            if (
              log.args.value != null && !approvals.find(
                (aproval) =>
                  aproval.args.spender == log.args.spender &&
                  aproval.address == log.address
              )
            ) {
              approvals.push(log);
            }
          });
          return approvals.sort((a, b) => b.address - a.address)
          .filter((approval) => (parseInt(approval.args.value) != 0))
          .map((approved) => (
                <tr>
                    <td>{approved.address}</td>
                    <td>{approved.args.spender}</td>
                    <td>{parseInt(approved.args.value)}</td>
                </tr>
          ));
        }}
      />
    </tbody>
  </table>
</div>

In order to make the result more beautiful we'll use a few components to display the data:

<tr>
    <td><TokenName token={approved.address} /></td>
    <td>
        <AddressDisplay address={approved.args.spender} />
    </td>
    <td>
        <TokenAmount
            token={approved.address}
            amount={approved.args.value}
        />
    </td>
</tr>

Now we'll use the ContractWrite component to add a revoke button to the approvals. The component accepts a few parameters:

  • address the contract we'll interact with.

  • abi the ABI of the contract, the ABI can be customized to tweak the UI (default parameters, hide inputs, change the button text..).

  • args the default arguments

In our case, revoking the contract will looks like this:

<ContractWrite
    address={approved.address}
    abi={ABIs.ERC20}
    functionName="approve"
    buttonText="Revoke"
    args={[approved.args.spender, 0]}
/>
<ContractWrite
    address={approved.address}
    abi={[
        {
    "inputs": [
      {
        "internalType": "address",
        "name": "spender",
        "type": "address",
        "hidden": true
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256",
        "hidden": true
      }
    ],
    "name": "approve",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
     }
    ]}
    functionName="approve"
    buttonText="Revoke"
    args={[approved.args.spender, 0]}
/>

Our complete code looks like this, if you click on the Revoke button you'll see the transaction for removing the approval being asked for confirmation by you.

<div class="overflow-x-auto">
  <table class="table">
    <thead>
      <tr>
        <th>Token</th>
        <th>Spender</th>
        <th>Amount</th>
        <th>Revoke</th>
      </tr>
    </thead>
    <tbody>
      <Events
        address={null}
        abi={ABIs.ERC20}
        eventName="Approval"
        args={[userAddress]}
        render={function (logs) {
          approvals = [];
          logs.reverse().forEach(function (log) {
            if (
              log.args.value != null &&
              !approvals.find(
                (aproval) =>
                  aproval.args.spender == log.args.spender &&
                  aproval.address == log.address
              )
            ) {
              approvals.push(log);
            }
          });
          return approvals
            .filter((approval) => parseInt(approval.args.value) != 0)
            .sort((a, b) => b.address - a.address)
            .map((approved) => (
              <tr>
                <td>
                  <TokenName token={approved.address} />
                </td>
                <td>
                  <AddressDisplay address={approved.args.spender} />
                </td>
                <td>
                  <TokenAmount
                    token={approved.address}
                    amount={approved.args.value}
                  />
                </td>
                <td>
                  <ContractWrite
                    address={approved.address}
                    abi={[
                      {
                        inputs: [
                          {
                            internalType: "address",
                            name: "spender",
                            type: "address",
                            hidden: true,
                          },
                          {
                            internalType: "uint256",
                            name: "amount",
                            type: "uint256",
                            hidden: true,
                          },
                        ],
                        name: "approve",
                        outputs: [
                          {
                            internalType: "bool",
                            name: "",
                            type: "bool",
                          },
                        ],
                        stateMutability: "nonpayable",
                        type: "function",
                      },
                    ]}
                    functionName="approve"
                    buttonText="Revoke"
                    args={[approved.args.spender, 0]}
                  />
                </td>
              </tr>
            ));
        }}
      />
    </tbody>
  </table>
</div>

Let's now add some title, description and style our page.

We'll also wrap the table into a PleaseConnect component as we need to enforce that the user is connected to access the userAddress variable.

We'll also add metadata to our app at the beginning of it in yaml format. The metadata are:

  • title a title used for SEO

  • description a description used for SEO

  • author who wrote the code

  • chain an array of chain ID this app is available on.

---
chain: [1,10,42161,137,5]
authors: grands_marquis
theme: dark
title: Fast Revoke
description: Manage your ERC20 approvals with Fast Revoke
---

<div class="p-10">
  <center>
    <h1>⚡ Fast Revoke ⚡</h1>
    <p>
      When using dapps like Uniswap or OpenSea you have to grant them permission
      to spend your tokens and NFTs. This is called a token approval. If you
      don't revoke these approvals, the dapp can spend your tokens forever. Take
      back control by revoking your approvals.
    </p>
  </center>
  <PleaseConnect >
    <div class="overflow-x-auto">
      <table class="table">
        <thead>
          <tr>
            <th>Token</th>
            <th>Spender</th>
            <th>Amount</th>
            <th>Revoke</th>
          </tr>
        </thead>
        <tbody>
          <Events
            address={null}
            abi={ABIs.ERC20}
            eventName="Approval"
            args={[userAddress]}
            render={function (logs) {
              approvals = [];
              logs.reverse().forEach(function (log) {
                if (
                  log.args.value != null &&
                  !approvals.find(
                    (aproval) =>
                      aproval.args.spender == log.args.spender &&
                      aproval.address == log.address
                  )
                ) {
                  approvals.push(log);
                }
              });
              return approvals.sort((a, b) => b.address - a.address)
                .filter((approval) => parseInt(approval.args.value) != 0)
                .map((approved) => (
                  <tr>
                    <td>
                      <TokenName token={approved.address} />
                    </td>
                    <td>
                      <AddressDisplay address={approved.args.spender} />
                    </td>
                    <td>
                      <TokenAmount
                        token={approved.address}
                        amount={approved.args.value}
                      />
                    </td>
                    <td>
                      <ContractWrite
                        address={approved.address}
                        abi={[
                          {
                            inputs: [
                              {
                                internalType: "address",
                                name: "spender",
                                type: "address",
                                hidden: true,
                              },
                              {
                                internalType: "uint256",
                                name: "amount",
                                type: "uint256",
                                hidden: true,
                              },
                            ],
                            name: "approve",
                            outputs: [
                              {
                                internalType: "bool",
                                name: "",
                                type: "bool",
                              },
                            ],
                            stateMutability: "nonpayable",
                            type: "function",
                          },
                        ]}
                        functionName="approve"
                        buttonText="Revoke"
                        args={[approved.args.spender, 0]}
                      />
                    </td>
                  </tr>
                ));
            }}
          />
        </tbody>
      </table>
    </div>
  </PleaseConnect>
</div>

Once you are happy with the result, you can press the publish button at the top right of the editor. We'll upload your code on IPFS and give you a link to share with others.

We managed to build a full application to manage users ERC20 approvals in 110 lines of code. Here are sme ideas you can try to implement to practice more building with Fast Dapp:

  • List all approvals history

  • Handle ERC721 NFTs approvals

Formatting the data

Displaying the data

: Display the name of the token from its address

: Display an address shortened or its ENS

: Display a token amount with precision handling

Revoke an approval

But as you can see the component will show the inputs, let's improve by grabbing the approval ABI and hidding the inputs by adding "hidden": true to the inputs. There are a lot of ways to customize the ContractWrite

Polishing

theme select a base theme to make our app looks beautiful. See .

Here is our final code, you can directly or as :

Publishing

Conclusion

Display the of the user in the table

Use to read the user's address

Let us know if you need .

​
​
TokenName
AddressDisplay
TokenAmount
​
components like handling token input, approvals, time value..
​
available themes
see it in the editor
a published app
​
​
token balance
query parameters
any help building on Telegram
app to manage ERC20 approvals for an address
​
Fast Dapp web editor
​
revoke.cash
​
Events