DEV Community

Ankit Sharma
Ankit Sharma

Posted on

Product authenticator using :Stellar Blockchain Technology

Image description
A) Environment Setup:
Install Rust, using command:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Install the Soroban CLI using below mentioned command.
cargo install --locked soroban-cli
For more info visit => Soroban docs

Install Node.js

Get the Freighter Wallet extension for you browser. Once enabled, then got to the network section and connect your wallet to the testnet.

Install wasm32-unknown-unknown package using command:
rustup target add wasm32-unknown-unknown

To configure your CLI to interact with Testnet, run the following command:

soroban network add \
--global testnet \
--rpc-url https://soroban-testnet.stellar.org:443 \
--network-passphrase "Test SDF Network ; September 2015"

In order to deploy the smartcontract you will need an account. You can either use the an account from theFreighter Walletor can configure an account named alice in the testnet using the command:
soroban keys generate --global alice --network testnet

You can see the public key of account alice:soroban keys address alice

Image description

B) Backend (Smart-contract) Setup:

Code for your freighter wallet is as follow :

import {
  requestAccess,
  signTransaction,
  setAllowed,
} from "@stellar/freighter-api";

async function checkConnection() {
  const isAllowed = await setAllowed();
  if (isAllowed) {
    return isAllowed;
  }
}

const retrievePublicKey = async () => {
  let publicKey = "";
  let error = "";
  try {
    publicKey = await requestAccess();
  } catch (e) {
    error = e;
  }
  if (error) {
    return error;
  }
  return publicKey;
};

const userSignTransaction = async (xdr, network, signWith) => {
  let signedTransaction = "";
  let error = "";

  try {
    signedTransaction = await signTransaction(xdr, {
      network,
      accountToSign: signWith,
    });
  } catch (e) {
    error = e;
  }

  if (error) {
    return error;
  }

  return signedTransaction;
};

export { checkConnection, retrievePublicKey, userSignTransaction };

Enter fullscreen mode Exit fullscreen mode

Smart-contract folder Structure:

smart-contract
├── Cargo.lock
├── Cargo.toml
├── README.md
└── contracts
└── Gate-Pass-Dapp
├── Cargo.toml
└── src
└── lib.rs

=> Go inside the /smart-contract directory and do the below mentioned steps:

Build the contract:
soroban contract build

Alternte command:

cargo build --target wasm32-unknown-unknown --release

Install Optimizer:

cargo install --locked soroban-cli --features opt

Build an Opmize the contract:

soroban contract optimize --wasm target/wasm32-unknown-unknown/release/Gate_Pass_Dapp.wasm

Steps to the Deploy smart-contract on testnet:

Get the hash of the Wasm bytes, like

4bb69f6355400b2954f9537ec55cd24c4a0a3021eae95758a088b383587657cb

using this command:
soroban contract install --source-account bhupendra --wasm target/wasm32-unknown-unknown/release/Gate_Pass_Dapp.wasm --rpc-url https://soroban-testnet.stellar.org:443 --network-passphrase "Test SDF Network ; September 2015"

By using that Wasm hash, deploy the smartcontract on the testnet and get deployed address of the smartcontract using the following command:

soroban contract deploy \
--wasm-hash 4bb69f6355400b2954f9537ec55cd24c4a0a3021eae95758a088b383587657cb \
--source alice \
--network testnet

Deployed address of this smartcontract:
CBPSRM3TVRYA6PT7ESIXC64QZDTKIQNSKBYJ4CED64CN2OITETB67X2P

*NOTE: If you get the XDR Error error: xdr processing error: xdr value invalid, then follow this article.

The code for Header.js file is as follow:-

import React, { useEffect, useState } from "react";
import { checkConnection, retrievePublicKey } from "./Freighter";
import StellarLogo from "../assets/stellarlogo.png"

const Header = ({setPubKey}) => {
  const [connect, getConnected] = useState("Connect");
  const [publickey, getPublicKey] = useState("");
  const [open, setOpen] = useState(false);

  const handleOpenMenu = () => setOpen(!open);

  useEffect(() => {
    if (publickey !== "") {
      getConnected("Connected!");
      setPubKey(publickey);
    }
  }, [publickey]);

  const connectWallet = async () => {
    if (await checkConnection()) {
      getPublicKey(await retrievePublicKey());
    }
  };

  return (
    <div className="bg-black flex md:flex-row shadow-md justify-between items-center px-10 py-4">
      <div
        className="text-2xl sm:text-3xl lg:text-3xl font-semibold text-white flex items-center gap-5"
      >
        {/* <img src={StellarLogo} alt="CratePass X Stellar" className="w-11" /> */}
        <span className="text-white">Product Authenticator</span>
      </div>

      <div
        onClick={() => handleOpenMenu()}
        className="text-4xl absolute top-4 right-3 md:hidden cursor-pointer text-white"
      >
        <ion-icon name={open ? "close" : "menu"}></ion-icon>
      </div>

      <div>
        <ul
          className={`${
            open ? "top-20 left-0" : "top-[-496px]"
          } flex flex-col md:flex-row md:justify-around items-center text-nowrap md:pb-0 py-3 absolute md:static bg-black md:bg-transparent gap-5 w-full md:w-auto pl-3 md:border-none border-2 border-white rounded-b-2xl transition-all duration-500 ease-in-out z-10`}
        >
          <li>
            <div className="p-1 bg-white border-2 max-w-max rounded-md">
              <span className="p-1 px-2 bg-black text-white h-full rounded-md">
                Address
              </span>
              <span className="px-2 text-black">
                {`${publickey.substring(0, 4)} ${
                  publickey && "..."
                } ${publickey.substring(publickey.length - 4)}`}
              </span>
            </div>
          </li>
          <li>
            <button
              className="text-xl w-52 hover:bg-gray-800 bg-white rounded-md p-4 font-bold text-black border-4 border-white"
              onClick={connectWallet}
            >
              {connect}
            </button>
          </li>
        </ul>
      </div>
    </div>
  );
};

export default Header;
Enter fullscreen mode Exit fullscreen mode

The Soroban file code as follow(without function):-

(1)
import {
  Contract,
  SorobanRpc,
  TransactionBuilder,
  Networks,
  BASE_FEE,
  nativeToScVal,
  Address,
} from "@stellar/stellar-sdk";
import { userSignTransaction } from "../Freighter";

let rpcUrl = "https://soroban-testnet.stellar.org";

let contractAddress =
  "CDHHT6IKGRCFPSYJNKR2JEC3GUHRXGPIDUE2JN4LQLZMTCHYKLB4KZ7D";


const accountToScVal = (account) => new Address(account).toScVal();


const stringToScValString = (value) => {
  return nativeToScVal(value);
};

const numberToU64 = (value) => {
  return nativeToScVal(value, { type: "u64" });
};

let params = {
  fee: BASE_FEE,
  networkPassphrase: Networks.TESTNET,
};

async function contractInt(caller, functName, values) {
  const provider = new SorobanRpc.Server(rpcUrl, { allowHttp: true });
  const sourceAccount = await provider.getAccount(caller);
  const contract = new Contract(contractAddress);
  let buildTx;

  if (values == null) {
    buildTx = new TransactionBuilder(sourceAccount, params)
      .addOperation(contract.call(functName))
      .setTimeout(30)
      .build();
  } else if (Array.isArray(values)) {
    buildTx = new TransactionBuilder(sourceAccount, params)
      .addOperation(contract.call(functName, ...values))
      .setTimeout(30)
      .build();
  } else {
    buildTx = new TransactionBuilder(sourceAccount, params)
      .addOperation(contract.call(functName, values))
      .setTimeout(30)
      .build();
  }

  let _buildTx = await provider.prepareTransaction(buildTx);

  let prepareTx = _buildTx.toXDR(); 

  let signedTx = await userSignTransaction(prepareTx, "TESTNET", caller);

  let tx = TransactionBuilder.fromXDR(signedTx, Networks.TESTNET);

  try {
    let sendTx = await provider.sendTransaction(tx).catch(function (err) {
      console.error("Catch-1", err);
      return err;
    });
    if (sendTx.errorResult) {
      throw new Error("Unable to submit transaction");
    }
    if (sendTx.status === "PENDING") {
      let txResponse = await provider.getTransaction(sendTx.hash);

      while (txResponse.status === "NOT_FOUND") {
        txResponse = await provider.getTransaction(sendTx.hash);
        await new Promise((resolve) => setTimeout(resolve, 100));
      }
      if (txResponse.status === "SUCCESS") {
        let result = txResponse.returnValue;
        return result;
      }
    }
  } catch (err) {
    console.log("Catch-2", err);
    return;
  }
} 
Enter fullscreen mode Exit fullscreen mode

.....Integrate it with upcoming code .
Invoke functions from the smart-contract:

To invoke any of the function from the smartcontract you can use this command fromat.

soroban contract invoke \
--id <DEPLOYED_CONTRACT_ADDRESS> \
--source <YOUR_ACCOUNT_NAME> \
--network testnet \
-- \
<FUNCTION_NAME> --<FUNCTION_PARAMETER> <ARGUMENT>

For example:
To status of all registered passes, invoke view_all_pass_status function.

soroban contract invoke \
--id CBPSRM3TVRYA6PT7ESIXC64QZDTKIQNSKBYJ4CED64CN2OITETB67X2P \
--source alice \
--network testnet \
-- \
view_all_pass_status

To create a new pass, invoke create_pass function:

soroban contract invoke \
--id CBPSRM3TVRYA6PT7ESIXC64QZDTKIQNSKBYJ4CED64CN2OITETB67X2P \
--source alice \
--network testnet \
-- \
create_pass --record_id <YOUR_PUBLIC_ADDRESS> --title "Going Home" --descrip "I am going to my home today."

Soroban file code with function:-

(95)
async function createPass(caller, title, descrip) {
  let titleScVal = stringToScValString(title);
  let descripScVal = stringToScValString(descrip);
  let values = [titleScVal, descripScVal];

  try {
    const passId = await contractInt(caller, "create_pass", values);
    let resolvedPassId = Number(passId?._value?._value);
    console.log(resolvedPassId);


    return resolvedPassId;
  } catch (error) {
    console.log("Pass not created. Check if you already have a active pass");
  }
}



async function approvePass(caller, pass_id) {

  let values = numberToU64(pass_id);

  try {
    await contractInt(caller, "approve_pass", values);
    console.log(`!!Pass ID - ${pass_id}, is now Arrpoved!!`);
  } catch (error) {
    console.log("Pass can't be approved!!");
  }
}


async function expirePass(caller, pass_id) {
  let values = numberToU64(pass_id);

  try {
    await contractInt(caller, "expire_pass", values);
    console.log(`!!Pass ID - ${pass_id}, is now expired!!`);
  } catch (error) {
    console.log("Pass can't be expired!!");
  }
}



async function  fetchAllPassStatus(caller) {
  try {
    let result = await contractInt(caller, "view_all_pass_status", null);


    let approvedVal = Number(result?._value[0]?._attributes?.val?._value);


    let expiredVal = Number(result?._value[1]?._attributes?.val?._value);


    let pendingVal = Number(result?._value[2]?._attributes?.val?._value);


    let totalVal = Number(result?._value[3]?._attributes?.val?._value);

    console.log(approvedVal, expiredVal, pendingVal, totalVal);
    let ansArr = [];
    ansArr.push(approvedVal);
    ansArr.push(expiredVal);
    ansArr.push(pendingVal);
    ansArr.push(totalVal);
    return ansArr;
  } catch (error) {
    console.log("Unable to fetch All Pass Status!!");
  }
}


async function fetchMyPassStatus(caller, pass_id) {
  let values = numberToU64(pass_id);
  let result1;
  let result2;

  try {
    result1 = await contractInt(caller, "view_my_pass", values);

  } catch (error) {
    console.log("Unable to fetch Your Pass Status!!");
  }

  try {
    result2 = await contractInt(caller, "view_ac_pass_by_unique_id", values);

  } catch (error) {
    console.log("Unable to fetch Your Pass Status!!");
  }


  let createdTimeVal = Number(result1?._value[0]?._attributes?.val?._value);
  console.log(createdTimeVal);


  let descripVal = result1?._value[1]?._attributes?.val?._value?.toString();
  console.log(descripVal);


  let inTimeVal = Number(result1?._value[2]?._attributes?.val?._value);
  console.log(inTimeVal);


  let isExpiredVal = result1?._value[3]?._attributes?.val?._value;
  console.log(isExpiredVal);


  let titleVal = result1?._value[4]?._attributes?.val?._value?.toString();
  console.log(titleVal);


  let passIdVal = Number(result1?._value[5]?._attributes?.val?._value);
  console.log(passIdVal);


  let approvalStatusVal = result2?._value[1]?._attributes?.val?._value;
  console.log(approvalStatusVal);


  let outTimeVal = Number(result2?._value[2]?._attributes?.val?._value);
  console.log(outTimeVal);


  let ansArr = [];
  ansArr.push(createdTimeVal);
  ansArr.push(descripVal);
  ansArr.push(inTimeVal);
  ansArr.push(isExpiredVal);
  ansArr.push(titleVal);
  ansArr.push(passIdVal);
  ansArr.push(approvalStatusVal);
  ansArr.push(outTimeVal);

  return ansArr;
}




export {
  createPass,
  approvePass,
  expirePass,
  fetchAllPassStatus,
  fetchMyPassStatus,
};
Enter fullscreen mode Exit fullscreen mode

The App.js file is as follow;-

import "./App.css";
import Header from "./components/Header";
import Admin from "./components/Admin/Admin";
import RegularUser from "./components/RegularUser/RegularUser";
import { createContext, useState } from "react";
import { ADMIN_KEY } from "./constants/constants";

const pubKeyData = createContext();
const passIdContext = createContext();

function App() {
  const [pubkey, _setPubKey] = useState("");
  const [passId, _setPassId] = useState();

  return (
    <div className="App">
      <pubKeyData.Provider value={pubkey}>
        <Header setPubKey={_setPubKey} />

          <passIdContext.Provider value={{passId, _setPassId}}>
            <RegularUser />
            {pubkey.toString() === ADMIN_KEY && <Admin />}
          </passIdContext.Provider>
      </pubKeyData.Provider>
    </div>
  );
}

export default App;
export { pubKeyData, passIdContext };
Enter fullscreen mode Exit fullscreen mode

The more about my code you can reach to my github repo:-

https://github.com/Ankitsharma2023/volatile

Top comments (0)