A Step-by-Step dApp Tutorial on Building a Decentralized Supply Chain Tracker DApp on Oasis Blockchain:
Revolutionizing Supply Chain Management
In the fast-paced and intricate realm of modern business, supply chains form the backbone of seamless global commerce. Yet, the intricate web of stakeholders, processes, and transactions within supply chains often gives rise to challenges in maintaining transparency, traceability, and accountability.
In response, blockchain technology and smart contract have emerged as groundbreaking a solution with the potential to revolutionize supply chain management.
In this comprehensive technical article, we will guide you through the step-by-step process of creating a decentralized supply chain tracking application using solidity while leveraging the unique capabilities of the Oasis blockchain.
We will also explore the integration of essential tools like Wagmi, RainbowKit, and the Oasis Sapphire network, demonstrating why this particular blockchain is the perfect fit for this transformative endeavor.
Unveiling the Problem: Enhancing Transparency and Traceability in Supply Chains
The traditional landscape of supply chain management is fraught with pressing issues. Gaps in transparency at various stages of the supply chain can result in counterfeit products, delays, and even ethical violations.
The intricacies of tracing an item’s journey from manufacturer to end consumer are compounded by intermediaries whose interests may not align with ensuring transparency.
Moreover, the centralized databases typically used in supply chain management are vulnerable to data manipulation and unauthorized access.
Inadequate coordination and communication among participants can lead to inefficiencies, causing delays, escalating costs, and undermining customer satisfaction.
Unveiling the Solution: Empowering Decentralized Supply Chain Tracking with Smart Contracts
Incorporating blockchain technology, characterized by decentralization, transparency, and immutability, emerges as a potent remedy to the challenges confronting traditional supply chains.
At the heart of this transformative approach lies smart contracts — self-executing scripts residing on the blockchain — which introduce a brilliant method for automating and fortifying agreements.
Central to our mission is the conception of a decentralized supply chain tracking application leveraging smart contracts. This innovative solution begets an indomitable digital ledger, meticulously recording each stage of an item’s journey.
This transparent and distributed ledger stands readily accessible to authorized participants, culminating in an unparalleled level of transparency and traceability, all while substantially curtailing the dependency on intermediaries.
Project Scope: Building a Decentralized Supply Chain Tracking System
Our project embarks on the journey of developing a comprehensive supply chain tracking system, meticulously designed to facilitate seamless order placement, tracking, and cancellation through a user-friendly decentralized application.
The endeavor entails the inception of a resilient smart contract governing the entire process, coupled with an intuitive frontend orchestrating interactions with the contract.
The smart contract’s multifaceted functionality encompasses order initiation, cancellation, status updates, and retrieval of real-time status information.
Oasis Blockchain: The Perfect Canvas for Supply Chain Transformation
The Oasis blockchain emerges as the ideal substrate for this transformative venture. Boasting a robust technology stack, the Oasis Network is a trailblazing, privacy-enabled blockchain platform designed to power the next wave of blockchain innovation.
The Oasis Network’s ability to deliver scalability, privacy, and security, while fostering a rapidly growing and engaged community, makes it an ideal foundation for building decentralized applications that demand transparency, efficiency, and accountability.
Empowering Tools: Leveraging Solidity, Wagmi, RainbowKit, and Oasis Sapphire Network
Our journey is further enriched by the integration of essential tools.
- Solidity is a contract-oriented, high-level programming language for implementing smart contracts. It’s an object-oriented language that targets the Ethereum virtual machine (EVM).
- Wagmi, a cutting-edge blockchain middleware, seamlessly bridges the gap between the Oasis blockchain and the front end, ensuring smooth communication and enhancing user experience.
- RainbowKit, a comprehensive toolkit, equips us with a plethora of pre-built components, accelerating development and fostering innovation.
- The Oasis Sapphire network, a pillar of the Oasis ecosystem, provides a robust and secure environment for deploying and interacting with smart contracts, underscoring the integrity and efficacy of our supply chain tracking application.
Let’s dive right in and start building out our project
Step 1:
Before we start writing our smart contract logic, we would first do the following:
- Set up our development environment
- Create a new project
- Initialize node in the project
- Create a react app using Vite
- Install the Rainbowkit + Wagmi Library
Don’t know how to create a react app using Vite; Here’s a helpful resource. we will be using the Javascript when setting up our react app.
mkdir supply-chain-tracker-tutorial
cd supply-chain-tracker-tutorial
npm init -y
npm create vite@latest
npm install @rainbow-me/rainbowkit wagmi viem
Step 2: Supply Chain Contract
for this tutorial, we will be would be using Remix to write the Oasis Supply Chain smart contract.
Remix IDE is used for the entire journey of smart contract development by users at every knowledge level. It requires no setup, fosters a fast development cycle, and has a rich set of plugins with intuitive GUIs. The IDE comes in two flavors (web app or desktop app) and as a VSCode extension.
Remix IDE is part of the Remix Project which also includes the Remix Plugin Engine and Remix Libraries, which are low-level tools for wider use.
- Click on the contracts folder and then click on the file icon to create a .sol file for your smart contract.
- Copy and paste the smart contract code below to your Remix Online IDE to follow along.
I’ve also added comments that explain what each code block does for better clarity and understanding.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
contract OasisSupplyChain is Ownable {
enum Status {
Ordered,
Shipped,
Delivered,
Cancelled
}
struct Item {
uint id;
string name;
Status status;
address orderedBy;
address approvedBy;
address deliveredTo;
}
mapping(uint => Item) private items; // Mapping to store items by their IDs
uint private itemCount; // Counter to track the total number of items
uint256 private nonce = 0; // Nonce for random number generation
uint256[] private itemIds; // Array to store generated item IDs
/**
* @dev Generates a random number based on various factors.
*/
function getRandomNumber() internal returns (uint256) {
uint256 randomNumber = uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty, msg.sender, nonce)));
nonce++;
return randomNumber;
}
/**
* @dev Generates a random 4-digit number based on a random seed.
* @return A 4-digit random number.
*/
function getRandomFourDigitNumber() internal returns (uint256) {
uint256 randomNumber = getRandomNumber(); // Generate a random number using keccak256
uint256 fourDigitNumber = uint256(randomNumber % 10000); // Ensure it's a 4-digit number
return fourDigitNumber;
}
/**
* @dev Orders a new item with a generated ID.
* @param _name The name of the item.
*/
function orderItem(string memory _name) public {
uint256 newItemId = getRandomFourDigitNumber(); // Generate a random item ID
Item memory newItem = Item({
id: newItemId,
name: _name,
status: Status.Ordered,
orderedBy: msg.sender,
approvedBy: address(0),
deliveredTo: address(0)
});
items[newItemId] = newItem;
itemIds.push(newItemId); // Add the new item ID to the array
itemCount++;
}
/**
* @dev Cancels an item if certain conditions are met.
* @param _id The ID of the item.
*/
function cancelItem(uint _id) public {
require(
items[_id].orderedBy == msg.sender,
"Only the person who ordered the item can cancel it"
);
require(
items[_id].status == Status.Ordered,
"Item can only be cancelled if it is in the Ordered state"
);
items[_id].status = Status.Cancelled;
}
/**
* @dev Approves an ordered item for shipping.
* @param _id The ID of the item.
*/
function approveItem(uint256 _id) public onlyOwner {
require(
items[_id].status == Status.Ordered,
"Item must be in Ordered state to be approved"
);
items[_id].status = Status.Shipped;
items[_id].approvedBy = msg.sender;
}
/**
* @dev Marks a shipped item as delivered.
* @param _id The ID of the item.
*/
function shipItem(uint256 _id) public onlyOwner {
require(
items[_id].status == Status.Shipped,
"Item must be in Shipped state to be shipped"
);
items[_id].status = Status.Delivered;
items[_id].deliveredTo = items[_id].orderedBy;
}
/**
* @dev Retrieves the status of an item.
* @param _id The ID of the item.
* @return The status of the item.
*/
function getItemStatus(uint _id) public view returns (Status) {
return items[_id].status;
}
/**
* @dev Retrieves details of a specific item.
* @param _id The ID of the item.
* @return The item's details.
* @notice Throws an error if the specified ID does not correspond to any item.
*/
function getItem(uint _id) public view returns (Item memory) {
bool isValidId = false;
for (uint256 i = 0; i < itemIds.length; i++) {
if (itemIds[i] == _id) {
isValidId = true;
break;
}
}
require(isValidId, "No item with the specified ID");
return items[_id];
}
/**
* @dev Retrieves the total count of items in the contract.
* @return The total item count.
*/
function getItemCount() public view returns (uint) {
return itemCount;
}
/**
* @dev Retrieves all items stored in the contract.
* @return An array of all items.
*/
function getAllItems() public view returns (Item[] memory) {
Item[] memory allItems = new Item[](itemIds.length);
for (uint256 i = 0; i < itemIds.length; i++) {
allItems[i] = items[itemIds[i]];
}
return allItems;
}
}
It will look like this on Remix Online IDE
Step 3: Compiling the Oasis Supply Chain Contract
- Navigate to the compiler section
- Ensure that the compiler version is the same as the version of your contract
- Click the compile button or use ctrl + enter on Windows or cmd + enter on Mac or simply click the play icon on the Remix Online IDE as seen in the image below.
After the compile process is successful as shown in the image below, you will be able to copy the ABI of your contract which we will use later on in this tutorial on our front end to interact with our smart contract.
Step 4: Deploying the Oasis Supply Chain Contract
We will be deploying the smart contract to Sapphire Testnetwork so for this step, you would need to configure the network on your metamask.
Here’s a helpful resource to get you to import Sapphire testnet in your metamask.
- Once you’re done importing Sapphire into your metamask
- Navigate to the deploy section
- Click on the environment options dropdown and select inject browser in other to use your metamask as shown below.
Make sure you have the Sapphire testnet token as this deployment will require a gas fee.
Click here to get test tokens from the Sapphire Faucet.
A metamask pop-up will show prompting you to connect your wallet to Remix. Upon successful confirmation, you can now deploy the contract to the Sapphire Network as shown below.
Step 5: Verifying the Smart Contract
When a smart contract imports any other contract in the current contract, there are some cases where the verification of such smart contracts does not go through as usual.
In order to verify such a smart contract, the contract needs to be flattened and because our contract used an external library (OpenZeppelin) we would need to flatten it.
- Go back to the contract file in Solidity and right-click.
- The options as seen below will be displayed
- Click on the newly generated flattened contract and copy the flattened code
- Go to https://testnet.explorer.sapphire.oasis.dev/
- Paste the contract address of the just deployed contract as shown in the image below
- Navigate to the code section and click on the
verify & Publish
button as shown below
- Click on next to continue
- Enter the name of the contract and select the correct compiler version as shown in the image below.
Remember that the selected compiler version must match the version the contract was complied with and deployed with.
- Paste the flattened code, copied from the generated flattened file from Remix.
- Click on the
Verify and Publish
button to procced to verifying the contract
- The contract has been successfully verified and is ready to be interacted with on the Sapphire Explorer.
- Navigate to the
Read Contract
section and interact with the read functions
- Navigate to the
Write Contract
section and interact with the write functions.
Use the explorer to test out the contract functions to ensure that everything is working according to and returning the right information.
Step 6: Building The Oasis Supply Chain Tracker Frontend.
We will be building out the user interface of the dApp and as well integrating it with the deployed and verified smart contract using Rainbowkit and Wagmi.
Unfortunately, the Wagmi chain does not support the Oasis Sappahire chain by default but they have a provision for you to set up a custom chain.
That feature is super cool because it will allow us to do a lot more cool stuff later on.
- Create a file on the root directory called
Chain.tsx
and paste the code below to set up a custom chain
import { Chain } from 'wagmi'
export const OasisSapphireTestnet = {
id: 23295,
name: 'Oasis Sapphire Testnet',
network: 'Oasis Sapphire Testnet',
nativeCurrency: {
decimals: 18,
name: 'TEST',
symbol: 'TEST',
},
rpcUrls: {
public: { http: ['https://testnet.sapphire.oasis.dev'] },
default: { http: ['https://testnet.sapphire.oasis.dev'] },
},
blockExplorers: {
etherscan: { name: 'Sapphire', url: 'https://testnet.explorer.sapphire.oasis.dev' },
default: { name: 'Sapphire', url: 'https://testnet.explorer.sapphire.oasis.dev' },
},
} as const satisfies Chain
- Open the React vite app folder from
Step 1
and navigate into themain.jsx
file and set up the Rainbowkit and Wagmi library as shown below;
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
import '@rainbow-me/rainbowkit/styles.css';
import { ChakraProvider } from '@chakra-ui/react'
import {
getDefaultWallets,
RainbowKitProvider,
} from '@rainbow-me/rainbowkit';
import { configureChains, createConfig, WagmiConfig } from 'wagmi';
import { OasisSapphireTestnet } from './Chain'
import { jsonRpcProvider } from '@wagmi/core/providers/jsonRpc'
// Configuring the Oasis Sapphire Network Chain
const { chains, publicClient } = configureChains(
[OasisSapphireTestnet],
[
jsonRpcProvider({
rpc: (chain) => ({ http: chain.rpcUrls.default.http[0]
}),
}),
]
);
const { connectors } = getDefaultWallets({
appName: 'Oasis Supply Chain Tracker',
projectId: 'ENTER YOUR PROJECT_ID',
chains
});
const wagmiConfig = createConfig({
autoConnect: true,
connectors,
publicClient
})
ReactDOM.createRoot(document.getElementById('root')).render(
<WagmiConfig config={wagmiConfig}>
<RainbowKitProvider chains={chains}>
<React.StrictMode>
<ChakraProvider>
<App />
</ChakraProvider>
</React.StrictMode>,
</RainbowKitProvider>
</WagmiConfig>
)
Let's continue designing the UI of our dApp
- Inside the src folder, create two folders and title it
components
andutils.
- Create a file inside the
components
folder and name it button. This component will be the button to be used throughout the project. Paste the code below into the file you just created.
export default function Button({
title,
textColor,
bgColor,
onClick,
disabled }) {
return (
<button
disabled={disabled}
className={`py-2 px-10 rounded ${disabled ? 'bg-gray-300' : bgColor ? bgColor : 'bg-blue-600'} ${textColor ? textColor : 'text-white'}`}
onClick={onClick}
>
{title}
</button>
);
}
- Create five more files inside the
components
folder and name itnavbar.jsx
,inputField.jsx
,footer.jsx
,form.jsx
,information.jsx
. - Paste the code below in the
navbar.jsx
file
import { ConnectButton } from '@rainbow-me/rainbowkit';
export default function Navbar() {
return (
<>
<div className="fixed bg-white w-full border-b p-5 z-50">
<div className="md:max-w-7xl mx-auto">
<div className="flex justify-between items-center space-x-5 md:space-x-10">
<div className="flex space-x-2 md:space-x-3 items-center font-extrabold">
<img src="/logo.png" alt=""
className="rounded-full object-cover h-12 w-12"
/>
<span className="text-2xl md:text-4xl text-emerald-900">Supply Chain Tracker</span>
</div>
<div className="flex space-x-5">
<ConnectButton showBalance={false}/>
</div>
</div>
</div>
</div>
</>
)
}
- Paste the code below in the
information.jsx
file
export default function Information() {
return (
<>
<div className="md:flex md:space-x-10 items-center">
<div className='p-5 md:p-0 w-full md:w-1/2' data-aos="fade-down"
data-aos-easing="ease-out-cubic"
data-aos-duration="2000">
<h1 className='text-2xl md:text-6xl font-bold py-10'
>Decentralised Supply Chain Tracking </h1>
<p className='text-gray-500 py-2'
>
This is a decentralized application (dApp) built on top of the Oasis Layer 1 blockchain.
The dApp is designed to help users track the status of items in a supply chain.
</p>
</div>
<div className='w-full md:w-1/2'
data-aos="zoom-in"
data-aos-easing="ease-out-cubic"
>
<img src="/img1.avif" alt=""
className="rounded object-cover h-full w-full max-h-[350px] md:max-w-1/2"
/>
</div>
</div>
</>
)
}
- Paste the code below in the
inputField.jsx
file
export default function InputField({
placeholder, value, onchange, type
}) {
return (
<input
type={type}
id="voice-search"
className="bg-white text-gray-900 text-sm w-full p-2 border rounded focus:ring-0 focus:outline-none"
placeholder={placeholder}
value={value}
onChange={(e) => onchange(e.target.value)}
/>
);
}
- Paste the code below in the
footer.jsx
file
export default function Footer() {
return (
<div className="bg-blue-600">
<div className="mx-auto max-w-7xl py-8">
<div className="md:flex md:space-x-10 text-center justify-center items-center">
<p className="text-white">© All rights reserved. 2023</p>
</div>
</div>
</div>
);
}
Before we write the code for interacting with our smart contract from the front end.
- Create a new file inside the utils folder and name it
OasisSupplyChainABI.json
and then paste your copied ABI code from Remix inside as shown below
- Create another folder still inside the utils folder and name it
contract-interact.
In this folder, we will store our smart contract function to ensure our code base is clean and easy to read.
Also inside the contract-interact
folder, create the following files; orderItem.jsx
, approveItem.jsx
, cancelItem.jsx
, shipItem.jsx
, searchItem.jsx
.
- Paste the code below in the
orderItem.jsx
file
import { useState } from 'react';
import { usePrepareContractWrite, useContractWrite } from 'wagmi';
import { OasisSupplyChainABI } from '../OasisSupplyChainABI'
function useOrderItem() {
// State to store the item to be ordered
const [itemName, setItemName] = useState('');
const { config } = usePrepareContractWrite({
address: "ENTER YOUR OASIS_SUPPLY_CHAIN_CONTRACT_ADDRESS",
abi: OasisSupplyChainABI,
functionName: 'orderItem',
args: [itemName],
})
const { data, isLoading, isSuccess, write } = useContractWrite(config);
const orderItem = async => {
if (write) {
write();
}
}
// Return the necessary values and functions for external use
return {
isLoading, orderItem, itemName, setItemName, isSuccess,
};
}
export default useOrderItem;
- Paste the code below in the
approveItem.jsx
file
import { useState } from 'react';
import { usePrepareContractWrite, useContractWrite } from 'wagmi';
import { OasisSupplyChainABI } from '../OasisSupplyChainABI'
function useApproveItem() {
// State to store the item ID to be approved
const [approveItemId, setApproveItemId] = useState('');
// Prepare the configuration for the contract write operation
const { config } = usePrepareContractWrite({
address: "ENTER YOUR OASIS_SUPPLY_CHAIN_CONTRACT_ADDRESS",
abi: OasisSupplyChainABI,
functionName: 'approveItem',
args: [approveItemId],
})
const { data, isLoading: approveIsLoading, isSuccess: approveIsSuccess, write } = useContractWrite(config);
const approveItem = async (itemId) => {
if (write) {
setApproveItemId(itemId);
write();
}
}
// Return the necessary values and functions for external use
return {
approveIsLoading, approveItem, approveItemId, setApproveItemId, approveIsSuccess,
};
}
export default useApproveItem;
- Paste the code below in the
cancelItem.jsx
file
import { useState } from 'react';
import { usePrepareContractWrite, useContractWrite } from 'wagmi';
import { OasisSupplyChainABI } from '../OasisSupplyChainABI'
function useCancelItem() {
// State to store the item ID to be cancelled
const [cancelItemId, setCancelItemId] = useState('');
// Prepare the configuration for the contract write operation
const { config } = usePrepareContractWrite({
address: "ENTER YOUR OASIS_SUPPLY_CHAIN_CONTRACT_ADDRESS",
abi: OasisSupplyChainABI,
functionName: 'cancelItem',
args: [cancelItemId],
})
const { data, isLoading: cancelIsLoading, isSuccess: cancelIsSuccess, write } = useContractWrite(config);
const cancelItem = async(itemId) => {
if (write) {
setCancelItemId(itemId);
write();
}
}
// Return the necessary values and functions for external use
return {
cancelIsLoading, cancelItem, cancelItemId, setCancelItemId, cancelIsSuccess,
};
}
export default useCancelItem;
- Paste the code below in the
searchItem.jsx
file
import { useState } from 'react';
import { useContractRead } from 'wagmi';
import { OasisSupplyChainABI } from '../OasisSupplyChainABI'
function useSearchItem() {
// Storing the state variables
const [itemId, setItemId] = useState('');
const [itemDetails, setItemDetails] = useState(null);
const [items, setItems] = useState([]);
// Prepare the configuration for the contract read operation
const { data: searchItemData } = useContractRead({
address: "ENTER YOUR OASIS_SUPPLY_CHAIN_CONTRACT_ADDRESS",
abi: OasisSupplyChainABI,
functionName: 'getItem',
args: [itemId]
});
const { data: getAllItemData } = useContractRead({
address: import.meta.env.VITE_OASIS_SUPPLY_CHAIN_CONTRACT,
abi: OasisSupplyChainABI,
functionName: 'getAllItems',
});
const getAllItem = async () => {
if (getAllItemData) {
setItems(getAllItemData);
}
};
const getItem = async () => {
if (searchItemData) {
setItemDetails(searchItemData);
}
};
// Return the necessary values and functions for external use
return {
itemId,
itemDetails,
setItemDetails,
setItemId,
getItem,
getAllItem,
items,
setItems,
};
}
export default useSearchItem;
- Paste the code below in the
shipItem.jsx
file
import { useState } from 'react';
import { usePrepareContractWrite, useContractWrite } from 'wagmi';
import { OasisSupplyChainABI } from '../OasisSupplyChainABI'
function useShipItem() {
// State to store the item ID to be shipped
const [shipItemId, setShipItemId] = useState('');
// Prepare the configuration for the contract read operation
const { config } = usePrepareContractWrite({
address: import.meta.env.VITE_OASIS_SUPPLY_CHAIN_CONTRACT,
abi: OasisSupplyChainABI,
functionName: 'shipItem',
args: [shipItemId],
})
const { data, isLoading: shipIsLoading, isSuccess: shipIsSuccess, write } = useContractWrite(config);
const shipItem = async (itemId) => {
if (write) {
setShipItemId(itemId);
write();
}
}
// Return the necessary values and functions for external use
return {
shipIsLoading, shipItem, shipItemId, setShipItemId, shipIsSuccess,
};
}
export default useShipItem;
- Navigate back to the
components
folder and paste the code below inside theform.jsx
file.
import { useState, useEffect } from 'react';
import InputField from './InputField';
import Button from './button';
import { Spinner } from '@chakra-ui/react'
import useSearchItem from '../utils/contract-interact/searchItems';
import useOrderItem from '../utils/contract-interact/orderItem';
import useApproveItem from '../utils/contract-interact/approveItem';
import useCancelItem from '../utils/contract-interact/cancelItem';
import useShipItem from '../utils/contract-interact/shipItem';
function SupplyChain() {
const [loading, setLoading] = useState(false);
const {
itemId,
itemDetails,
setItemDetails,
setItemId,
getItem,
getAllItem,
items,
setItems,
} = useSearchItem();
const {
isLoading, orderItem, itemName, setItemName, isSuccess
} = useOrderItem()
const {
approveIsLoading, approveItem, approveItemId,
setApproveItemId, approveIsSuccess,
} = useApproveItem()
const {
cancelIsLoading, cancelItem, cancelItemId, setCancelItemId, cancelIsSuccess,
} = useCancelItem()
const {
shipIsLoading, shipItem, shipItemId, setShipItemId, shipIsSuccess,
} = useShipItem()
useEffect(() => {
getAllItem();
}, []);
function getStatusText(status) {
switch (status) {
case 0:
return "Ordered";
case 1:
return "Approved";
case 2:
return "Delivered";
case 3:
return "Cancelled";
default:
return "";
}
}
function displayPartialAddress(address) {
if (address.length <= 7) {
return address;
} else {
const firstThree = address.substring(0, 3);
const lastFour = address.substring(address.length - 4);
return `${firstThree}...${lastFour}`;
}
}
const cols = [
"ID", "Name", "Status", "Ordered by", "Approved by", "Delivered to"
];
return (
<>
<div className="overflow-hidden rounded-lg border border-gray-200 shadow-md m-5" data-aos="fade-up" data-aos-offset="300" data-aos-easing="ease-in-sine">
<div className="m-10 flex justify-between space-x-5">
<div className="w-full md:w-1/2 flex justify-between items-center space-x-3">
<InputField
value={itemName}
onchange={(e) => setItemName(e)}
placeholder="Type your item here ..."
/>
<Button
title={isLoading ? <Spinner /> : "Order"}
onClick={orderItem}
disabled={itemName === ""}
/>
</div>
<div className="w-full md:w-1/2 flex justify-between items-center space-x-3">
<InputField
value={itemId}
type={"number"}
onchange={(e) => setItemId(e)}
placeholder="Enter item ID ..."
/>
<Button
title= "Search"
onClick={getItem}
disabled={itemId < 0}
/>
<button className='mx-5 text-blue-600' onClick={getAllItem}>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="2.5" stroke="currentColor" className="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
</svg>
</button>
</div>
</div>
<div className='m-10'>
<div className="mb-4">
{loading ? (
<div className="text-center text-green-600">Loading...</div>
) : itemDetails && (
<div className="border border-gray-300 p-4 rounded">
<div>Item ID: {`${itemDetails.id}`}</div>
<div className='text-sm'>Name: {itemDetails.name}</div>
<div className='text-sm'>Status: {getStatusText(itemDetails.status)}</div>
<div className='text-sm'>Ordered By: {itemDetails.orderedBy}</div>
<div className='text-sm'>Approved By: {itemDetails.approvedBy}</div>
<div className='text-sm'>Delivered To: {itemDetails.deliveredTo}</div>
</div>
)}
</div>
</div>
<table className="w-full border-collapse bg-white text-left text-sm text-gray-500">
<thead className="bg-gray-50">
<tr>
{cols.map((col) => (
<th
scope="col"
className="px-6 py-4 font-medium text-gray-900"
key={col}
>
{col}
</th>
))}
<th
scope="col"
className="px-6 py-4 text-center font-medium text-gray-900"
>
Actions
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-100 border-t border-gray-100">
{items.length < 1 ? (
<tr>
<td
colSpan={cols.length + 1}
className="text-center text-xl py-10"
>
No items to display
</td>
</tr>
) :
(
items
.sort((a, b) => Number(b.id) - Number(a.id))
.map((item, index) => (
<tr className="hover:bg-gray-50" key={index}>
<th className="flex gap-3 px-6 py-2 font-normal text-gree-900">
{`${item.id}`}
</th>
<td
className="px-6 py-2 cursor-pointer"
onClick={() => setItemDetails(item)}
>
{item.name}
</td>
<td className="px-6 py-2">
<span
className={`inline-flex items-center gap-1 rounded-full bg-green-50 px-2 py-1 text-xs font-semibold ${item.status === 3
? 'text-red-600'
: 'text-green-600'
}`}
>
<span
className={`h-1.5 w-1.5 rounded-full ${item.status === 3
? 'bg-red-600'
: 'bg-green-600'
}`}
/>
{getStatusText(item.status)}
</span>
</td>
<td className="px-6 py-2">
{displayPartialAddress(item.orderedBy)}
</td>
<td className="px-6 py-2">
{displayPartialAddress(item.approvedBy)}
</td>
<td className="px-6 py-2">
{displayPartialAddress(item.deliveredTo)}
</td>
<td className="px-6 py-2 text-center">
<div className="flex justify-end space-x-3 gap-4">
{item.status === 0 && (
<>
<button
className='text-red-600' onClick={() => cancelItem(Number(item.id))}>
{cancelIsLoading ? 'Cancelling...' : 'Cancel'}
</button>
<button
className='text-green-600' onClick={() => approveItem(Number(item.id))}>
{approveIsLoading ? 'Approving...' : 'Approve'}
</button>
</>
)}
{item.status === 1 && (
<button className='text-blue-600' onClick={() => shipItem(item.id)}>
{shipIsLoading
? 'Shipping...'
: shipIsSuccess
? 'Item has been shipped!'
: 'Ship Item'}
</button>
)}
</div>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</>
);
}
export default SupplyChain;
The provided code represents the core functionality of our decentralized supply chain tracking application built on the Oasis blockchain.
The React component, SupplyChain
, serves as the user interface to interact with the supply chain smart contracts. It leverages various custom hooks from the contract-interact
directory to enable essential actions such as ordering items, approving items, canceling orders, and shipping items.
The component displays a user-friendly interface for searching, ordering, and managing items within the supply chain. It also dynamically updates and visualizes item statuses, authorized parties, and actions, providing a comprehensive view of the entire supply chain process.
Step 7: Bringing it All Together
With all the components created, along with the scripts to interact with our smart contract, now prepared, the next crucial step is to integrate them all into our react dApp and create our very own decentralized Supply Chain Tracker.
- Open the
app.jsx
file still inside thesrc
folder and paste the code below.
import Form from './components/form';
import Navbar from './components/navbar';
import Information from './components/information';
import ExtraInformation from './components/extraInformation';
import Footer from './components/footer';
import AOS from 'aos';
import 'aos/dist/aos.css';
import { useEffect } from 'react';
export default function App() {
useEffect(() => {
AOS.init();
}, []);
return (
<>
<Navbar />
<div className="md:max-w-7xl mx-auto pt-32">
<div>
<Information />
</div>
<div>
<Form />
</div>
</div>
<Footer />
</>
);
}
If you’re carefully following along, run npm run dev
in your terminal to power up your local host http://localhost:5173
and you should see a screen like the one below.
Go ahead and click on the connect wallet button to connect your wallet, and start making interactions with your dApp as can be seen below.
Step 7: Deploying Supply Chain Tracker dApp
Next, we will be deploying our dApp using Vercel and I would walk you through exactly how to do that, so stay with me.
- First, you have to push the project you wish to deploy to your GitHub repository, and here is a resource that shows you how to do that.
- Click here to create an account or log in to start deploying with Vercel as shown in the image below.
- In your Vercel dashboard, you will see a list of all projects inside your GitHub repository so select the project which you wish to deploy and click
import
as shown below.
- Insert your preferred project name.
- You have successfully deployed your supply chain tracker dApp.
Click on the continue to Dashboard
button to head over to your dashboard where you can copy the link to your website and share it with your friends.
Let your friends and the rest of the world see the cool stuff you’re built on one of the most amazing blockchains out there.
Congratulations on accomplishing the following!!!
- You have now successfully written and deployed a smart contract on the Oasis Sapphire Network.
- You built a functional Frontend decentralized application allowing you to interact with the smart contract.
- You deployed your website on Vercel allowing everyone to interact with your Supply Chain Tracker dApp.
Summary: Advancing Towards a Transparent and Efficient Future
The solution we propose is poised to revolutionize supply chain management, offering an array of advantages that encompass heightened transparency, streamlined processes, reduced costs, enhanced data security, fortified trust, and simplified compliance.
As we traverse the technical landscape of building this decentralized supply chain tracking application on the Oasis blockchain, you’re invited to join us on this transformative journey that promises to reshape the future of supply chain management through cutting-edge technology, innovative tools, and an unwavering commitment to excellence.
Happy Building 🤝
Helpful Resources:
- How to deploy your website using Vercel: https://dev.to/therickedge/how-to-deploy-your-website-using-vercel-4499
- Step by Step deploying your website on Netlify: https://www.netlify.com/blog/2016/09/29/a-step-by-step-guide-deploying-on-netlify/
- Link to the project GitHub repository: https://github.com/amdonatusprince/oasis-supply-chain-dapp
- Link to the Deployed Website on Vercel:
https://oasis-supplychaintracker.vercel.app/ - Oasis Network’s website: https://oasisprotocol.org/
- Oasis Sapphire documentation: https://docs.oasis.io/dapp/sapphire/
- Remix website: https://remix.ethereum.org/