DEV Community

Cover image for শার্দিয়াম-এ একটি এনএফটি মিন্টার ড্যাপ তৈরি করুন
Shafiqul Mridha
Shafiqul Mridha

Posted on

শার্দিয়াম-এ একটি এনএফটি মিন্টার ড্যাপ তৈরি করুন

এই নির্দেশিকাটি আপনাকে একটি সাধারণ এনএফটি মিন্টার অ্যাপ্লিকেশন তৈরির প্রক্রিয়ার মধ্য দিয়ে নিয়ে যাবে যা ফ্রন্ট-এন্ডের জন্য রিএক্ট জেএস, বিকেন্দ্রীভূত স্টোরেজের জন্য আইপিএফএস ব্যবহার করে এবং শার্দিয়াম টেস্টনেটে একটি সলিডিটি স্মার্ট চুক্তি স্থাপন করে। এই অ্যাপ্লিকেশানটি ব্যবহারকারীদের নাম, বিবরণ এবং চিত্র/গিফ-এর মতো কাস্টম মেটাডেটা সহ এনএফটি মিন্ট করতে দেয়।

১. আমাদের প্রকল্প সেট আপ করা

একটি খালি প্রজেক্ট ফাইল তৈরি করা এবং npm শুরু করা যাক।

mkdir shardeum-nft-minter
cd shardeum-nft-minter
npm init
Enter fullscreen mode Exit fullscreen mode

২. স্মার্ট চুক্তি এবং হার্ডহাট সেটআপ

আমরা স্মার্ট কন্ট্রাক্ট স্থাপন, পরীক্ষা এবং ডিবাগ করার জন্য Hardhat – একটি ডেভেলপমেন্ট ফ্রেমওয়ার্ক ব্যবহার করব।

i) এখন, একটি ডেভ-নির্ভরতা হিসাবে হার্ডহাট ইনস্টল করা যাক; 'Create an empty hardhat.config.js' নির্বাচন করুন এবং ওপেনজেপেলিন কন্ট্রাক্ট লাইব্রেরি ইনস্টল করুন। এছাড়াও, আসুন অন্যান্য প্রয়োজনীয় হার্ডহাট লাইব্রেরিগুলি ইনস্টল করি।

npm install --save-dev hardhat
npx hardhat
npm install @openzeppelin/contracts
npm install --save ethers@5.7.2 hardhat @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers
Enter fullscreen mode Exit fullscreen mode

ii) একটি 'contracts' এবং 'scripts' ফোল্ডার তৈরি করুন। চুক্তি ফোল্ডারে, NftMinter.sol ফাইলে নিম্নলিখিত কোড যোগ করুন:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.3;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract NFTMinter is ERC721URIStorage {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;
    constructor() ERC721("Shardeum Dev NFTMinter, "SNFT") {}
    function mintNFT(address recipient, string memory tokenURI) public returns (uint256) {
        _tokenIds.increment();
        uint256 newItemId = _tokenIds.current();
        _mint(recipient, newItemId);
        _setTokenURI(newItemId, tokenURI);
        return newItemId;
    }
}
Enter fullscreen mode Exit fullscreen mode

উপরের চুক্তিটি OpenZeppelin থেকে ERC721URIStorage চুক্তি থেকে উত্তরাধিকারসূত্রে প্রাপ্ত, যা একটি ERC721-এর প্রয়োজনীয় কার্যকারিতাগুলির বেশিরভাগই আমাদের কাছে উপলব্ধ করে। আপনি কনস্ট্রাক্টরকে কাস্টমাইজ করতে পারেন এবং আপনার পছন্দ মতো ERC721 টোকেনের নাম এবং চিহ্নের নাম পরিবর্তন করতে পারেন। আমরা একটি minNFT ফাংশনও সংজ্ঞায়িত করছি যেখানে আমরা tokenIDs ট্র্যাক রাখি এবং tokenURI সেট করি যা সেই টোকেনের মেটাডেটা সংরক্ষণ করে। এটাই, এই একটি স্মার্ট চুক্তি আমাদের মিন্টার অ্যাপ্লিকেশনের জন্য যথেষ্ট।

iii) আপনার স্ক্রিপ্ট ফোল্ডারে একটি deploy.js ফাইল তৈরি করুন এবং নিম্নলিখিত স্থাপনার স্ক্রিপ্ট যোগ করুন:

const { ethers } = require("hardhat");

async function main() {
    const NFTMinter = await ethers.getContractFactory("NFTMinter");
    const nftMinter = await NFTMinter.deploy();
    await nftMinter.deployed();
    console.log("NFTMinter deployed to:", nftMinter.address);
  }

    main()
    .then(() => process.exit(0))
    .catch((error) => {
      console.error(error);
      process.exit(1);
    });
Enter fullscreen mode Exit fullscreen mode

iv) এখন, শার্ডিয়াম টেস্টনেট এ স্থাপনের জন্য প্রয়োজনীয় কোড যোগ করা যাক।

require("@nomiclabs/hardhat-waffle");
module.exports = {
  networks: {
    hardhat: {
    },
    sphinx: {
      url: "https://dapps.shardeum.org/",
      accounts:[``] // Add private key here
    },
  solidity: "0.8.3",
};
Enter fullscreen mode Exit fullscreen mode

অ্যাকাউন্ট ভেরিয়েবলে আপনার ব্যক্তিগত কী যোগ করুন এবং নিশ্চিত করুন যে আপনার অ্যাকাউন্টে পর্যাপ্ত শার্ডিয়াম টেস্টনেট টোকেন রয়েছে।

v) এখন, এই উদাহরণের জন্য শার্দিয়াম স্ফিংস ড্যাপ-এ আমাদের স্মার্ট চুক্তিগুলি স্থাপন করা যাক।

npx hardhat run scripts/deploy.js --network sphinx
Enter fullscreen mode Exit fullscreen mode

ডিপ্লোয় স্ক্রিপ্টটি শার্ডিয়াম টেস্টনেট-এ স্মার্ট কন্ট্রাক্ট স্থাপন করবে এবং ডিপ্লোয়ড স্মার্ট কন্ট্রাক্ট অ্যাড্রেস আউটপুট করবে। আপনার এই ঠিকানাটি পরে প্রয়োজন হবে, তাই এটি সংরক্ষণ করুন।

৩. একটি ফ্রন্ট-এন্ড অ্যাপ্লিকেশন তৈরি করুন

এখন, আমাদের স্থাপন করা স্মার্ট চুক্তির সাথে ইন্টারঅ্যাক্ট করার জন্য একটি মৌলিক ফ্রন্ট-এন্ড অ্যাপ্লিকেশন তৈরি করা যাক।

আসুন একই ফোল্ডারে একটি রিয়েক্ট-অ্যাপ্লিকেশন শুরু করা শুরু করি। রিয়েক্ট অ্যাপ্লিকেশন সেট আপ করার পরে, সমস্ত প্রয়োজনীয় ফ্রন্ট-এন্ড প্যাকেজ ইনস্টল করুন।

npx create-react-app .
npm install @emotion/react @emotion/styled @mui/material axios
Enter fullscreen mode Exit fullscreen mode

আমরা src ফোল্ডারে আমাদের ফ্রন্ট-এন্ড কোড পরিবর্তন করব। আপনার artifacts ফোল্ডার থেকে NFTMinter.json ফাইলটি সনাক্ত করুন এবং এটি src ফোল্ডারে আনুন।

প্রয়োজনীয় কার্যকারিতা পেতে আপনাকে চারটি নতুন জাভাস্ক্রিপ্ট ফাইল তৈরি করতে হবে:

  • connectWallet.js: এই ফাইলটি আমাদের সামনের প্রান্তকে স্মার্ট চুক্তির সাথে সংযুক্ত করতে দেবে এবং আমাদের মেটামাস্কের সাথে সংযুক্ত করবে। নির্ধারিত ভেরিয়েবলে আপনার চুক্তির ঠিকানা যোগ করুন।
import { ethers } from "ethers";
import NFTMinter from "./NftMinter.json";
export async function connectWallet() {
  await window.ethereum.request({ method: "eth_requestAccounts" });
  const provider = new ethers.providers.Web3Provider(window.ethereum);
  const signer = provider.getSigner();
  // Insert deployed contract address here
  const contract = new ethers.Contract(``, NFTMinter.abi, signer);

  return { signer, contract };
  }

  export async function connectMetaMask (){
    const { signer } = await connectWallet();
    const address = await signer.getAddress();
    const balance = await signer.getBalance();
    const formattedBalance = ethers.utils.formatEther(balance);
    return {address, formattedBalance}
  };
Enter fullscreen mode Exit fullscreen mode
  • ipfsUploader.js: পিনাটা ব্যবহার করে আমাদের ফাইলগুলি ipfs এ আপলোড করার জন্য প্রয়োজনীয় সমস্ত কোড এই ফাইলটিতে রয়েছে। পিনাটা এ একটি অ্যাকাউন্ট তৈরি করুন এবং একটি নতুন এপিআই লিঙ্ক তৈরি করুন।
import axios from 'axios';
const pinataApiKey = ``; // Insert pinata Api Key
const pinataApiSecret = `` ; // Insert pinata Api secret
const pinataApiUrl = 'https://api.pinata.cloud/pinning/pinFileToIPFS';
const pinataHeaders = {
  headers: {
    'Content-Type': 'multipart/form-data',
    pinata_api_key: pinataApiKey,
    pinata_secret_api_key: pinataApiSecret,
  },
};
export async function uploadToIPFS(file) {
  const formData = new FormData();
  formData.append('file', file);
  try {
    const response = await axios.post(pinataApiUrl, formData, pinataHeaders);
    const ipfsHash = response.data.IpfsHash;
    return `https://gateway.pinata.cloud/ipfs/${ipfsHash}`;
  } catch (error) {
    console.error('Error uploading file to Pinata:', error);
    throw error;
  }
}
Enter fullscreen mode Exit fullscreen mode
  • MintNFT.js: এই ফাইলটিতে আমাদের মিন্টিং অ্যাপ্লিকেশনের প্রধান উপাদান এবং আমাদের ফ্রন্ট-এন্ড কোড রয়েছে।
import React, { useState } from "react";
import { connectWallet, connectMetaMask } from "./connectWallet";
import { uploadToIPFS } from "./ipfsUploader";
import {
  TextField,
  Button,
  Typography,
  Container,
  Box,
  Link,
  Grid,
  Snackbar,
  Alert,
  LinearProgress,
} from "@mui/material";
function MintNFT() {
  const [name, setName] = useState("");
  const [description, setDescription] = useState("");
  const [image, setImage] = useState(null);
  const [status, setStatus] = useState("");
  const [ipfsLink, setIpfsLink] = useState("");
  const [imageStatus, setImageStatus] = useState("");
  const [alertOpen, setAlertOpen] = useState(false);
  const [loading, setLoading] = useState(false);
  const [walletAddress, setWalletAddress] = useState("");
  const [walletBalance, setWalletBalance] = useState("");
  const [imagePreviewUrl, setImagePreviewUrl] = useState(null);
  const [transactionHistory, setTransactionHistory] = useState([]);
  const handleConnectMetaMask = async () => {
    const { address, formattedBalance } = await connectMetaMask();
    setWalletAddress(address);
    setWalletBalance(formattedBalance);
  };
  const handleImageChange = (e) => {
    setImage(e.target.files[0]);
    setImageStatus("Image selected for upload");
    setImagePreviewUrl(URL.createObjectURL(e.target.files[0]));
  };
  const mint = async () => {
    setStatus("Uploading to IPFS...");
    const imageURI = await uploadToIPFS(image);
    setIpfsLink(imageURI);
    setStatus("Minting NFT...");
    setLoading(true);
    const { signer, contract } = await connectWallet();
    const tokenURI = `data:application/json;base64,${btoa(
      JSON.stringify({
        name,
        description,
        image: imageURI,
      })
    )}`;
    const transaction = await contract.mintNFT(signer.getAddress(), tokenURI);
    await transaction.wait();
    setTransactionHistory((prevHistory) => [
      ...prevHistory,
      transaction.hash,
    ]);
    setStatus("NFT minted!");
    setAlertOpen(true);
    setLoading(false);
  };
  return (

    <Container maxWidth="lg">
      <Box sx={{ mt: 4, mb: 2 }}>
        <Typography variant="h4" align="center" gutterBottom>
          Shardeum NFT Minter
        </Typography>
      </Box>
      <Grid container spacing={2}>
        <Grid item xs={12} md={6}>
        <Box mt={2}>
              <Button
                fullWidth
                variant="contained"
                color="primary"
                onClick={handleConnectMetaMask}
                size="small"
                disabled={walletAddress} 
              >
                {walletAddress ? "Wallet Connected" : "Connect Wallet to Shardeum Sphinx Dapp 1.X"}
              </Button>
            </Box>
          {walletAddress && (
            <Box mt={2}>
              <Typography align="center">
                Wallet Address: {walletAddress}
              </Typography>
              <Typography align="center">
                Wallet Balance: {walletBalance} SHM
              </Typography>
            </Box>
          )}
          <TextField
            fullWidth
            label="NFT Name"
            variant="outlined"
            margin="normal"
            onChange={(e) => setName(e.target.value)}
          />
          <TextField
            fullWidth
            label="NFT Description"
            variant="outlined"
            margin="normal"
            onChange={(e) => setDescription(e.target.value)}
          />
          <input
            type="file"
            style={{ display: "none" }}
            id="image-upload"
            onChange={handleImageChange}
          />
          <p></p>
          <label      htmlFor="image-upload">
        <Button variant="contained" color="primary" component="span">
          Upload Image
        </Button>
      </label>
      {imageStatus && (
        <Typography variant="caption" display="block" gutterBottom>
          {imageStatus}
        </Typography>
      )}
      <Box mt={2}>
        <Button
          fullWidth
          variant="contained"
          color="secondary"
          onClick={mint}
        >
          Mint NFT
        </Button>
      </Box>
      {loading && <LinearProgress />}

      <Snackbar
        open={alertOpen}
        autoHideDuration={6000}
        onClose={() => setAlertOpen(false)}
        anchorOrigin={{ vertical: "top", horizontal: "right" }}
      >
        <Alert
          onClose={() => setAlertOpen(false)}
          severity="success"
          variant="filled"
          sx={{ width: "100%" }}
        >
          NFT minted successfully!
        </Alert>
      </Snackbar>
    </Grid>
    <Grid item xs={12} md={6}>
  <Box
    mt={2}
    sx={{
      border: "1px dashed #999",
      borderRadius: "12px",
      padding: "16px",
      display: "flex",
      justifyContent: "center",
      alignItems: "center",
      minHeight: "300px",
      background: imagePreviewUrl
        ? "none"
        : "linear-gradient(45deg, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%)",
    }}
  >
    {imagePreviewUrl ? (
      <img
        src={imagePreviewUrl}
        alt="Uploaded preview"
        style={{
          width: "100%",
          maxHeight: "300px",
          objectFit: "contain",
          borderRadius: "12px",
        }}
      />
    ) : (
      <Typography variant="caption" color="text.secondary">
        Preview image will be displayed here
      </Typography>
    )}
  </Box>
</Grid>
    <Box mt={2}>
        <Typography align="center" color="textSecondary">
          {status}
        </Typography>
        {ipfsLink && (
      <Typography align="left">
    IPFS Link:{" "}
    <Link href={ipfsLink} target="_blank" rel="noopener noreferrer">
      {ipfsLink}
    </Link>
      </Typography>
)}
      </Box>
  </Grid>
  <Box mt={4}>
        <Typography variant="h7" align="center">
          Transaction History:
        </Typography>
        {transactionHistory.length > 0 ? (
          transactionHistory.map((hash, index) => (
            <Box key={index} mt={1} textAlign="left">
              <Link
                href={`https://explorer-dapps.shardeum.org/transaction/${hash}`}
                target="_blank"
                rel="noopener noreferrer"
              >
                {`Transaction ${index + 1}: ${hash}`}
              </Link>
            </Box>
          ))
        ) : (
          <Typography align="center" mt={1}>
            No transactions yet.
          </Typography>
        )}import React, { useState } from "react";
import { connectWallet, connectMetaMask } from "./connectWallet";
import { uploadToIPFS } from "./ipfsUploader";
import {TextField,Button,Typography,Container,Box,Link,Grid,Snackbar,Alert,LinearProgress,} from "@mui/material";
function MintNFT() {
  const [name, setName] = useState("");
  const [description, setDescription] = useState("");
  const [image, setImage] = useState(null);
  const [status, setStatus] = useState("");
  const [ipfsLink, setIpfsLink] = useState("");
  const [imageStatus, setImageStatus] = useState("");
  const [alertOpen, setAlertOpen] = useState(false);
  const [loading, setLoading] = useState(false);
  const [walletAddress, setWalletAddress] = useState("");
  const [walletBalance, setWalletBalance] = useState("");
  const [imagePreviewUrl, setImagePreviewUrl] = useState(null);
  const [transactionHistory, setTransactionHistory] = useState([]);
  const handleConnectMetaMask = async () => {
    const { address, formattedBalance } = await connectMetaMask();
    setWalletAddress(address);
    setWalletBalance(formattedBalance);
  };
  const handleImageChange = (e) => {
    setImage(e.target.files[0]);
    setImageStatus("Image selected for upload");
    setImagePreviewUrl(URL.createObjectURL(e.target.files[0]));
  };
  const mint = async () => {
    setStatus("Uploading to IPFS...");
    const imageURI = await uploadToIPFS(image);
    setIpfsLink(imageURI);
    setStatus("Minting NFT...");
    setLoading(true);
    const { signer, contract } = await connectWallet();
    const tokenURI = `data:application/json;base64,${btoa(
      JSON.stringify({
        name,
        description,
        image: imageURI,
      })
    )}`;
    const transaction = await contract.mintNFT(signer.getAddress(), tokenURI);
    await transaction.wait();
    setTransactionHistory((prevHistory) => [
      ...prevHistory,
      transaction.hash,
    ]);
    setStatus("NFT minted!");
    setAlertOpen(true);
    setLoading(false);
  };
  return (

    <Container maxWidth="lg">
      <Box sx={{ mt: 4, mb: 2 }}>
        <Typography variant="h4" align="center" gutterBottom>
          Shardeum NFT Minter
        </Typography>
      </Box>
      <Grid container spacing={2}>
        <Grid item xs={12} md={6}>
        <Box mt={2}>
              <Button
                fullWidth
                variant="contained"
                color="primary"
                onClick={handleConnectMetaMask}
                size="small"
                disabled={walletAddress} 
              >
                {walletAddress ? "Wallet Connected" : "Connect Wallet to Shardeum Sphinx Dapp 1.X"}
              </Button>
            </Box>
          {walletAddress && (
            <Box mt={2}>
              <Typography align="center">
                Wallet Address: {walletAddress}
              </Typography>
              <Typography align="center">
                Wallet Balance: {walletBalance} SHM
              </Typography>
            </Box>
          )}
          <TextField
            fullWidth
            label="NFT Name"
            variant="outlined"
            margin="normal"
            onChange={(e) => setName(e.target.value)}
          />
          <TextField
            fullWidth
            label="NFT Description"
            variant="outlined"
            margin="normal"
            onChange={(e) => setDescription(e.target.value)}
          />
          <input
            type="file"
            style={{ display: "none" }}
            id="image-upload"
            onChange={handleImageChange}
          />
          <p></p>
          <label      htmlFor="image-upload">
        <Button variant="contained" color="primary" component="span">
          Upload Image
        </Button>
      </label>
      {imageStatus && (
        <Typography variant="caption" display="block" gutterBottom>
          {imageStatus}
        </Typography>
      )}
      <Box mt={2}>
        <Button
          fullWidth
          variant="contained"
          color="secondary"
          onClick={mint}
        >
          Mint NFT
        </Button>
      </Box>
      {loading && <LinearProgress />}

      <Snackbar
        open={alertOpen}
        autoHideDuration={6000}
        onClose={() => setAlertOpen(false)}
        anchorOrigin={{ vertical: "top", horizontal: "right" }}
      >
        <Alert
          onClose={() => setAlertOpen(false)}
          severity="success"
          variant="filled"
          sx={{ width: "100%" }}
        >
          NFT minted successfully!
        </Alert>
      </Snackbar>
    </Grid>
    <Grid item xs={12} md={6}>
  <Box
    mt={2}
    sx={{
      border: "1px dashed #999",
      borderRadius: "12px",
      padding: "16px",
      display: "flex",
      justifyContent: "center",
      alignItems: "center",
      minHeight: "300px",
      background: imagePreviewUrl
        ? "none"
        : "linear-gradient(45deg, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%)",
    }}
  >
    {imagePreviewUrl ? (
      <img
        src={imagePreviewUrl}
        alt="Uploaded preview"
        style={{
          width: "100%",
          maxHeight: "300px",
          objectFit: "contain",
          borderRadius: "12px",
        }}
      />
    ) : (
      <Typography variant="caption" color="text.secondary">
        Preview image will be displayed here
      </Typography>
    )}
  </Box>
</Grid>
    <Box mt={2}>
        <Typography align="center" color="textSecondary">
          {status}
        </Typography>
        {ipfsLink && (
      <Typography align="left">
    IPFS Link:{" "}
    <Link href={ipfsLink} target="_blank" rel="noopener noreferrer">
      {ipfsLink}
    </Link>
      </Typography>
)}
      </Box>
  </Grid>
  <Box mt={4}>
        <Typography variant="h7" align="center">
          Transaction History:
        </Typography>
        {transactionHistory.length > 0 ? (
          transactionHistory.map((hash, index) => (
            <Box key={index} mt={1} textAlign="left">
              <Link
                href={`https://explorer-dapps.shardeum.org/transaction/${hash}`}
                target="_blank"
                rel="noopener noreferrer"
              >
                {`Transaction ${index + 1}: ${hash}`}
              </Link>
            </Box>
          ))
        ) : (
          <Typography align="center" mt={1}>
            No transactions yet.
          </Typography>
        )}
      </Box>
</Container>
);
}
export default MintNFT;
Enter fullscreen mode Exit fullscreen mode
  • Theme.js: এই ফাইলটিতে আমাদের অ্যাপ্লিকেশন স্টাইল করার জন্য প্রয়োজনীয় সমস্ত থিম রয়েছে।
import { createTheme } from "@mui/material/styles";
const theme = createTheme({
  palette: {
    mode: "dark",

    primary: {
      main: "#ffc926",
    },
    secondary: {
      main: "#088ef3",
    },
  },
  typography: {
    fontFamily: "Roboto, Arial, sans-serif",
    h4: {
      fontWeight: 700,
      marginBottom: "16px",
    },
    h5: {
      fontWeight: 600,
      marginBottom: "12px",
    },
    h6: {
      fontWeight: 500,
      marginBottom: "8px",
    },
    subtitle1: {
      fontWeight: 400,
      marginBottom: "8px",
    },
    caption: {
      fontStyle: "italic",
    },
  },
});
export default theme;
Enter fullscreen mode Exit fullscreen mode

উপরের ফাইলগুলির সাথে, এখন আমরা আমাদের বেশিরভাগ ফ্রন্ট-এন্ড কভার করেছি। App.js, App.css, index.js এবং index.css-এ প্রয়োজনীয় পরিবর্তন করে সমস্ত স্টাইলিং একত্রিত করুন এবং প্রয়োজনীয় ফাইল আমদানি করুন। আপনি এখানে গিটহাব জিস্টে এর জন্য চূড়ান্ত ফাইলগুলি খুঁজে পেতে পারেন।

আপনি এখানে সম্পূর্ণরূপে নির্মিত অ্যাপ্লিকেশন খুঁজে পেতে পারেন। যেখানেই আটকে থাকুক না কেন এটিকে আপনার নিজের কোডের সাথে মেলাতে পারেন নির্দ্বিধায়।

৪. স্থানীয়ভাবে অ্যাপ্লিকেশন চালান

এখন যেহেতু আমাদের কাছে সমস্ত প্রয়োজনীয় কোড লেখা আছে, এটি স্থানীয়ভাবে আমাদের অ্যাপ্লিকেশন চালানোর সময়। আপনার লোকালহোস্টে এটি চালানোর জন্য নিম্নলিখিত কমান্ডটি চালান।

npm start
Enter fullscreen mode Exit fullscreen mode

আপনার নতুন তৈরি শার্দিয়াম এনএফটি মিন্টার ব্যবহার শুরু করতে আপনার ওয়েব ব্রাউজারে http://localhost:3000 খুলুন!

এখানে অ্যাপ্লিকেশনটির একটি ডেমো চিত্র রয়েছে

Image description

লেখক সম্পর্কে: সন্দীপন কুন্ডু সার্ডিয়াম এর বিকাশকারী সম্পর্ক প্রকৌশলী। তিনি ২০১৭ সাল থেকে ওয়েব৩ ইকোসিস্টেমের প্রাথমিক অবদানকারী এবং এর আগে পলিগন ডেভরেল টিম বৃদ্ধিতেও অবদান রেখেছেন। তিনি ওয়েব৩ এবং বিকেন্দ্রীকরণ সম্পর্কে কথা বাড়াতে এবং ছড়িয়ে দেওয়ার জন্য হ্যাকাথন, ওয়ার্কশপ, প্রযুক্তিগত বিষয়বস্তু ইত্যাদির সাহায্যে সক্রিয়ভাবে শক্তিশালী বিকাশকারী ইভাঞ্জেলিজম প্রোগ্রাম তৈরি করছেন।

লেখকের সামাজিক লিঙ্ক:

ই-মেইল: sandipan@shardeum.org
টুইটার: https://twitter.com/SandipanKundu42

@shardeum #ShardeumIsBorderless

Top comments (0)