πŸ–¨οΈRegister Data On-Chain

This tutorial teaches you how to set up a basic React app to connect to the Concordium blockchain for on-chain data registration without smart contracts.

This tutorial will implement a basic React application that can connect to the Concordium blockchain. It will not involve any smart contract development or interaction and can run without a node.

Concordium offers a special transaction type to store data on-chain without the need to code a smart contract. An example of its simplest use case is ensuring the integrity of data. For instance, you can store the hash of data on-chain and verify it later to ensure it hasn't been tampered with.

Step 1: Setting up the React Project

  1. Create a React project: Set up a working directory for your dApp and create an empty React project by running the following command in your terminal:

yarn create react-app register-data-dapp --template typescript

Your project will be created as follows:

When you run the application, you will see the template application interface as shown below:

Note: You're starting from scratch with an empty React application that has been bootstrapped from a React template and will be using Material-UI.

  1. Installation of Dependencies: Once the project is created, install the necessary dependencies by running the command below:

yarn add @mui/material @emotion/react @mui/icons-material @emotion/styled @concordium/web-sdk @concordium/browser-wallet-api-helpers

Note: This command installs all the specified packages. It adds dependencies for some React components from Material-UI and necessary libraries from Concordium Web SDK and Concordium Web Wallet Helper.

Once the command runs successfully, it will create a "package.json" file that includes all dependencies:

Step 2: Creating Header Component

Create Header Component: Create a file named "Header.tsx" containing a button and handle the connect() function to the web wallet. Then, paste the following code to define the Header component:

// Header.tsx
import React, { useState } from 'react';
import { AppBar, Toolbar, Typography, Button } from '@mui/material';
import { detectConcordiumProvider, WalletApi } from '@concordium/browser-wallet-api-helpers';

interface HeaderProps {
    onConnected: (provider: WalletApi, account: string) => void;
    onDisconnected: () => void;
}

const Header: React.FC<HeaderProps> = ({ onConnected, onDisconnected }) => {
    const [isConnected, setConnected] = useState(false);

    const connect = () => {
        detectConcordiumProvider()
            .then(provider => {
                provider.connect()
                    .then(account => {
                        setConnected(true);
                        onConnected(provider, account!);
                    })
                    .catch(_ => {
                        alert('Please allow wallet connection');
                        setConnected(false);
                    });
                provider.removeAllListeners();
                provider.on('accountDisconnected', () => {
                    setConnected(false);
                    onDisconnected();
                });
                provider.on('accountChanged', account => {
                    onDisconnected();
                    onConnected(provider, account);
                    setConnected(true);
                });
                provider.on('chainChanged', () => {
                    onDisconnected();
                    setConnected(false);
                });
            })
            .catch(_ => {
                console.error('could not find provider');
                alert('Please download Concordium Wallet');
            });
    };

    return (
        <AppBar position="static">
            <Toolbar>
                <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
                    Concordium Register Data
                </Typography>
                <Button color="inherit" onClick={connect} disabled={isConnected}>
                    {isConnected ? 'Connected' : 'Connect'}
                </Button>
            </Toolbar>
        </AppBar>
    );
};

export default Header;

Step 3: Creating Register Component

Create Register Component: Create a file named "Register.tsx" in the "src" directory that will take input from the user with a text box, calculate its SHA-256, and store it on-chain with a simple and special type of transaction called "registerData."

To do this, paste the following code to define the Register component:

// Register.tsx
import React, { useState, FormEvent } from 'react';
import { detectConcordiumProvider } from '@concordium/browser-wallet-api-helpers';
import { AccountTransactionType, DataBlob, RegisterDataPayload, sha256 } from '@concordium/web-sdk';
import { Button, Link, Stack, TextField, Typography } from '@mui/material';
import { Buffer } from 'buffer/';

const RegisterData: React.FC = () => {
    const [state, setState] = useState({
        checking: false,
        error: '',
        hash: '',
    });

    const submit = async (event: FormEvent<HTMLFormElement>) => {
        event.preventDefault();
        setState({ ...state, error: '', checking: true, hash: '' });
        const formData = new FormData(event.currentTarget);

        var formValues = {
            data: formData.get('data')?.toString() ?? '',
        };

        if (!formValues.data) {
            setState({ ...state, error: 'Invalid Data' });
            return;
        }

        const provider = await detectConcordiumProvider();
        const account = await provider.connect();

        if (!account) {
            alert('Please connect');
        }

        try {
            const txnHash = await provider.sendTransaction(
                account!,
                AccountTransactionType.RegisterData,
                {
                    data: new DataBlob(sha256([Buffer.from(formValues.data)])),
                } as RegisterDataPayload
            );

            setState({ checking: false, error: '', hash: txnHash });
        } catch (error: any) {
            setState({ checking: false, error: error.message || error, hash: '' });
        }
    };

    return (
        <Stack component={'form'} spacing={2} onSubmit={submit} autoComplete={'true'}>
            <TextField
                id="data"
                name="data"
                label="Data"
                variant="standard"
                disabled={state.checking}
            />
            {state.error && (
                <Typography component="div" color="error">
                    {state.error}
                </Typography>
            )}
            {state.checking && <Typography component="div">Checking..</Typography>}
            {state.hash && (
                <Link
                    href={`https://dashboard.testnet.concordium.com/lookup/${state.hash}`}
                    target="_blank"
                >
                    View Transaction <br />
                    {state.hash}
                </Link>
            )}
            <Button
                type="submit"
                variant="contained"
                fullWidth
                size="large"
                disabled={state.checking}
            >
                Register Data
            </Button>
        </Stack>
    );
};

export default RegisterData;

There are two important parts of the code to be careful about:

  • The connection with the wallet

  • The transaction parameters

To ensure these are provided, add a control that checks if the wallet is connected successfully.

Then, you need to pass the data as a parameter to the AccountTransactionType.RegisterData transaction and convert it to SHA256. Keep the returning transaction hash (txnHash) to verify the transaction’s state and its details:

const provider = await detectConcordiumProvider();
  const account = await provider.connect();

  if (!account) {
   alert("Please connect");
  }

  try {
   const txnHash = await provider.sendTransaction(
    account!,
    AccountTransactionType.RegisterData,
    {
     data: new DataBlob(sha256([Buffer.from(formValues.data)])),
    } as RegisterDataPayload
   );

   setState({ checking: false, error: "", hash: txnHash });
  } catch (error: any) {
   setState({ checking: false, error: error.message || error, hash: "" });
  }
 };

Step 4: Integrating Components in App

Integrate Components in App: Open the App.tsx file in the src directory and paste the following code to integrate the Header and Register components:

import "./App.css";
import Header from "./Header";
import { useState } from "react";
import { Container } from "@mui/material";
import RegisterData from "./RegisterData";

export default function App() {
 const [isConnected, setConnected] = useState(false);

 return (
  <div className="App">
   <Header
    onConnected={() => setConnected(true)}
    onDisconnected={() => setConnected(false)}
   />
   <Container sx={{ mt: 15 }}>{isConnected && <RegisterData />}</Container>
  </div>
 );
}

Step 5: Testing the Application

  1. Testing the Application: Once everything is set up, run the application. You should see the application interface similar to the one below:

  1. Connecting to the Wallet: Click the "Connect" button to connect to the wallet. After connecting, the interface should change to indicate that you are connected, as shown below:

  1. Registering Data: Fill in the input field with desired data and click the "REGISTER DATA" button. The application will print the TxnHash value for tracking on the block explorer. You can click on it to verify, as demonstrated below:

  1. Verification on Block Explorer: Upon clicking the TxnHash, you'll be redirected to the dashboard to view the specific block details, confirming the data registration:

It will redirect you to the dashboard, to that block in particular:

  1. Verification with SHA-256 Calculator: Compare the SHA-256 value of the stored data with an online SHA-256 calculator to ensure data integrity, as illustrated below:

Conclusion: Reflecting on the Implementation

As expected, those two values are matched! Take a step back and consider what you've implemented with this dApp. Essentially, you've stored proof of data on-chain, enabling anyone interested in verifying the integrity of your data to do so by examining this decentralized network. It is now stored on-chain, and you cannot change it. The source code can be found in this GitHub repository.

Last updated