आप जो निर्माण कर रहे हैं, उसके लिए यहां एक डेमो और गिट रेपो देखें!
मैं इस web3.0 बिल्ड को आपके लिए जारी करने के लिए बहुत उत्साहित हूं, मुझे पता है कि आप विकेन्द्रीकृत अनुप्रयोगों को विकसित करने में आरंभ करने के लिए एक महान उदाहरण की तलाश में हैं।
यदि आप यहां नए हैं, तो मैं डार्लिंगटन गॉस्पेल हूं, जो एक डैप मेंटर है जो आपके जैसे डेवलपर्स को वेब 2.0 से वेब 3.0 में संक्रमण करने में मदद करता है।
इस ट्यूटोरियल में, आप चरण-दर-चरण सीखेंगे कि गुमनाम चैट सुविधाओं के साथ एक विकेन्द्रीकृत स्वायत्त संगठन (DAO) को कैसे लागू किया जाए।
यदि आप इस निर्माण के लिए उत्साहित हैं, तो आइए ट्यूटोरियल में कूदें…
इस बिल्ड को सफलतापूर्वक कुचलने के लिए आपको निम्नलिखित टूल इंस्टॉल करने होंगे:
NodeJs इंस्टालेशन सुनिश्चित करें कि आपके पास पहले से ही आपकी मशीन पर NodeJs स्थापित हैं। इसके बाद, यह पुष्टि करने के लिए टर्मिनल पर कोड चलाएँ कि यह स्थापित है।
यार्न, गनाचे-क्ली और ट्रफल इंस्टॉलेशन इन आवश्यक पैकेजों को विश्व स्तर पर स्थापित करने के लिए अपने टर्मिनल पर निम्नलिखित कोड चलाएँ।
npm i -g yarn npm i -g truffle npm i -g ganache-cli
वेब3 स्टार्टर प्रोजेक्ट की क्लोनिंग नीचे दिए गए कमांड का उपयोग करके, नीचे दिए गए वेब 3.0 स्टार्टर प्रोजेक्ट को क्लोन करें। यह सुनिश्चित करेगा कि हम सभी एक ही पृष्ठ पर हैं और एक ही पैकेज का उपयोग कर रहे हैं।
git clone https://github.com/Daltonic/dominionDAO
बढ़िया, आइए package.json
फ़ाइल को नीचे दी गई फ़ाइल से बदलें:
{ "name": "dominionDAO", "private": true, "version": "0.0.0", "scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-scripts eject" }, "dependencies": { "@cometchat-pro/chat": "3.0.6", "moment": "^2.29.3", "react": "^17.0.2", "react-dom": "^17.0.2", "react-hooks-global-state": "^1.0.2", "react-icons": "^4.3.1", "react-identicons": "^1.2.5", "react-moment": "^1.1.2", "react-router-dom": "6", "react-scripts": "5.0.0", "react-toastify": "^9.0.1", "recharts": "^2.1.9", "web-vitals": "^2.1.4", "web3": "^1.7.1" }, "devDependencies": { "@openzeppelin/contracts": "^4.5.0", "@tailwindcss/forms": "0.4.0", "@truffle/hdwallet-provider": "^2.0.4", "assert": "^2.0.0", "autoprefixer": "10.4.2", "babel-polyfill": "^6.26.0", "babel-preset-env": "^1.7.0", "babel-preset-es2015": "^6.24.1", "babel-preset-stage-2": "^6.24.1", "babel-preset-stage-3": "^6.24.1", "babel-register": "^6.26.0", "buffer": "^6.0.3", "chai": "^4.3.6", "chai-as-promised": "^7.1.1", "crypto-browserify": "^3.12.0", "dotenv": "^16.0.0", "https-browserify": "^1.0.0", "mnemonics": "^1.1.3", "os-browserify": "^0.3.0", "postcss": "8.4.5", "process": "^0.11.10", "react-app-rewired": "^2.1.11", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "tailwindcss": "3.0.18", "url": "^0.11.0" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } }
बढ़िया, अपनी package.json
फ़ाइल को उपरोक्त कोड से बदलें और फिर अपने टर्मिनल पर yarn install
चलाएं।
यह सब स्थापित होने के साथ, डोमिनियन डीएओ स्मार्ट अनुबंध लिखना शुरू करते हैं।
कॉमेटचैट एसडीके को कॉन्फ़िगर करने के लिए, नीचे दिए गए चरणों का पालन करें, अंत में, आपको इन कुंजियों को एक पर्यावरण चर के रूप में संग्रहीत करने की आवश्यकता है।
चरण 1: कॉमेटचैट डैशबोर्ड पर जाएं और एक खाता बनाएं।
चरण 2: कॉमेटचैट डैशबोर्ड में पंजीकरण के बाद ही लॉग इन करें।
चरण 3: डैशबोर्ड से, डोमिनियनडीएओ नामक एक नया ऐप जोड़ें।
चरण 4: सूची से आपके द्वारा अभी बनाया गया ऐप चुनें।
चरण 5: त्वरित प्रारंभ से APP_ID
, REGION
, और AUTH_KEY
को अपनी .env
फ़ाइल में कॉपी करें। छवि और कोड स्निपेट देखें।
REACT_COMET_CHAT
प्लेसहोल्डर कुंजियों को उनके उपयुक्त मानों से बदलें।
REACT_APP_COMET_CHAT_REGION=** REACT_APP_COMET_CHAT_APP_ID=************** REACT_APP_COMET_CHAT_AUTH_KEY=******************************
चरण 1: इन्फ्यूरिया पर जाएं, और एक खाता बनाएं।
चरण 2: डैशबोर्ड से एक नया प्रोजेक्ट बनाएं।
चरण 3: अपनी .env
फ़ाइल में Rinkeby
परीक्षण नेटवर्क WebSocket समापन बिंदु URL की प्रतिलिपि बनाएँ।
इसके बाद, अपना मेटामास्क गुप्त वाक्यांश और अपनी पसंदीदा खाता निजी कुंजी जोड़ें। यदि आपने उन्हें सही तरीके से किया है, तो आपके पर्यावरण चर अब इस तरह दिखना चाहिए।
ENDPOINT_URL=*************************** DEPLOYER_KEY=********************** REACT_APP_COMET_CHAT_REGION=** REACT_APP_COMET_CHAT_APP_ID=************** REACT_APP_COMET_CHAT_AUTH_KEY=******************************
यदि आप नहीं जानते कि अपनी निजी कुंजी का उपयोग कैसे करें, तो नीचे दिया गया अनुभाग देखें।
चरण 1: अपने Metamask
ब्राउज़र एक्सटेंशन पर क्लिक करें, और सुनिश्चित करें कि Rinkeby
को परीक्षण नेटवर्क के रूप में चुना गया है। इसके बाद, पसंदीदा खाते पर, लंबवत बिंदीदार रेखा पर क्लिक करें और खाता विवरण चुनें। नीचे दी गई छवि देखें।
चरण 2: दिए गए क्षेत्र में अपना पासवर्ड दर्ज करें और पुष्टि बटन पर क्लिक करें, इससे आप अपने खाते की निजी कुंजी तक पहुंच सकेंगे।
चरण 3: अपनी निजी कुंजी देखने के लिए "निजी कुंजी निर्यात करें" पर क्लिक करें। सुनिश्चित करें कि आप अपनी चाबियों को कभी भी सार्वजनिक पृष्ठ जैसे कि Github
पर उजागर न करें। इसलिए हम इसे एक पर्यावरण चर के रूप में जोड़ रहे हैं।
चरण 4: अपनी निजी कुंजी को अपनी .env फ़ाइल में कॉपी करें। नीचे दी गई छवि और कोड स्निपेट देखें:
ENDPOINT_URL=*************************** SECRET_KEY=****************** DEPLOYER_KEY=********************** REACT_APP_COMET_CHAT_REGION=** REACT_APP_COMET_CHAT_APP_ID=************** REACT_APP_COMET_CHAT_AUTH_KEY=******************************
जहां तक आपके SECRET_KEY
का संबंध है, आपको अपने Metamask
गुप्त वाक्यांश को पर्यावरण फ़ाइल में दिए गए स्थान में चिपकाना होगा।
यहां स्मार्ट अनुबंध का पूरा कोड है, मैं एक के बाद एक सभी कार्यों और चरों की व्याख्या करूंगा।
// SPDX-License-Identifier: MIT pragma solidity ^0.8.7; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; contract DominionDAO is ReentrancyGuard, AccessControl { bytes32 private immutable CONTRIBUTOR_ROLE = keccak256("CONTRIBUTOR"); bytes32 private immutable STAKEHOLDER_ROLE = keccak256("STAKEHOLDER"); uint32 immutable MIN_VOTE_DURATION = 1 weeks; uint256 totalProposals; uint256 public daoBalance; mapping(uint256 => ProposalStruct) private raisedProposals; mapping(address => uint256[]) private stakeholderVotes; mapping(uint256 => VotedStruct[]) private votedOn; mapping(address => uint256) private contributors; mapping(address => uint256) private stakeholders; struct ProposalStruct { uint256 id; uint256 amount; uint256 duration; uint256 upvotes; uint256 downvotes; string title; string description; bool passed; bool paid; address payable beneficiary; address proposer; address executor; } struct VotedStruct { address voter; uint256 timestamp; bool choosen; } event Action( address indexed initiator, bytes32 role, string message, address indexed beneficiary, uint256 amount ); modifier stakeholderOnly(string memory message) { require(hasRole(STAKEHOLDER_ROLE, msg.sender), message); _; } modifier contributorOnly(string memory message) { require(hasRole(CONTRIBUTOR_ROLE, msg.sender), message); _; } function createProposal( string calldata title, string calldata description, address beneficiary, uint256 amount )external stakeholderOnly("Proposal Creation Allowed for Stakeholders only") { uint256 proposalId = totalProposals++; ProposalStruct storage proposal = raisedProposals[proposalId]; proposal.id = proposalId; proposal.proposer = payable(msg.sender); proposal.title = title; proposal.description = description; proposal.beneficiary = payable(beneficiary); proposal.amount = amount; proposal.duration = block.timestamp + MIN_VOTE_DURATION; emit Action( msg.sender, CONTRIBUTOR_ROLE, "PROPOSAL RAISED", beneficiary, amount ); } function performVote(uint256 proposalId, bool choosen) external stakeholderOnly("Unauthorized: Stakeholders only") { ProposalStruct storage proposal = raisedProposals[proposalId]; handleVoting(proposal); if (choosen) proposal.upvotes++; else proposal.downvotes++; stakeholderVotes[msg.sender].push(proposal.id); votedOn[proposal.id].push( VotedStruct( msg.sender, block.timestamp, choosen ) ); emit Action( msg.sender, STAKEHOLDER_ROLE, "PROPOSAL VOTE", proposal.beneficiary, proposal.amount ); } function handleVoting(ProposalStruct storage proposal) private { if ( proposal.passed || proposal.duration <= block.timestamp ) { proposal.passed = true; revert("Proposal duration expired"); } uint256[] memory tempVotes = stakeholderVotes[msg.sender]; for (uint256 votes = 0; votes < tempVotes.length; votes++) { if (proposal.id == tempVotes[votes]) revert("Double voting not allowed"); } } function payBeneficiary(uint256 proposalId) external stakeholderOnly("Unauthorized: Stakeholders only") returns (bool) { ProposalStruct storage proposal = raisedProposals[proposalId]; require(daoBalance >= proposal.amount, "Insufficient fund"); require(block.timestamp > proposal.duration, "Proposal still ongoing"); if (proposal.paid) revert("Payment sent before"); if (proposal.upvotes <= proposal.downvotes) revert("Insufficient votes"); payTo(proposal.beneficiary, proposal.amount); proposal.paid = true; proposal.executor = msg.sender; daoBalance -= proposal.amount; emit Action( msg.sender, STAKEHOLDER_ROLE, "PAYMENT TRANSFERED", proposal.beneficiary, proposal.amount ); return true; } function contribute() payable external { if (!hasRole(STAKEHOLDER_ROLE, msg.sender)) { uint256 totalContribution = contributors[msg.sender] + msg.value; if (totalContribution >= 5 ether) { stakeholders[msg.sender] = totalContribution; contributors[msg.sender] += msg.value; _setupRole(STAKEHOLDER_ROLE, msg.sender); _setupRole(CONTRIBUTOR_ROLE, msg.sender); } else { contributors[msg.sender] += msg.value; _setupRole(CONTRIBUTOR_ROLE, msg.sender); } } else { contributors[msg.sender] += msg.value; stakeholders[msg.sender] += msg.value; } daoBalance += msg.value; emit Action( msg.sender, STAKEHOLDER_ROLE, "CONTRIBUTION RECEIVED", address(this), msg.value ); } function getProposals() external view returns (ProposalStruct[] memory props) { props = new ProposalStruct[](totalProposals); for (uint256 i = 0; i < totalProposals; i++) { props[i] = raisedProposals[i]; } } function getProposal(uint256 proposalId) external view returns (ProposalStruct memory) { return raisedProposals[proposalId]; } function getVotesOf(uint256 proposalId) external view returns (VotedStruct[] memory) { return votedOn[proposalId]; } function getStakeholderVotes() external view stakeholderOnly("Unauthorized: not a stakeholder") returns (uint256[] memory) { return stakeholderVotes[msg.sender]; } function getStakeholderBalance() external view stakeholderOnly("Unauthorized: not a stakeholder") returns (uint256) { return stakeholders[msg.sender]; } function isStakeholder() external view returns (bool) { return stakeholders[msg.sender] > 0; } function getContributorBalance() external view contributorOnly("Denied: User is not a contributor") returns (uint256) { return contributors[msg.sender]; } function isContributor() external view returns (bool) { return contributors[msg.sender] > 0; } function getBalance() external view returns (uint256) { return contributors[msg.sender]; } function payTo( address to, uint256 amount ) internal returns (bool) { (bool success,) = payable(to).call{value: amount}(""); require(success, "Payment failed"); return true; } }
प्रोजेक्ट में आपने अभी क्लोन किया है, src >> कॉन्ट्रैक्ट डायरेक्टरी पर जाएं और DominionDAO.sol
नाम की एक फाइल बनाएं, फिर उसके अंदर उपरोक्त कोड पेस्ट करें।
व्याख्या:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.7;
सॉलिडिटी को आपके कोड को संकलित करने के लिए एक लाइसेंस पहचानकर्ता की आवश्यकता होती है, अन्यथा यह एक चेतावनी उत्पन्न करेगा जो आपको एक निर्दिष्ट करने के लिए कहेगा। साथ ही, सॉलिडिटी के लिए आवश्यक है कि आप अपने स्मार्ट अनुबंध के लिए कंपाइलर का संस्करण निर्दिष्ट करें। pragma
शब्द यही दर्शाता है।
import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
उपरोक्त कोड ब्लॉक में, हम भूमिकाओं को निर्दिष्ट करने के लिए दो openzeppelin's
स्मार्ट अनुबंधों का उपयोग कर रहे हैं और हमारे स्मार्ट अनुबंध को पुनर्प्रवेश हमलों से बचा रहे हैं।
bytes32 private immutable CONTRIBUTOR_ROLE = keccak256("CONTRIBUTOR"); bytes32 private immutable STAKEHOLDER_ROLE = keccak256("STAKEHOLDER"); uint32 immutable MIN_VOTE_DURATION = 1 weeks; uint256 totalProposals; uint256 public daoBalance;
हमने हितधारक और योगदानकर्ता भूमिकाओं के लिए कुछ राज्य चर स्थापित किए और न्यूनतम वोट अवधि एक सप्ताह के लिए निर्दिष्ट की। और हमने अपने उपलब्ध शेष का रिकॉर्ड रखने के लिए कुल प्रस्ताव काउंटर और एक चर भी शुरू किया।
mapping(uint256 => ProposalStruct) private raisedProposals; mapping(address => uint256[]) private stakeholderVotes; mapping(uint256 => VotedStruct[]) private votedOn; mapping(address => uint256) private contributors; mapping(address => uint256) private stakeholders;
raisedProposals
गए प्रस्ताव हमारे स्मार्ट अनुबंध को सबमिट किए गए सभी प्रस्तावों का ट्रैक रखते हैं। stakeholderVotes
वोट जैसा कि इसके नाम से तात्पर्य है कि हितधारकों द्वारा किए गए वोटों का ट्रैक रखना। votedOn
एक प्रस्ताव से जुड़े सभी मतों का ट्रैक रखता है। जहां योगदानकर्ता हमारे मंच को दान करने वाले किसी भी व्यक्ति का ट्रैक रखते हैं, वहीं दूसरी ओर हितधारक उन लोगों पर नज़र रखते हैं जिन्होंने 1 ether
तक योगदान दिया है।
struct ProposalStruct { uint256 id; uint256 amount; uint256 duration; uint256 upvotes; uint256 downvotes; string title; string description; bool passed; bool paid; address payable beneficiary; address proposer; address executor; } struct VotedStruct { address voter; uint256 timestamp; bool choosen; }
proposalStruct
संरचना प्रत्येक प्रस्ताव की सामग्री का वर्णन करती है जबकि votedStruct
प्रत्येक वोट की सामग्री का वर्णन करती है।
event Action( address indexed initiator, bytes32 role, string message, address indexed beneficiary, uint256 amount );
यह एक गतिशील घटना है जिसे एक्शन कहा जाता है। यह हमें प्रति लेनदेन लॉग आउट की गई जानकारी को समृद्ध बनाने में मदद करेगा।
modifier stakeholderOnly(string memory message) { require(hasRole(STAKEHOLDER_ROLE, msg.sender), message); _; } modifier contributorOnly(string memory message) { require(hasRole(CONTRIBUTOR_ROLE, msg.sender), message); _; }
उपरोक्त संशोधक हमें भूमिका के आधार पर उपयोगकर्ताओं की पहचान करने में मदद करते हैं और उन्हें कुछ अनधिकृत संसाधनों तक पहुँचने से भी रोकते हैं।
function createProposal( string calldata title, string calldata description, address beneficiary, uint256 amount )external stakeholderOnly("Proposal Creation Allowed for Stakeholders only") { uint256 proposalId = totalProposals++; ProposalStruct storage proposal = raisedProposals[proposalId]; proposal.id = proposalId; proposal.proposer = payable(msg.sender); proposal.title = title; proposal.description = description; proposal.beneficiary = payable(beneficiary); proposal.amount = amount; proposal.duration = block.timestamp + MIN_VOTE_DURATION; emit Action( msg.sender, CONTRIBUTOR_ROLE, "PROPOSAL RAISED", beneficiary, amount ); }
उपरोक्त फ़ंक्शन एक प्रस्ताव का शीर्षक, विवरण, राशि और लाभार्थी के बटुए का पता लेता है और एक प्रस्ताव बनाता है। फ़ंक्शन केवल हितधारकों को प्रस्ताव बनाने की अनुमति देता है। हितधारक वे उपयोगकर्ता हैं जिन्होंने कम से कम 1 ether
का योगदान दिया है।
function performVote(uint256 proposalId, bool choosen) external stakeholderOnly("Unauthorized: Stakeholders only") { ProposalStruct storage proposal = raisedProposals[proposalId]; handleVoting(proposal); if (choosen) proposal.upvotes++; else proposal.downvotes++; stakeholderVotes[msg.sender].push(proposal.id); votedOn[proposal.id].push( VotedStruct( msg.sender, block.timestamp, choosen ) ); emit Action( msg.sender, STAKEHOLDER_ROLE, "PROPOSAL VOTE", proposal.beneficiary, proposal.amount ); }
यह फ़ंक्शन दो तर्कों को स्वीकार करता है, एक प्रस्ताव आईडी, और एक बूलियन मान द्वारा दर्शाया गया पसंदीदा विकल्प। सही का मतलब है कि आपने वोट स्वीकार कर लिया है और झूठा अस्वीकृति का प्रतिनिधित्व करता है।
function handleVoting(ProposalStruct storage proposal) private { if ( proposal.passed || proposal.duration <= block.timestamp ) { proposal.passed = true; revert("Proposal duration expired"); } uint256[] memory tempVotes = stakeholderVotes[msg.sender]; for (uint256 votes = 0; votes < tempVotes.length; votes++) { if (proposal.id == tempVotes[votes]) revert("Double voting not allowed"); } }
यह फ़ंक्शन वास्तविक मतदान करता है जिसमें यह जांचना शामिल है कि क्या उपयोगकर्ता एक हितधारक है और वोट देने के योग्य है।
function payBeneficiary(uint256 proposalId) external stakeholderOnly("Unauthorized: Stakeholders only") returns (bool) { ProposalStruct storage proposal = raisedProposals[proposalId]; require(daoBalance >= proposal.amount, "Insufficient fund"); require(block.timestamp > proposal.duration, "Proposal still ongoing"); if (proposal.paid) revert("Payment sent before"); if (proposal.upvotes <= proposal.downvotes) revert("Insufficient votes"); payTo(proposal.beneficiary, proposal.amount); proposal.paid = true; proposal.executor = msg.sender; daoBalance -= proposal.amount; emit Action( msg.sender, STAKEHOLDER_ROLE, "PAYMENT TRANSFERED", proposal.beneficiary, proposal.amount ); return true; }
यह फ़ंक्शन कुछ मानदंडों के आधार पर प्रस्ताव से जुड़े लाभार्थी को भुगतान करने के लिए ज़िम्मेदार है।
function contribute() payable external { if (!hasRole(STAKEHOLDER_ROLE, msg.sender)) { uint256 totalContribution = contributors[msg.sender] + msg.value; if (totalContribution >= 5 ether) { stakeholders[msg.sender] = totalContribution; contributors[msg.sender] += msg.value; _setupRole(STAKEHOLDER_ROLE, msg.sender); _setupRole(CONTRIBUTOR_ROLE, msg.sender); } else { contributors[msg.sender] += msg.value; _setupRole(CONTRIBUTOR_ROLE, msg.sender); } } else { contributors[msg.sender] += msg.value; stakeholders[msg.sender] += msg.value; } daoBalance += msg.value; emit Action( msg.sender, STAKEHOLDER_ROLE, "CONTRIBUTION RECEIVED", address(this), msg.value ); }
यह समारोह दाताओं और हितधारक बनने में रुचि रखने वालों से योगदान एकत्र करने के लिए जिम्मेदार है।
function getProposals() external view returns (ProposalStruct[] memory props) { props = new ProposalStruct[](totalProposals); for (uint256 i = 0; i < totalProposals; i++) { props[i] = raisedProposals[i]; } }
यह फ़ंक्शन इस स्मार्ट अनुबंध पर दर्ज किए गए प्रस्तावों की एक सरणी को पुनः प्राप्त करता है।
function getProposal(uint256 proposalId) external view returns (ProposalStruct memory) { return raisedProposals[proposalId]; }
यह फ़ंक्शन आईडी द्वारा एक विशेष प्रस्ताव पुनर्प्राप्त करता है।
function getVotesOf(uint256 proposalId) external view returns (VotedStruct[] memory) { return votedOn[proposalId]; }
यह किसी विशेष प्रस्ताव से जुड़े वोटों की सूची देता है।
function getStakeholderVotes() external view stakeholderOnly("Unauthorized: not a stakeholder") returns (uint256[] memory) { return stakeholderVotes[msg.sender]; }
यह स्मार्ट अनुबंध पर हितधारकों की सूची लौटाता है और केवल एक हितधारक ही इस फ़ंक्शन को कॉल कर सकता है।
function getStakeholderBalance() external view stakeholderOnly("Unauthorized: not a stakeholder") returns (uint256) { return stakeholders[msg.sender]; }
यह हितधारकों द्वारा योगदान की गई राशि को लौटाता है।
function isStakeholder() external view returns (bool) { return stakeholders[msg.sender] > 0; }
यदि उपयोगकर्ता हितधारक है तो सही या गलत लौटाता है।
function getContributorBalance() external view contributorOnly("Denied: User is not a contributor") returns (uint256) { return contributors[msg.sender]; }
यह एक योगदानकर्ता का संतुलन लौटाता है और केवल योगदानकर्ता के लिए ही सुलभ है।
function isContributor() external view returns (bool) { return contributors[msg.sender] > 0; }
यह जांचता है कि कोई उपयोगकर्ता योगदानकर्ता है या नहीं और इसे सही या गलत के साथ दर्शाया गया है।
function getBalance() external view returns (uint256) { return contributors[msg.sender]; }
कॉल करने वाले उपयोगकर्ता की भूमिका की परवाह किए बिना शेष राशि लौटाता है।
function payTo( address to, uint256 amount ) internal returns (bool) { (bool success,) = payable(to).call{value: amount}(""); require(success, "Payment failed"); return true; }
यह फ़ंक्शन एक निर्दिष्ट राशि और खाते दोनों के साथ भुगतान करता है।
स्मार्ट अनुबंध के साथ एक और बात परिनियोजन स्क्रिप्ट को कॉन्फ़िगर करना है।
प्रोजेक्ट हेड पर माइग्रेशन फ़ोल्डर में, >> 2_deploy_contracts.js, और इसे नीचे दिए गए कोड स्निपेट के साथ अपडेट करें।
const DominionDAO = artifacts.require('DominionDAO') module.exports = async function (deployer) { await deployer.deploy(DominionDAO) }
बढ़िया, हमने अभी-अभी अपने एप्लिकेशन के लिए स्मार्ट अनुबंध समाप्त किया है, यह डैप इंटरफ़ेस का निर्माण शुरू करने का समय है।
सामने के छोर में कई घटक और भाग होते हैं। हम सभी घटकों, विचारों और बाकी बाह्य उपकरणों का निर्माण करेंगे।
हैडर घटक
यह घटक वर्तमान उपयोगकर्ता के बारे में जानकारी प्राप्त करता है और लाइट और डार्क मोड के लिए थीम टॉगलिंग बटन रखता है। और अगर आपको आश्चर्य है कि मैंने यह कैसे किया, तो यह टेलविंड सीएसएस के माध्यम से था, नीचे दिए गए कोड को देखें।
import { useState, useEffect } from 'react' import { FaUserSecret } from 'react-icons/fa' import { MdLightMode } from 'react-icons/md' import { FaMoon } from 'react-icons/fa' import { Link } from 'react-router-dom' import { connectWallet } from '../Dominion' import { useGlobalState, truncate } from '../store' const Header = () => { const [theme, setTheme] = useState(localStorage.theme) const themeColor = theme === 'dark' ? 'light' : 'dark' const darken = theme === 'dark' ? true : false const [connectedAccount] = useGlobalState('connectedAccount') useEffect(() => { const root = window.document.documentElement root.classList.remove(themeColor) root.classList.add(theme) localStorage.setItem('theme', theme) }, [themeColor, theme]) const toggleLight = () => { const root = window.document.documentElement root.classList.remove(themeColor) root.classList.add(theme) localStorage.setItem('theme', theme) setTheme(themeColor) } return ( <header className="sticky top-0 z-50 dark:text-blue-500"> <nav className="navbar navbar-expand-lg shadow-md py-2 relative flex items-center w-full justify-between bg-white dark:bg-[#212936]"> <div className="px-6 w-full flex flex-wrap items-center justify-between"> <div className="navbar-collapse collapse grow flex flex-row justify-between items-center p-2"> <Link to={'/'} className="flex flex-row justify-start items-center space-x-3" > <FaUserSecret className="cursor-pointer" size={25} /> <span className="invisible md:visible dark:text-gray-300"> Dominion </span> </Link> <div className="flex flex-row justify-center items-center space-x-5"> {darken ? ( <MdLightMode className="cursor-pointer" size={25} onClick={toggleLight} /> ) : ( <FaMoon className="cursor-pointer" size={25} onClick={toggleLight} /> )} {connectedAccount ? ( <button className="px-4 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded-full shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out dark:text-blue-500 dark:border dark:border-blue-500 dark:bg-transparent" > {truncate(connectedAccount, 4, 4, 11)} </button> ) : ( <button className="px-4 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded-full shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out dark:text-blue-500 dark:border dark:border-blue-500 dark:bg-transparent" onClick={connectWallet} > Connect Wallet </button> )} </div> </div> </div> </nav> </header> ) } export default Header
बैनर घटक
इस घटक में डीएओ की वर्तमान स्थिति के बारे में जानकारी होती है, जैसे कि कुल शेष राशि और खुले प्रस्तावों की संख्या।
इस घटक में एक नया प्रस्ताव तैयार करने के लिए योगदान समारोह का उपयोग करने की क्षमता भी शामिल है। नीचे दिए गए कोड को देखें।
import { useState } from 'react' import { setGlobalState, useGlobalState } from '../store' import { performContribute } from '../Dominion' import { toast } from 'react-toastify' const Banner = () => { const [isStakeholder] = useGlobalState('isStakeholder') const [proposals] = useGlobalState('proposals') const [connectedAccount] = useGlobalState('connectedAccount') const [currentUser] = useGlobalState('currentUser') const [balance] = useGlobalState('balance') const [mybalance] = useGlobalState('mybalance') const [amount, setAmount] = useState('') const onPropose = () => { if (!isStakeholder) return setGlobalState('createModal', 'scale-100') } const onContribute = () => { if (!!!amount || amount == '') return toast.info('Contribution in progress...') performContribute(amount).then((bal) => { if (!!!bal.message) { setGlobalState('balance', Number(balance) + Number(bal)) setGlobalState('mybalance', Number(mybalance) + Number(bal)) setAmount('') toast.success('Contribution received') } }) } const opened = () => proposals.filter( (proposal) => new Date().getTime() < Number(proposal.duration + '000') ).length return ( <div className="p-8"> <h2 className="font-semibold text-3xl mb-5"> {opened()} Proposal{opened() == 1 ? '' : 's'} Currenly Opened </h2> <p> Current DAO Balance: <strong>{balance} Eth</strong> <br /> Your contributions:{' '} <span> <strong>{mybalance} Eth</strong> {isStakeholder ? ', and you are now a stakeholder 😊' : null} </span> </p> <hr className="my-6 border-gray-300 dark:border-gray-500" /> <p> {isStakeholder ? 'You can now raise proposals on this platform 😆' : 'Hey, when you contribute upto 1 ether you become a stakeholder 😎'} </p> <div className="flex flex-row justify-start items-center md:w-1/3 w-full mt-4"> <input type="number" className="form-control block w-full px-3 py-1.5 text-base font-normaltext-gray-700 bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 shadow-md focus:text-gray-500 focus:outline-none dark:border-gray-500 dark:bg-transparent" placeholder="eg 2.5 Eth" onChange={(e) => setAmount(e.target.value)} value={amount} required /> </div> <div className="flex flex-row justify-start items-center space-x-3 mt-4" role="group" > <button type="button" className={`inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase shadow-md rounded-full hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out dark:text-blue-500 dark:border dark:border-blue-500 dark:bg-transparent`} data-mdb-ripple="true" data-mdb-ripple-color="light" onClick={onContribute} > Contribute </button> {isStakeholder ? ( <button type="button" className={`inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase shadow-md rounded-full hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out dark:text-blue-500 dark:border dark:border-blue-500 dark:bg-transparent`} data-mdb-ripple="true" data-mdb-ripple-color="light" onClick={onPropose} > Propose </button> ) : null} {currentUser && currentUser.uid == connectedAccount.toLowerCase() ? null : ( <button type="button" className={`inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase shadow-md rounded-full hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out dark:border dark:border-blue-500`} data-mdb-ripple="true" data-mdb-ripple-color="light" onClick={() => setGlobalState('loginModal', 'scale-100')} > Login Chat </button> )} </div> </div> ) } export default Banner
प्रस्ताव घटक
इस घटक में हमारे स्मार्ट अनुबंध में प्रस्तावों की एक सूची है। साथ ही, आपको बंद और खुले प्रस्तावों के बीच फ़िल्टर करने में सक्षम बनाता है। एक प्रस्ताव की समाप्ति पर, एक पेआउट बटन उपलब्ध हो जाता है जो एक हितधारक को प्रस्ताव से जुड़ी राशि का भुगतान करने का विकल्प देता है। नीचे दिए गए कोड को देखें।
import Identicon from 'react-identicons' import { useState } from 'react' import { Link } from 'react-router-dom' import { truncate, useGlobalState, daysRemaining } from '../store' import { payoutBeneficiary } from '../Dominion' import { toast } from 'react-toastify' const Proposals = () => { const [data] = useGlobalState('proposals') const [proposals, setProposals] = useState(data) const deactive = `bg-transparent text-blue-600 font-medium text-xs leading-tight uppercase hover:bg-blue-700 focus:bg-blue-700 focus:outline-none focus:ring-0 active:bg-blue-600 transition duration-150 ease-in-out overflow-hidden border border-blue-600 hover:text-white focus:text-white` const active = `bg-blue-600 text-white font-medium text-xs leading-tight uppercase hover:bg-blue-700 focus:bg-blue-700 focus:outline-none focus:ring-0 active:bg-blue-800 transition duration-150 ease-in-out overflow-hidden border border-blue-600` const getAll = () => setProposals(data) const getOpened = () => setProposals( data.filter( (proposal) => new Date().getTime() < Number(proposal.duration + '000') ) ) const getClosed = () => setProposals( data.filter( (proposal) => new Date().getTime() > Number(proposal.duration + '000') ) ) const handlePayout = (id) => { payoutBeneficiary(id).then((res) => { if (!!!res.code) { toast.success('Beneficiary successfully Paid Out!') window.location.reload() } }) } return ( <div className="flex flex-col p-8"> <div className="flex flex-row justify-center items-center" role="group"> <button aria-current="page" className={`rounded-l-full px-6 py-2.5 ${active}`} onClick={getAll} > All </button> <button aria-current="page" className={`px-6 py-2.5 ${deactive}`} onClick={getOpened} > Open </button> <button aria-current="page" className={`rounded-r-full px-6 py-2.5 ${deactive}`} onClick={getClosed} > Closed </button> </div> <div className="overflow-x-auto sm:-mx-6 lg:-mx-8"> <div className="py-2 inline-block min-w-full sm:px-6 lg:px-8"> <div className="h-[calc(100vh_-_20rem)] overflow-y-auto shadow-md rounded-md"> <table className="min-w-full"> <thead className="border-b dark:border-gray-500"> <tr> <th scope="col" className="text-sm font-medium px-6 py-4 text-left" > Created By </th> <th scope="col" className="text-sm font-medium px-6 py-4 text-left" > Title </th> <th scope="col" className="text-sm font-medium px-6 py-4 text-left" > Expires </th> <th scope="col" className="text-sm font-medium px-6 py-4 text-left" > Action </th> </tr> </thead> <tbody> {proposals.map((proposal) => ( <tr key={proposal.id} className="border-b dark:border-gray-500" > <td className="text-sm font-light px-6 py-4 whitespace-nowrap"> <div className="flex flex-row justify-start items-center space-x-3"> <Identicon string={proposal.proposer.toLowerCase()} size={25} className="h-10 w-10 object-contain rounded-full mr-3" /> <span>{truncate(proposal.proposer, 4, 4, 11)}</span> </div> </td> <td className="text-sm font-light px-6 py-4 whitespace-nowrap"> {proposal.title.substring(0, 80) + '...'} </td> <td className="text-sm font-light px-6 py-4 whitespace-nowrap"> {new Date().getTime() > Number(proposal.duration + '000') ? 'Expired' : daysRemaining(proposal.duration)} </td> <td className="flex justify-start items-center space-x-3 text-sm font-light px-6 py-4 whitespace-nowrap" > <Link to={'/proposal/' + proposal.id} className="dark:border rounded-full px-6 py-2.5 dark:border-blue-600 dark:text-blue-600 dark:bg-transparent font-medium text-xs leading-tight uppercase hover:border-blue-700 focus:border-blue-700 focus:outline-none focus:ring-0 active:border-blue-800 transition duration-150 ease-in-out text-white bg-blue-600" > View </Link> {new Date().getTime() > Number(proposal.duration + '000') ? ( proposal.upvotes > proposal.downvotes ? ( !proposal.paid ? ( <button className="dark:border rounded-full px-6 py-2.5 dark:border-red-600 dark:text-red-600 dark:bg-transparent font-medium text-xs leading-tight uppercase hover:border-red-700 focus:border-red-700 focus:outline-none focus:ring-0 active:border-red-800 transition duration-150 ease-in-out text-white bg-red-600" onClick={() => handlePayout(proposal.id)} > Payout </button> ) : ( <button className="dark:border rounded-full px-6 py-2.5 dark:border-green-600 dark:text-green-600 dark:bg-transparent font-medium text-xs leading-tight uppercase hover:border-green-700 focus:border-green-700 focus:outline-none focus:ring-0 active:border-green-800 transition duration-150 ease-in-out text-white bg-green-600" > Paid </button> ) ) : ( <button className="dark:border rounded-full px-6 py-2.5 dark:border-red-600 dark:text-red-600 dark:bg-transparent font-medium text-xs leading-tight uppercase hover:border-red-700 focus:border-red-700 focus:outline-none focus:ring-0 active:border-red-800 transition duration-150 ease-in-out text-white bg-red-600" > Rejected </button> ) ) : null} </td> </tr> ))} </tbody> </table> </div> </div> </div> </div> ) } export default Proposals
प्रस्ताव विवरण घटक
यह घटक लागत सहित वर्तमान प्रस्ताव के बारे में जानकारी प्रदर्शित करता है। यह घटक हितधारकों को किसी प्रस्ताव को स्वीकार या अस्वीकार करने की अनुमति देता है।
प्रस्तावक एक समूह बना सकता है, और अन्य प्लेटफ़ॉर्म उपयोगकर्ता वेब3.0-शैली की अनाम चैट में संलग्न हो सकते हैं।
इस घटक में एक बार चार्ट भी शामिल है जो आपको स्वीकार करने वालों और अस्वीकार करने वालों के अनुपात को देखने की अनुमति देता है। नीचे दिए गए कोड को देखें।
import moment from 'moment' import { useEffect, useState } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { toast } from 'react-toastify' import { getGroup, createNewGroup, joinGroup } from '../CometChat' import { BarChart, Bar, CartesianGrid, XAxis, YAxis, Legend, Tooltip, } from 'recharts' import { getProposal, voteOnProposal } from '../Dominion' import { useGlobalState } from '../store' const ProposalDetails = () => { const { id } = useParams() const navigator = useNavigate() const [proposal, setProposal] = useState(null) const [group, setGroup] = useState(null) const [data, setData] = useState([]) const [isStakeholder] = useGlobalState('isStakeholder') const [connectedAccount] = useGlobalState('connectedAccount') const [currentUser] = useGlobalState('currentUser') useEffect(() => { retrieveProposal() getGroup(`pid_${id}`).then((group) => { if (!!!group.code) setGroup(group) console.log(group) }) }, [id]) const retrieveProposal = () => { getProposal(id).then((res) => { setProposal(res) setData([ { name: 'Voters', Acceptees: res?.upvotes, Rejectees: res?.downvotes, }, ]) }) } const onVote = (choice) => { if (new Date().getTime() > Number(proposal.duration + '000')) { toast.warning('Proposal expired!') return } voteOnProposal(id, choice).then((res) => { if (!!!res.code) { toast.success('Voted successfully!') window.location.reload() } }) } const daysRemaining = (days) => { const todaysdate = moment() days = Number((days + '000').slice(0)) days = moment(days).format('YYYY-MM-DD') days = moment(days) days = days.diff(todaysdate, 'days') return days == 1 ? '1 day' : days + ' days' } const onEnterChat = () => { if (group.hasJoined) { navigator(`/chat/${`pid_${id}`}`) } else { joinGroup(`pid_${id}`).then((res) => { if (!!res) { navigator(`/chat/${`pid_${id}`}`) console.log('Success joining: ', res) } else { console.log('Error Joining Group: ', res) } }) } } const onCreateGroup = () => { createNewGroup(`pid_${id}`, proposal.title).then((group) => { if (!!!group.code) { toast.success('Group created successfully!') setGroup(group) } else { console.log('Error Creating Group: ', group) } }) } return ( <div className="p-8"> <h2 className="font-semibold text-3xl mb-5">{proposal?.title}</h2> <p> This proposal is to payout <strong>{proposal?.amount} Eth</strong> and currently have{' '} <strong>{proposal?.upvotes + proposal?.downvotes} votes</strong> and will expire in <strong>{daysRemaining(proposal?.duration)}</strong> </p> <hr className="my-6 border-gray-300" /> <p>{proposal?.description}</p> <div className="flex flex-row justify-start items-center w-full mt-4 overflow-auto"> <BarChart width={730} height={250} data={data}> <CartesianGrid strokeDasharray="3 3" /> <XAxis dataKey="name" /> <YAxis /> <Tooltip /> <Legend /> <Bar dataKey="Acceptees" fill="#2563eb" /> <Bar dataKey="Rejectees" fill="#dc2626" /> </BarChart> </div> <div className="flex flex-row justify-start items-center space-x-3 mt-4" role="group" > {isStakeholder ? ( <> <button type="button" className="inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded-full shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out dark:text-gray-300 dark:border dark:border-gray-500 dark:bg-transparent" data-mdb-ripple="true" data-mdb-ripple-color="light" onClick={() => onVote(true)} > Accept </button> <button type="button" className="inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded-full shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out dark:border dark:border-gray-500 dark:bg-transparent" data-mdb-ripple="true" data-mdb-ripple-color="light" onClick={() => onVote(false)} > Reject </button> {currentUser && currentUser.uid.toLowerCase() == proposal?.proposer.toLowerCase() && !group ? ( <button type="button" className="inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded-full shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out dark:border dark:border-blue-500" data-mdb-ripple="true" data-mdb-ripple-color="light" onClick={onCreateGroup} > Create Group </button> ) : null} </> ) : null} {currentUser && currentUser.uid.toLowerCase() == connectedAccount.toLowerCase() && !!!group?.code && group != null ? ( <button type="button" className="inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded-full shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out dark:border dark:border-blue-500" data-mdb-ripple="true" data-mdb-ripple-color="light" onClick={onEnterChat} > Chat </button> ) : null} {proposal?.proposer.toLowerCase() != connectedAccount.toLowerCase() && !!!group ? ( <button type="button" className="inline-block px-6 py-2.5 bg-blue-600 dark:bg-transparent text-white font-medium text-xs leading-tight uppercase rounded-full shadow-md hover:border-blue-700 hover:shadow-lg focus:border-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:border-blue-800 active:shadow-lg transition duration-150 ease-in-out dark:text-blue-500 dark:border dark:border-blue-500 disabled:bg-blue-300" data-mdb-ripple="true" data-mdb-ripple-color="light" disabled > Group N/A </button> ) : null} </div> </div> ) } export default ProposalDetails
मतदाता घटक
यह घटक केवल उन हितधारकों को सूचीबद्ध करता है जिन्होंने किसी प्रस्ताव पर मतदान किया है। घटक एक उपयोगकर्ता को अस्वीकार और स्वीकार करने वालों के बीच फ़िल्टर करने का मौका भी देता है। नीचे दिए गए कोड को देखें।
import Identicon from 'react-identicons' import moment from 'moment' import { useState, useEffect } from 'react' import { useParams } from 'react-router-dom' import { truncate } from '../store' import { listVoters } from '../Dominion' const Voters = () => { const [voters, setVoters] = useState([]) const [data, setData] = useState([]) const { id } = useParams() const timeAgo = (timestamp) => moment(Number(timestamp + '000')).fromNow() const deactive = `bg-transparent text-blue-600 font-medium text-xs leading-tight uppercase hover:bg-blue-700 focus:bg-blue-700 focus:outline-none focus:ring-0 active:bg-blue-600 transition duration-150 ease-in-out overflow-hidden border border-blue-600 hover:text-white focus:text-white` const active = `bg-blue-600 text-white font-medium text-xs leading-tight uppercase hover:bg-blue-700 focus:bg-blue-700 focus:outline-none focus:ring-0 active:bg-blue-800 transition duration-150 ease-in-out overflow-hidden border border-blue-600` useEffect(() => { listVoters(id).then((res) => { setVoters(res) setData(res) }) }, [id]) const getAll = () => setVoters(data) const getAccepted = () => setVoters(data.filter((vote) => vote.choosen)) const getRejected = () => setVoters(data.filter((vote) => !vote.choosen)) return ( <div className="flex flex-col p-8"> <div className="flex flex-row justify-center items-center" role="group"> <button aria-current="page" className={`rounded-l-full px-6 py-2.5 ${active}`} onClick={getAll} > All </button> <button aria-current="page" className={`px-6 py-2.5 ${deactive}`} onClick={getAccepted} > Acceptees </button> <button aria-current="page" className={`rounded-r-full px-6 py-2.5 ${deactive}`} onClick={getRejected} > Rejectees </button> </div> <div className="overflow-x-auto sm:-mx-6 lg:-mx-8"> <div className="py-2 inline-block min-w-full sm:px-6 lg:px-8"> <div className="h-[calc(100vh_-_20rem)] overflow-y-auto shadow-md rounded-md"> <table className="min-w-full"> <thead className="border-b dark:border-gray-500"> <tr> <th scope="col" className="text-sm font-medium px-6 py-4 text-left" > Voter </th> <th scope="col" className="text-sm font-medium px-6 py-4 text-left" > Voted </th> <th scope="col" className="text-sm font-medium px-6 py-4 text-left" > Vote </th> </tr> </thead> <tbody> {voters.map((voter, i) => ( <tr key={i} className="border-b dark:border-gray-500 transition duration-300 ease-in-out" > <td className="text-sm font-light px-6 py-4 whitespace-nowrap"> <div className="flex flex-row justify-start items-center space-x-3"> <Identicon string={voter.voter.toLowerCase()} size={25} className="h-10 w-10 object-contain rounded-full mr-3" /> <span>{truncate(voter.voter, 4, 4, 11)}</span> </div> </td> <td className="text-sm font-light px-6 py-4 whitespace-nowrap"> {timeAgo(voter.timestamp)} </td> <td className="text-sm font-light px-6 py-4 whitespace-nowrap"> {voter.choosen ? ( <button className="border-2 rounded-full px-6 py-2.5 border-blue-600 text-blue-600 font-medium text-xs leading-tight uppercase hover:border-blue-700 focus:border-blue-700 focus:outline-none focus:ring-0 active:border-blue-800 transition duration-150 ease-in-out" > Accepted </button> ) : ( <button className="border-2 rounded-full px-6 py-2.5 border-red-600 text-red-600 font-medium text-xs leading-tight uppercase hover:border-red-700 focus:border-red-700 focus:outline-none focus:ring-0 active:border-red-800 transition duration-150 ease-in-out" > Rejected </button> )} </td> </tr> ))} </tbody> </table> </div> </div> </div> <div className="mt-4 text-center"> {voters.length >= 10 ? ( <button aria-current="page" className="rounded-full px-6 py-2.5 bg-blue-600 font-medium text-xs leading-tight uppercase hover:bg-blue-700 focus:bg-blue-700 focus:outline-none focus:ring-0 active:bg-blue-800 transition duration-150 ease-in-out dark:text-gray-300 dark:border dark:border-gray-500 dark:bg-transparent" > Load More </button> ) : null} </div> </div> ) } export default Voters
संदेश घटक
इस घटक के साथ संयुक्त कॉमेटचैट एसडीके की शक्ति के साथ, उपयोगकर्ता गुमनाम रूप से एक-से-कई चैट में संलग्न हो सकते हैं। योगदानकर्ता और हितधारक अपनी निर्णय लेने की प्रक्रिया में यहां एक प्रस्ताव पर आगे चर्चा कर सकते हैं। सभी उपयोगकर्ता अपनी गुमनामी बनाए रखते हैं और उनके पहचान चिह्नों द्वारा दर्शाए जाते हैं।
import Identicon from 'react-identicons' import { useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { truncate, useGlobalState } from '../store' import { getMessages, sendMessage, CometChat } from '../CometChat' const Messages = ({ gid }) => { const navigator = useNavigate() const [connectedAccount] = useGlobalState('connectedAccount') const [message, setMessage] = useState('') const [messages, setMessages] = useState([]) useEffect(() => { getMessages(gid).then((msgs) => { if (!!!msgs.code) setMessages(msgs.filter((msg) => msg.category == 'message')) }) listenForMessage(gid) }, [gid]) const listenForMessage = (listenerID) => { CometChat.addMessageListener( listenerID, new CometChat.MessageListener({ onTextMessageReceived: (message) => { setMessages((prevState) => [...prevState, message]) scrollToEnd() }, }) ) } const handleMessage = (e) => { e.preventDefault() sendMessage(gid, message).then((msg) => { if (!!!msg.code) { setMessages((prevState) => [...prevState, msg]) setMessage('') scrollToEnd() } }) } const scrollToEnd = () => { const elmnt = document.getElementById('messages-container') elmnt.scrollTop = elmnt.scrollHeight } const dateToTime = (date) => { let hours = date.getHours() let minutes = date.getMinutes() let ampm = hours >= 12 ? 'pm' : 'am' hours = hours % 12 hours = hours ? hours : 12 minutes = minutes < 10 ? '0' + minutes : minutes let strTime = hours + ':' + minutes + ' ' + ampm return strTime } return ( <div className="p-8"> <div className="flex flex-row justify-start"> <button className="px-4 py-2.5 bg-transparent hover:text-white font-bold text-xs leading-tight uppercase rounded-full shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out" onClick={() => navigator(`/proposal/${gid.substr(4)}`)} > Exit Chat </button> </div> <div id="messages-container" className="h-[calc(100vh_-_16rem)] overflow-y-auto sm:pr-4 my-3" > {messages.map((message, i) => message.sender.uid.toLowerCase() != connectedAccount.toLowerCase() ? ( <div key={i} className="flex flex-row justify-start my-2"> <div className="flex flex-col bg-transparent w-80 p-3 px-5 rounded-3xl shadow-md"> <div className="flex flex-row justify-start items-center space-x-2"> <Identicon string={message.sender.uid.toLowerCase()} size={25} className="h-10 w-10 object-contain shadow-md rounded-full mr-3" /> <span>@{truncate(message.sender.uid, 4, 4, 11)}</span> <small>{dateToTime(new Date(message.sentAt * 1000))}</small> </div> <small className="leading-tight my-2">{message.text}</small> </div> </div> ) : ( <div key={i} className="flex flex-row justify-end my-2"> <div className="flex flex-col bg-transparent w-80 p-3 px-5 rounded-3xl shadow-md shadow-blue-300"> <div className="flex flex-row justify-start items-center space-x-2"> <Identicon string={connectedAccount.toLowerCase()} size={25} className="h-10 w-10 object-contain shadow-md rounded-full mr-3" /> <span>@you</span> <small>{dateToTime(new Date(message.sentAt * 1000))}</small> </div> <small className="leading-tight my-2">{message.text}</small> </div> </div> ) )} </div> <form onSubmit={handleMessage} className="flex flex-row"> <input className="w-full bg-transparent rounded-lg p-4 focus:ring-0 focus:outline-none border-gray-500" type="text" placeholder="Write a message..." value={message} onChange={(e) => setMessage(e.target.value)} required /> <button type="submit" hidden> send </button> </form> </div> ) } export default Messages
प्रस्ताव घटक बनाएँ
यह घटक आपको ऊपर की छवि में देखे गए क्षेत्रों पर जानकारी की आपूर्ति करके एक प्रस्ताव देने की सुविधा देता है। नीचे दिए गए कोड को देखें।
import { useState } from 'react' import { FaTimes } from 'react-icons/fa' import { raiseProposal } from '../Dominion' import { setGlobalState, useGlobalState } from '../store' import { toast } from 'react-toastify' const CreateProposal = () => { const [createModal] = useGlobalState('createModal') const [title, setTitle] = useState('') const [amount, setAmount] = useState('') const [beneficiary, setBeneficiary] = useState('') const [description, setDescription] = useState('') const handleSubmit = (e) => { e.preventDefault() if (!title || !description || !beneficiary || !amount) return const proposal = { title, description, beneficiary, amount } raiseProposal(proposal).then((proposed) => { if (proposed) { toast.success('Proposal created, reloading in progress...') closeModal() window.location.reload() } }) } const closeModal = () => { setGlobalState('createModal', 'scale-0') resetForm() } const resetForm = () => { setTitle('') setAmount('') setBeneficiary('') setDescription('') } return ( <div className={`fixed top-0 left-0 w-screen h-screen flex items-center justify-center bg-black bg-opacity-50 transform z-50 transition-transform duration-300 ${createModal}`} > <div className="bg-white dark:bg-[#212936] shadow-xl shadow-[#122643] dark:shadow-gray-500 rounded-xl w-11/12 md:w-2/5 h-7/12 p-6"> <form className="flex flex-col"> <div className="flex flex-row justify-between items-center"> <p className="font-semibold">Raise Proposal</p> <button type="button" onClick={closeModal} className="border-0 bg-transparent focus:outline-none" > <FaTimes /> </button> </div> <div className="flex flex-row justify-between items-center border border-gray-500 dark:border-gray-500 rounded-xl mt-5"> <input className="block w-full text-sm bg-transparent border-0 focus:outline-none focus:ring-0" type="text" name="title" placeholder="Title" onChange={(e) => setTitle(e.target.value)} value={title} required /> </div> <div className="flex flex-row justify-between items-center border border-gray-500 dark:border-gray-500 rounded-xl mt-5"> <input className="block w-full text-sm bg-transparent border-0 focus:outline-none focus:ring-0" type="text" name="amount" placeholder="eg 2.5 Eth" onChange={(e) => setAmount(e.target.value)} value={amount} required /> </div> <div className="flex flex-row justify-between items-center border border-gray-500 dark:border-gray-500 rounded-xl mt-5"> <input className="block w-full text-sm bg-transparent border-0 focus:outline-none focus:ring-0" type="text" name="beneficiary" placeholder="Beneficiary Address" onChange={(e) => setBeneficiary(e.target.value)} value={beneficiary} required /> </div> <div className="flex flex-row justify-between items-center border border-gray-500 dark:border-gray-500 rounded-xl mt-5"> <textarea className="block w-full text-sm resize-none bg-transparent border-0 focus:outline-none focus:ring-0 h-20" type="text" name="description" placeholder="Description" onChange={(e) => setDescription(e.target.value)} value={description} required ></textarea> </div> <button className="rounded-lg px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase hover:bg-blue-700 focus:bg-blue-700 focus:outline-none focus:ring-0 active:bg-blue-800 transition duration-150 ease-in-out mt-5" onClick={handleSubmit} > Submit Proposal </button> </form> </div> </div> ) } export default CreateProposal
प्रमाणीकरण घटक
यह घटक आपको चैट सुविधाओं में भाग लेने में मदद करता है। यदि आपने पहले ही साइन अप कर लिया है तो आपको एक खाता बनाना होगा या लॉगिन करना होगा। लॉग इन करके, आप एक समूह चैट में भाग लेने में सक्षम हो सकते हैं और वेब3.0 शैली में एक प्रस्ताव में अन्य प्रतिभागियों के साथ कुछ गुमनाम बातचीत कर सकते हैं। नीचे दिए गए कोड को देखें।
import { FaTimes } from 'react-icons/fa' import { loginWithCometChat, signInWithCometChat } from '../CometChat' import { setGlobalState, useGlobalState } from '../store' import { toast } from 'react-toastify' const ChatLogin = () => { const [loginModal] = useGlobalState('loginModal') const [connectedAccount] = useGlobalState('connectedAccount') const handleSignUp = () => { signInWithCometChat(connectedAccount, connectedAccount).then((user) => { if (!!!user.code) { toast.success('Account created, now click the login button.') } else { toast.error(user.message) } }) } const handleLogin = () => { loginWithCometChat(connectedAccount).then((user) => { if (!!!user.code) { setGlobalState('currentUser', user) toast.success('Logged in successful!') closeModal() } else { toast.error(user.message) } }) } const closeModal = () => { setGlobalState('loginModal', 'scale-0') } return ( <div className={`fixed top-0 left-0 w-screen h-screen flex items-center justify-center bg-black bg-opacity-50 transform z-50 transition-transform duration-300 ${loginModal}`} > <div className="bg-white dark:bg-[#212936] shadow-xl shadow-[#122643] dark:shadow-gray-500 rounded-xl w-11/12 md:w-2/5 h-7/12 p-6"> <div className="flex flex-col"> <div className="flex flex-row justify-between items-center"> <p className="font-semibold">Authenticate</p> <button type="button" onClick={closeModal} className="border-0 bg-transparent focus:outline-none" > <FaTimes /> </button> </div> <div className="my-2 font-light"> <span> Once you login, you will be enabled to chat with other stakeholders to make a well-informed voting. </span> </div> <div className="flex flex-row justify-between items-center mt-2" role="group" > <button className="rounded-lg px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase hover:bg-blue-700 focus:bg-blue-700 focus:outline-none focus:ring-0 active:bg-blue-800 transition duration-150 ease-in-out mt-5" onClick={handleLogin} > Login </button> <button className="rounded-lg px-6 py-2.5 bg-transparent text-blue-600 font-medium text-xs leading-tight uppercase hover:bg-blue-700 hover:text-white focus:bg-blue-700 focus:outline-none focus:ring-0 active:bg-blue-800 transition duration-150 ease-in-out mt-5 border-blue-600" onClick={handleSignUp} > Create Account </button> </div> </div> </div> </div> ) } export default ChatLogin
बढ़िया, आइए सुनिश्चित करें कि विचारों का अच्छी तरह से प्रतिनिधित्व किया गया है …
होम व्यू
इस दृश्य में एक असाधारण डीएओ उपयोगकर्ता अनुभव प्रदान करने के लिए header
, banner
और proposals
घटक शामिल हैं। हमने इस रूप को प्राप्त करने के लिए Tailwind CSS की शक्ति का भी उपयोग किया। नीचे दिए गए कोड को देखें।
import Banner from '../components/Banner' import ChatLogin from '../components/ChatLogin' import CreateProposal from '../components/CreateProposal' import Header from '../components/Header' import Proposals from '../components/Proposals' const Home = () => { return ( <> <Header /> <Banner /> <Proposals /> <CreateProposal /> <ChatLogin /> </> ) } export default Home
प्रस्ताव दृश्य
यह दृश्य एक एकल घटक की सहज प्रस्तुति के लिए हेडर, प्रस्ताव विवरण और मतदाता घटक को एक साथ जोड़ता है। नीचे दिए गए कोड को देखें।
import Header from '../components/Header' import ProposalDetails from '../components/ProposalDetails' import Voters from '../components/Voters' const Proposal = () => { return ( <> <Header /> <ProposalDetails /> <Voters /> </> ) } export default Proposal
चैट व्यू
अंत में, चैट दृश्य में गुणवत्ता चैट इंटरफ़ेस प्रदान करने के लिए शीर्षलेख और संदेश घटक शामिल होते हैं। नीचे दिए गए कोड को देखें।
import { useParams, useNavigate } from 'react-router-dom' import { useEffect, useState } from 'react' import { getGroup } from '../CometChat' import { toast } from 'react-toastify' import Header from '../components/Header' import Messages from '../components/Messages' const Chat = () => { const { gid } = useParams() const navigator = useNavigate() const [group, setGroup] = useState(null) useEffect(() => { getGroup(gid).then((group) => { if (!!!group.code) { setGroup(group) } else { toast.warning('Please join the group first!') navigator(`/proposal/${gid.substr(4)}`) } }) }, [gid]) return ( <> <Header /> <Messages gid={gid} /> </> ) } export default Chat
कमाल है, App.jsx
फ़ाइल को भी अपडेट करना न भूलें।
ऐप कंपोनेंट ऐप कंपोनेंट को नीचे दिए गए कोड से बदलें।
import { useEffect, useState } from 'react' import { Routes, Route } from 'react-router-dom' import { loadWeb3 } from './Dominion' import { ToastContainer } from 'react-toastify' import { isUserLoggedIn } from './CometChat' import Home from './views/Home' import Proposal from './views/Proposal' import Chat from './views/Chat' import 'react-toastify/dist/ReactToastify.min.css' const App = () => { const [loaded, setLoaded] = useState(false) useEffect(() => { loadWeb3().then((res) => { if (res) setLoaded(true) }) isUserLoggedIn() }, []) return ( <div className="min-h-screen bg-white text-gray-900 dark:bg-[#212936] dark:text-gray-300"> {loaded ? ( <Routes> <Route path="/" element={<Home />} /> <Route path="proposal/:id" element={<Proposal />} /> <Route path="chat/:gid" element={<Chat />} /> </Routes> ) : null} <ToastContainer position="top-center" autoClose={5000} hideProgressBar={false} newestOnTop={false} closeOnClick rtl={false} pauseOnFocusLoss draggable pauseOnHover /> </div> ) } export default App
src, >> डायरेक्टरी पर निम्नलिखित कोड को उनकी संबंधित फाइलों में पेस्ट करें।
इंडेक्स.जेएसएक्स फाइल
import React from 'react' import ReactDOM from 'react-dom' import { BrowserRouter } from 'react-router-dom' import './index.css' import App from './App' import { initCometChat } from './CometChat' initCometChat().then(() => { ReactDOM.render( <BrowserRouter> <App /> </BrowserRouter>, document.getElementById('root') ) })
index.css फ़ाइल
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;600;700&display=swap'); * html { padding: 0; margin: 0; box-sizing: border-box; } body { margin: 0; font-family: 'Open Sans', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } @tailwind base; @tailwind components; @tailwind utilities;
कॉमेटचैट.जेएसएक्स
import Web3 from 'web3' import { setGlobalState, getGlobalState } from './store' import DominionDAO from './abis/DominionDAO.json' const { ethereum } = window const connectWallet = async () => { try { if (!ethereum) return alert('Please install Metamask') const accounts = await ethereum.request({ method: 'eth_requestAccounts' }) setGlobalState('connectedAccount', accounts[0]) } catch (error) { console.log(JSON.stringify(error)) } } const raiseProposal = async ({ title, description, beneficiary, amount }) => { try { amount = window.web3.utils.toWei(amount.toString(), 'ether') const contract = getGlobalState('contract') const account = getGlobalState('connectedAccount') let proposal = await contract.methods .createProposal(title, description, beneficiary, amount) .send({ from: account }) return proposal } catch (error) { console.log(error.message) return error } } const performContribute = async (amount) => { try { amount = window.web3.utils.toWei(amount.toString(), 'ether') const contract = getGlobalState('contract') const account = getGlobalState('connectedAccount') let balance = await contract.methods .contribute() .send({ from: account, value: amount }) balance = window.web3.utils.fromWei( balance.events.Action.returnValues.amount ) return balance } catch (error) { console.log(error.message) return error } } const retrieveProposal = async (id) => { const web3 = window.web3 try { const contract = getGlobalState('contract') const proposal = await contract.methods.getProposal(id).call().wait() return { id: proposal.id, amount: web3.utils.fromWei(proposal.amount), title: proposal.title, description: proposal.description, paid: proposal.paid, passed: proposal.passed, proposer: proposal.proposer, upvotes: Number(proposal.upvotes), downvotes: Number(proposal.downvotes), beneficiary: proposal.beneficiary, executor: proposal.executor, duration: proposal.duration, } } catch (error) { console.log(error) } } const reconstructProposal = (proposal) => { return { id: proposal.id, amount: window.web3.utils.fromWei(proposal.amount), title: proposal.title, description: proposal.description, paid: proposal.paid, passed: proposal.passed, proposer: proposal.proposer, upvotes: Number(proposal.upvotes), downvotes: Number(proposal.downvotes), beneficiary: proposal.beneficiary, executor: proposal.executor, duration: proposal.duration, } } const getProposal = async (id) => { try { const proposals = getGlobalState('proposals') return proposals.find((proposal) => proposal.id == id) } catch (error) { console.log(error) } } const voteOnProposal = async (proposalId, supported) => { try { const contract = getGlobalState('contract') const account = getGlobalState('connectedAccount') const vote = await contract.methods .performVote(proposalId, supported) .send({ from: account }) return vote } catch (error) { console.log(error) return error } } const listVoters = async (id) => { try { const contract = getGlobalState('contract') const votes = await contract.methods.getVotesOf(id).call() return votes } catch (error) { console.log(error) } } const payoutBeneficiary = async (id) => { try { const contract = getGlobalState('contract') const account = getGlobalState('connectedAccount') const balance = await contract.methods .payBeneficiary(id) .send({ from: account }) return balance } catch (error) { return error } } const loadWeb3 = async () => { try { if (!ethereum) return alert('Please install Metamask') window.web3 = new Web3(ethereum) await ethereum.request({ method: 'eth_requestAccounts' }) window.web3 = new Web3(window.web3.currentProvider) const web3 = window.web3 const accounts = await web3.eth.getAccounts() setGlobalState('connectedAccount', accounts[0]) const networkId = await web3.eth.net.getId() const networkData = DominionDAO.networks[networkId] if (networkData) { const contract = new web3.eth.Contract( DominionDAO.abi, networkData.address ) const isStakeholder = await contract.methods .isStakeholder() .call({ from: accounts[0] }) const proposals = await contract.methods.getProposals().call() const balance = await contract.methods.daoBalance().call() const mybalance = await contract.methods .getBalance() .call({ from: accounts[0] }) setGlobalState('contract', contract) setGlobalState('balance', web3.utils.fromWei(balance)) setGlobalState('mybalance', web3.utils.fromWei(mybalance)) setGlobalState('isStakeholder', isStakeholder) setGlobalState('proposals', structuredProposals(proposals)) } else { window.alert('DominionDAO contract not deployed to detected network.') } return true } catch (error) { alert('Please connect your metamask wallet!') console.log(error) return false } } const structuredProposals = (proposals) => { const web3 = window.web3 return proposals .map((proposal) => ({ id: proposal.id, amount: web3.utils.fromWei(proposal.amount), title: proposal.title, description: proposal.description, paid: proposal.paid, passed: proposal.passed, proposer: proposal.proposer, upvotes: Number(proposal.upvotes), downvotes: Number(proposal.downvotes), beneficiary: proposal.beneficiary, executor: proposal.executor, duration: proposal.duration, })) .reverse() } export { loadWeb3, connectWallet, performContribute, raiseProposal, retrieveProposal, voteOnProposal, getProposal, listVoters, payoutBeneficiary, }
चरण 1: नीचे दिए गए आदेश का उपयोग करके कुछ परीक्षण खाते को ganache-cli
साथ स्पिन करें:
ganache-cli -a
यह प्रत्येक खाते में लोड किए गए 100 नकली ईथर के साथ कुछ परीक्षण खाते बनाएगा, निश्चित रूप से, ये केवल परीक्षण उद्देश्यों के लिए हैं। नीचे दी गई छवि देखें:
चरण 2: मेटामास्क के साथ एक स्थानीय परीक्षण नेटवर्क जोड़ें जैसा कि नीचे की छवि में देखा गया है।
चरण 3: खाता आइकन पर क्लिक करें और आयात खाता चुनें।
लगभग पाँच निजी कुंजियों की प्रतिलिपि बनाएँ और उन्हें एक के बाद एक अपने स्थानीय परीक्षण नेटवर्क में जोड़ें। नीचे दी गई छवि देखें।
100 ईटीएच प्रीलोडेड के साथ अपने स्थानीय परीक्षण नेटवर्क में जोड़े गए नए खाते का निरीक्षण करें। सुनिश्चित करें कि आप लगभग पाँच खाते जोड़ते हैं ताकि आप अधिकतम परीक्षण कर सकें। नीचे दी गई छवि देखें।
अब एक नया टर्मिनल खोलें और नीचे कमांड चलाएँ।
truffle migrate # or truffle migrate --network rinkeby
उपरोक्त आदेश आपके स्मार्ट अनुबंध को आपके स्थानीय या इंफ्यूरिया रिंकीबी परीक्षण नेटवर्क पर तैनात करेगा।
इसके बाद, एक और टर्मिनल खोलें और रिएक्ट ऐप को yarn start
के साथ स्पिन करें।
हुर्रे, हमने अभी एक विकेन्द्रीकृत स्वायत्त संगठन विकसित करने के लिए एक अद्भुत ट्यूटोरियल पूरा किया है।
यदि आपको यह ट्यूटोरियल अच्छा लगा हो और आप मुझे अपने निजी गुरु के रूप में रखना चाहते हैं, तो कृपया मेरे साथ अपनी कक्षाएं बुक करें ।
अगली बार तक, शुभकामनाएँ।
गॉस्पेल डार्लिंगटन एक पूर्ण-स्टैक ब्लॉकचैन डेवलपर है जिसके पास सॉफ्टवेयर विकास उद्योग में 6+
वर्षों का अनुभव है।
सॉफ्टवेयर विकास, लेखन और शिक्षण के संयोजन से, वह प्रदर्शित करता है कि ईवीएम-संगत ब्लॉकचेन नेटवर्क पर विकेंद्रीकृत अनुप्रयोगों का निर्माण कैसे किया जाता है।
उनके ढेर में JavaScript
, React
, Vue
, Angular
, Node
, React Native
, Solidity
, NextJs
और बहुत कुछ शामिल हैं।
उनके बारे में अधिक जानकारी के लिए, कृपया ट्विटर , जीथब , लिंक्डइन , या उनकी वेबसाइट पर उनके पेज पर जाएं और उनका अनुसरण करें।