6. स्टेकिंग डीऐप(Dapp) कैसे बनाएं

R2W3 श्रृंखला के छठे सप्ताह में आपका स्वागत है। इस ट्यूटोरियल में, आप सीखेंगे कि कैसे एक सरल ब्याज दर इकट्ठा करने वाली, कटौती और जमा तथा निकालने की कार्यक्षमता के साथ एक साधारण स्टेकिंग डीऐप (डिसेंट्रलाइज्ड एप्लीकेशन) विकसित किया जाए।

R2W3 के पिछले 5 हफ्तों में, हमने कई अलग-अलग सॉलिडिटी और जावास्क्रिप्ट प्रिमिटिव्स (primitives) सीखे हैं जो हमें Web3 विकास के लिए बुनियादी बिल्डिंग ब्लॉक्स प्रदान करते हैं।

अब तक हमने सीखा है कि हार्डहैट को स्क्रैच से कैसे उपयोग किया जाए, अपने फ्रंटएंड कैसे बनाएं, और सॉलिडिटी कैसे लिखे।

हालाँकि ये सभी कौशल एक ठोस आधार बनाने के इच्छुक डेवलपर्स के लिए बेहद मूल्यवान हैं, लेकिन ऐसे टूल्स (tools) भी हैं जो एनवायरनमेंट सेटअप और डिपेंडेन्सीज की कुछ जटिलताओं को दूर करने में मदत करते हैं जो डेवलपर्स का काम आसान कर देता है ।

इनमें से एक टूल जिसकी हम अनुशंसा करते हैं वह है Scaffold-eth(स्कैफोल्ड-इथ)!

इसके मूल में, स्कैफोल्ड-इथ इथीरीयम पर तेजी से प्रोटोटाइपिंग के लिए एक ऑफ-द-शेल्फ स्टैक (stack) प्रदान करता है । इसके मदत से डेवलपर्स को अत्याधुनिक टूल्स तक पहुंच मिलती है जिससे वह इथीरीयम-आधारित डीऐप को जल्दी से सीख कर डिप्लॉय (deploy) कर सकें।

स्कैफोल्ड-इथ और अल्केमी का उपयोग करके, आप आसानी से ब्लॉकचैन पर कोड को संश्लेषित (synthesize) और डिप्लाॅय कर सकते हैं।

इस ट्यूटोरियल में, हम SpeedRunEthereum के चैलेंज #1 से बेस कोड का उपयोग करेंगे और एक साधारण स्टेकिंग (staking) डीऐप बनाने के लिए मिलकर काम करेंगे।

यदि आप क्रिप्टो स्टेकिंग से परिचित नहीं हैं, तो यह क्रिप्टो होल्डिंग्स को डिफाइ (DeFi) प्रोटोकॉल या स्मार्ट कॉन्ट्रैक्ट में लॉक या जमा करने और उन पर ब्याज अर्जित करने की प्रक्रिया को स्टेकिंग कहते है।

क्रिप्टो स्टेकिंग कई डिफाइ प्रोटोकॉल्स की आधारशिला बन गया है और डेवलपर्स को जटिल वित्तीय व्युत्पन्न उत्पाद (complex financial derivative products) बनाने में मदत करता है।

जबकि अधिकांश डिफाइ स्टेकिंग कॉन्ट्रैक्ट बेहद जटिल हैं, हम कुछ बुनियादी कॉन्ट्रैक्ट्स पर काम करेंगे ताकि हम प्रमुख अवधारणाओं (concepts) को सीख सकें।

साथ में, हम स्टेकिंग के लिए निम्नलिखित बिल्डिंग ब्लॉक्स सीखेंगे:

  1. स्कैफोल्ड-इथ का उपयोग करके
    • फ्रंटएंड्स हैक करना
    • सॉलिडिटी "बैकएंड्स" बनाना
  2. ETH (ईथर) को वॉलेट से स्मार्ट कॉन्ट्रैक्ट में स्थानांतरित करना और इसके विपरीत (vice versa)
  3. सॉलिडिटी मॉडिफायर्स (modifiers) का उपयोग करना

आइए यह समझने से शुरू करें कि स्कैफोल्ड-इथ कैसे काम करता है!

यदि आप साथ चलना चाहते हैं, तो इस निर्देशित वीडियो को देखें।

जब आप अपना प्रोजेक्ट पूरा कर लें, तो यहां सबमिट करें: https://university.alchemy.com/discord

  1. स्कैफोल्ड-इथ डाउनलोड करें

इस ट्यूटोरियल में, हम अपने स्मार्ट कॉन्ट्रैक्ट्स को तैयार करने और अपने फ्रंटएंड यूआई (Frontend UI) को एक साथ रखने के लिए Scaffold-Eth डेवलपर एनवायरनमेंट का उपयोग करने जा रहे हैं।

📘

शुरू करने से पहले, हम कुछ महत्वपूर्ण विवरणों को बताना चाहते हैं जिन्हें हमें ध्यान में रखना है!

Scaffold-Eth एनवायरनमेंट सेट-अप और फ्रंटएंड निर्भरता को दूर करने में बहुत बढ़िया है जो इसे एक शक्तिशाली टूल बनाता है।

हालाँकि बहुत सारी कार्यात्मकताएं (functionalities) हैं जो स्कैफोल्ड-इथ स्वचालित रूप से नियंत्रित करता हैं, कोड को समझना महत्वपूर्ण है ताकि हम यह समझ सके की इनमें से कुछ विशेषताएं कैसे उत्पन्न होती हैं जब आपके पास समग्र रूप से डेवलपर वातावरण की अधिक ठोस समझ होती है।

आइए, SpeedRunEthereum के चैलेंज #1 से बेस कोड रिपॉजिटरी को फोर्क (fork) करके शुरू करें:

git clone https://github.com/scaffold-eth/scaffold-eth-challenges.git challenge-1-decentralized-staking

cd challenge-1-decentralized-staking

git checkout challenge-1-decentralized-staking

yarn install

यदि आपने सफलतापूर्वक पालन किया है, तो आप अपनी बेस फाइल डायरेक्टरी में challenge-1-decentralized-staking शीर्षक वाले एक नए फ़ोल्डर को देखने में सक्षम होंगे।

ऊपर दिए गए आदेशों को चलाने के बाद, हमारे पास फाइलों से भरा एक बड़ा फोल्डर रह जाता है।

कोड पर जाने से पहले, हमें खुद को इस बात से परिचित कराना चाहिए कि स्कैफोल्ड-इथ में प्रमुख फाइलें कहाँ संग्रहीत हैं, ताकि हमें पता रहे कि कहाँ पर ध्यान केंद्रित करना है।


564
580

इस ट्यूटोरियल में, हम मुख्य रूप से Staker.sol और App.jsx पर काम करेंगे।

2. अपना पर्यावरण सेट अप करें

आगे बढ़ने के लिए, आपको निम्नलिखित तीन आदेशों के लिए तीन अलग-अलग टर्मिनल्स की आवश्यकता होगी:

अपना रिएक्ट फ्रंटेंड शुरू करें:

yarn start

अपना हार्डहाट बैकएंड प्रारंभ करें:

yarn chain

अपने packages/contracts फ़ाइल में सभी कॉन्ट्रैक्ट्स को कंपाइल, डिप्लॉय, और पब्लिश करें:

yarn deploy

📘

जब भी आप अपने कॉन्ट्रैक्ट्स को अपडेट करते हैं, तो yarn deploy --reset को Scaffold-Eth में अपने कॉन्ट्रैक्ट्स को रिफ्रेश (refresh) करने के लिए चलाएँ।

बढ़िया! अब आप http://localhost:3000/ पर इस रिपॉजिटरी के फ्रंटएंड यूआई को देख सकेंगे।

3. स्कैफोल्ड-इथ से परिचित हो जाओ

जबकि हमें पता है कि आप कोड लिखने के लिए उत्तेजित हैं, ध्यान रखने के लिए केवल कुछ और विवरण, फिर हम कोड लिखना शुरू करेंगे!

डिफ़ॉल्ट दृश्य में, हमारे पास दो टैब हैं - Staker UI और Debug Contracts


668

आगे बढ़ें और अपने फ़्रंटएंड पर आगे और पीछे स्विच करके अलग-अलग फीचर्स पर एक नज़र डाले।

Staker UI में वे सभी फ्रंटएंड कंपोनेंट्स शामिल हैं जिनके साथ हम मुख्य रूप से इंटरैक्ट करेंगे।

यदि आप प्रदान किए गए बटनों पर क्लिक करते हैं, तो आप देखेंगे कि उनमें से अधिकांश अभी तक जुड़े नहीं हैं और आप तुरंत एर्रस (त्रुटियों) का सामना करेंगे।

📘

Staker UI पर एक नज़र डालें। आप देखेंगे कि यह दर्दनाक रूप से संयमी है! यही हम मुख्य रूप से बाहर निकालेंगे।

चूंकि इथीरीयम पर किसी भी ऑन-चेन इंटरैक्शन के लिए टेस्टनेट ईथर (testnet ETH) की आवश्यकता होती है, इसलिए हैकिंग शुरू करने के लिए आपको लोकल टेस्टनेट ईथर की आवश्यकता होगी।

सबसे पहले, अपना लोकलहोस्ट वॉलेट ले।

ऊपरी दाएं कोने में "copy" बटन पर क्लिक करें।


948

अब, निचले बाएँ हाथ के कोने पर जाएँ। आप यहां लोकल फॉसेट (local faucet) का उपयोग करने में सक्षम होंगे।

  • या तो ओपन फील्ड में अपने पते को कॉपी/पेस्ट करें या "वॉलेट" आइकन पर क्लिक करें
  • विस्तृत दृश्य (expanded view) में अपना पता पेस्ट करें
  • अपने आप को कुछ टेस्ट ईथर भेजें

1128

अपने लोकल वॉलेट (local wallet) को भरने के बाद, आप अपने कॉन्ट्रैक्ट्स के साथ इंटरेक्ट करने में सक्षम होंगे!

दूसरा टैब, Debug Contracts, एक और फ्रंटएंड डिस्प्ले है जिसमें स्कैफोल्ड-इथ की महाशक्तियों में से एक समाहित है!

एक बार जब आप अपने कॉन्ट्रैक्ट्स को yarn deploy कर लेते हैं और कॉन्ट्रैक्ट डेटा को ठीक से पढ़ने के लिए इसे कॉन्फ़िगर कर लेते हैं, तो यह स्वचालित रूप से एक यूआई उत्पन्न करेगा जिससे आप अपने कॉन्ट्रैक्ट के फंक्शन्स के साथ इंटरैक्ट कर सकेंगे।

उदाहरण के लिए, नीचे दिए गए नमूने (सैंपल) में, हम केवल पैरामीटर्स को डाल कर और "Send" पर क्लिक करके अपने स्मार्ट कॉन्ट्रैक्ट के माध्यम से जानकारी पढ़ और लिख सकते हैं। स्कैफोल्ड-इथ के साथ, हमें केवल सीएलआई (CLI) कमांड्स का उपयोग करने की आवश्यकता नहीं है और हमारे पास प्रोटोटाइपिंग का अधिक सहज तरीका हैं।

📘

अगर आप Debug Contracts टैब में एक वेरिएबल को संग्रहित करना और देखना चाहते हैं, तो वेरिएबल को पब्लिक के रूप में सेट करना सुनिश्चित करें!

बहुत बढ़िया!

अब जब हम स्कैफोल्ड-इथ से परिचित हैं, तो हम कोड की गहराईयों को समझ सकते हैं।

4. सॉलिडिटी में गोता लगाएँ

हमारी Staker.sol फ़ाइल में हम देखते हैं कि हमारे पास एक खाली सॉलिडिटी फ़ाइल है जिसमे टिप्पणियों (comments) का एक समूह है जो निर्धारित करता है की इस फ़ाइल में क्या कोड लिखना है।

चूंकि रोड टू Web3 (R2W3) ट्यूटोरियल Scaffold-Eth's Challenge #1 से विचलित होता है, इसलिए हम आगे बढ़ सकते हैं और टिप्पणियों को अनदेखा करते हुए निम्नलिखित कोड के साथ शुरुआत कर सकते हैं।

pragma solidity >=0.6.0 <0.7.0;

import "hardhat/console.sol";
import "./ExampleExternalContract.sol";

contract Staker {
  ExampleExternalContract public exampleExternalContract;
  
  constructor(address exampleExternalContractAddress) public {
      exampleExternalContract = ExampleExternalContract(exampleExternalContractAddress);
  }
  
}

प्रोजेक्ट पैरामीटर्स

अपना स्मार्ट कॉन्ट्रैक्ट कोड लिखने से पहले, आइए देखें कि हम अपने स्टेकिंग डिऐप से कैसे काम करने की उम्मीद करते हैं!

  1. सरलता के लिए, हम केवल एक उपयोगकर्ता से हमारे स्टेकिंग डिऐप के साथ इंटरैक्ट करने की उम्मीद करते हैं
  2. हमें Staker कॉन्ट्रैक्ट में जमा करने और निकालने में सक्षम होना चाहिए
    • स्टेकिंग एक सिंगल-यूज़ एक्शन है, जिसका अर्थ है कि एक बार स्टेक करने के बाद हम फिर से स्टेक नहीं कर सकते
    • कॉन्ट्रैक्ट से निकासी (withdrawal) करने से संपूर्ण मूल शेष राशि और किसी भी अर्जित ब्याज को हटा दिया जाता है
  3. Staker कॉन्ट्रैक्ट में प्रत्येक सेकेंड के लिए 0.1 ETH की ब्याज भुगतान दर है, जो कि जमा ETH ब्याज अर्जित करने के लिए पात्र है
  4. कॉन्ट्रैक्ट डिप्लॉयमेंट पर, Staker कॉन्ट्रैक्ट 2 टाइमस्टैम्प काउंटरों के साथ शुरू होना चाहिए। पहली समय सीमा 2 मिनट और दूसरी समय सीमा 4 मिनट निर्धारित की जानी चाहिए
    • 2 मिनट की समय सीमा उस अवधि को निर्धारित करती है जिसमें स्टेकिंग उपयोगकर्ता धन जमा करने में सक्षम होता है। (t=0 मिनट और t=2 मिनट के बीच, स्टेकिंग उपयोगकर्ता जमा कर सकता है)
    • सभी ब्लॉक जो धनराशि जमा करने से 2 मिनट की समय सीमा के बीच होंगे वह ब्याज अर्जित करने के लिए मान्य हैं
    • 2 मिनट की निकासी की समय सीमा बीत जाने के बाद, स्टेकिंग उपयोगकर्ता 4 मिनट की समय सीमा समाप्त होने तक संपूर्ण मूल शेष राशि और किसी भी अर्जित ब्याज को वापस लेने में सक्षम होता है।
    • निकासी के लिए अतिरिक्त 2-मिनट की अवधि बीत जाने के बाद, उपयोगकर्ता को अपनी निधि (funds) को वापस लेने से रोक दिया जाता है क्योंकि ****समय सीमा समाप्त हों चुकी है।
  5. यदि किसी स्टेकिंग उपयोगकर्ता के पास धनराशि शेष है, तो हमारे पास एक अंतिम फंक्शन है जिसे हम बाहरी कॉन्ट्रैक्ट में निधियों को "लॉक" करने के लिए कॉल कर सकते हैं जो Scaffold-Eth एनवायरनमेंट में पहले से स्थापित है, ExampleExternalContract.sol

📘

जबकि ऊपर सूचीबद्ध स्टेकिंग पैरामीटर थोड़े पेचीदा लग सकते हैं, कई वास्तविक डिऐप्स में समान प्रिमिटिव्स होते है जहां उपयोगकर्ताओं के पास जमा करने और निकालने की सीमित अवधि होती है।

और, कई डिऐप्स "अनुत्पादक"(unproductive) पूंजी को हतोत्साहित करेंगे, जो कि स्टेकिंग अवधि समाप्त होने के बाद भी कॉन्ट्रैक्ट में रह जाती है।

कभी-कभी, प्रतीक्षा अवधि समाप्त होने के बाद डिफाइ प्रोटोकॉल बकाया जमा राशि को अवशोषित भी कर सकता है, अंतिम पैरामीटर के समान जो हमारे ट्यूटोरियल में बताए गया है।

सॉलिडिटी मैपिंग्स

हमारे स्मार्ट कॉन्ट्रैक्ट में, हमें कुछ डेटा संग्रहित करने में मदत के लिए दो मैपिंग्स की आवश्यकता होगी।

विशेष रूप से, हमें ट्रैक रखने के लिए कुछ चाहिए:

  1. कॉन्ट्रैक्ट में कितना ETH जमा किया गया है
  2. कॉट्रेक्ट में ETH कब जामा हुआ

हम इसे निम्नलिखित तरह से प्राप्त कर सकते हैं:

mapping(address => uint256) public balances; 
mapping(address => uint256) public depositTimestamps;

पब्लिक वेरिएबल्स (public Variables)

ऊपर बताए गए दिशानिर्देशों के अनुसार, हमें कुछ भिन्न वेरिएबल्स की भी आवश्यकता होगी।

uint256 public constant rewardRatePerSecond = 0.1 ether; 
uint256 public withdrawalDeadline = block.timestamp + 120 seconds; 
uint256 public claimDeadline = block.timestamp + 240 seconds; 
uint256 public currentBlock = 0;

इनाम की दर स्टेक की गई मूल राशि पर ETH के संवितरण (disbursement) के लिए ब्याज दर निर्धारित करती है।

निकासी और दावे की समय सीमा हमें स्टेकिंग तंत्र को शुरू या समाप्त करने के लिए समय सीमा निर्धारित करने में मदत करती है।

और, अंत में, हमारे पास एक वेरिएबल है जिसका उपयोग हम वर्तमान ब्लॉक को संग्रहित करने के लिए करते हैं।

📘

हमारे कॉन्ट्रैक्ट की शुरुआत के ठीक बाद XXX सेकंड की समय सीमा तय करने के लिए हम block.timestamp + XXX सेकंड का उपयोग करते हैं। समय तंत्र के रूप में यह निश्चित रूप से थोड़ा सरल है; क्या आप इसे लागू करने के बेहतर तरीके के बारे में सोच सकते हैं, उदाहरण के लिए एक जो एक मानक के रूप में स्थापित किया जा सके ।

इवेंट्स (Events)

भले ही हम इवेंट्स को अपने फ्रंटएंड पर नहीं इस्तेमाल करेंगे, फिर भी हमें यह सुनिश्चित करना चाहिए कि हम उन्हें अपने कॉन्ट्रैक्ट के प्रमुख हिस्सों में एमिट (emit) कर दें ताकि यह सुनिश्चित हो सके कि हम सर्वोत्तम प्रोग्रामिंग प्रथाओं को बनाए रखें।

event Stake(address indexed sender, uint256 amount); 
event Received(address, uint); 
event Execute(address indexed sender, uint256 amount);

अब जबकि हमारे पास प्रमुख पैरामीटर/वेरिएबल आ गए हैं, हम अपने ट्यूटोरियल में उपयोग किए जाने वाले विशिष्ट फ़ंक्शंस को तैयार करने के लिए आगे बढ़ सकते हैं।

रीड ओनली टाइम फ़ंक्शंस (READ ONLY Time Functions)

जैसा कि परियोजना के पैरामीटर्स में कहा गया है, कई अलग-अलग स्टेकिंग डिऐप्स की कार्यक्षमता "टाइम-लॉक" के अधीन हैं जो कुछ एक्शन्स को समय के अनुसार सक्षम / प्रतिबंधित करते है।

यहां हमारे पास दो अलग-अलग फ़ंक्शंस हैं जो निकासी के प्रारंभ और अंत को नियंत्रित करते हैं।

function withdrawalTimeLeft() public view returns (uint256 withdrawalTimeLeft) {
    if( block.timestamp >= withdrawalDeadline) {
      return (0);
    } else {
      return (withdrawalDeadline - block.timestamp);
    }
  }

  function claimPeriodLeft() public view returns (uint256 claimPeriodLeft) {
    if( block.timestamp >= claimDeadline) {
      return (0);
    } else {
      return (claimDeadline - block.timestamp);
    }
  }

दोनों फ़ंक्शंस वास्तव में डिजाइन में बहुत समान हैं।

उन दोनों में एक मानक (स्टैण्डर्ड) if -> else कथन है।

सशर्त (conditional) केवल यह जांचता है कि वर्तमान समय पब्लिक वैरिएबल्स सेक्शन में निर्धारित समय सीमा से अधिक है या कम है।

यदि वर्तमान समय पूर्व-निर्धारित समय सीमा से अधिक है, तो हम जानते हैं कि समय सीमा समाप्त हो गई है और हम 0 रिटर्न (return) करते हैं। यह दर्शाता है कि "state change" हुआ है।

अन्यथा, हम समय सीमा समाप्त होने से पहले शेष समय वापस कर देते हैं।

मॉडिफायर्स(Modifiers)

मॉडिफायर्स के अधिक गहन उदाहरण के लिए सॉलिडिटी बाई एग्जामपल(Solidity By Example) देखें।

संक्षेप में, सॉलिडिटी मॉडिफायर्स प्रगम्स होते हैं जो फ़ंक्शन कॉल से पहले और/या बाद में चल सकते हैं।

जबकि उनके पास कई अलग-अलग उद्देश्य हैं, सबसे आम और बुनियादी उपयोगों में से एक कुछ फ़ंक्शंस तक पहुंच को प्रतिबंधित करना है, यदि विशेष शर्तों को पूरा नहीं किया जाता है।

इस ट्यूटोरियल में, हम मॉडिफायर्स का उपयोग प्रमुख फ़ंक्शंस में गेटेड एक्सेस (gated access) स्थपित करने लिए करेंगे ताकि हमारी हिस्सेदारी, निकासी, और प्रत्यावर्तन कार्यात्मकताओं (our stake, withdraw, and repatriation functionalities) को निर्देशित कर सके।

यहां तीन मॉडिफायर्स हैं जिनका हम उपयोग करते हैं:

modifier withdrawalDeadlineReached( bool requireReached ) {
    uint256 timeRemaining = withdrawalTimeLeft();
    if( requireReached ) {
      require(timeRemaining == 0, "Withdrawal period is not reached yet");
    } else {
      require(timeRemaining > 0, "Withdrawal period has been reached");
    }
    _;
  }

  modifier claimDeadlineReached( bool requireReached ) {
    uint256 timeRemaining = claimPeriodLeft();
    if( requireReached ) {
      require(timeRemaining == 0, "Claim deadline is not reached yet");
    } else {
      require(timeRemaining > 0, "Claim deadline has been reached");
    }
    _;
  }

  modifier notCompleted() {
    bool completed = exampleExternalContract.completed();
    require(!completed, "Stake already completed!");
    _;
  }

मॉडिफायर्स withdrawalDeadlineReached(bool requireReached) और claimDeadlineReached(bool requireReached) दोनों एक बूलियन पैरामीटर को स्वीकार करते हैं और यह सुनिश्चित करने के लिए जांच करते हैं कि उनकी संबंधित समय सीमा या तो true है या false

मॉडिफायर notCompleted() इसी तरह से काम करता है लेकिन वास्तव में प्रकृति में थोड़ा अधिक जटिल है, भले ही इसमें कोड की कम पंक्तियां हों।

यह वास्तव में Staker के बाहर एक कॉन्ट्रैक्ट से completed() फ़ंक्शन पर कॉल करता है और जांच करता है कि क्या वह true लोटा रहा है या false ताकि यह सुनिश्चित हो सके की क्या वह स्विच किया गया है या नहीं।

अब, हम इन बनाए मॉडिफायर्स को अगले कुछ फ़ंक्शंस में गेटेड एक्सेस लागू करने के लिए हैं इस्तेमाल करेंगे ।

डिपॉजिट/स्टेकिंग फंक्शन (Depositing/Staking Function)

हमारे स्टेक फ़ंक्शन में, हम पूर्व में बनाए गए मॉडिफायर का उपयोग करते हैं, withdrawalDeadlineReached() के भीतर पैरामीटर को false और claimDeadlineReached() को false सेट कर के, क्योंकि हम नहीं चाहते हैं कि कोई भी समय सीमा अभी तक पारित हो।

// Stake function for a user to stake ETH in our contract
// हमारे कॉन्ट्रैक्ट में ETH को स्टेक करने के लिए एक उपयोगकर्ता के लिए स्टेक फंक्शन
  
  function stake() public payable withdrawalDeadlineReached(false) claimDeadlineReached(false) {
    balances[msg.sender] = balances[msg.sender] + msg.value;
    depositTimestamps[msg.sender] = block.timestamp;
    emit Stake(msg.sender, msg.value);
  }

शेष फंक्शन सामान्य "डिपॉजिट” परिदृश्य (scenario) में काफी मानक (standard) हैं जहां हमारे बैलेंस मैपिंग को भेजे गए धन को शामिल करने के लिए अपडेट किया जाता है।

हम अपने डिपॉजिट टाइमस्टैम्प को डिपॉजिट के वर्तमान समय के साथ भी सेट करते हैं ताकि हम बाद में ब्याज गणना के लिए उस संग्रहित मूल्य तक पहुंच सकें।

निकासी फंक्शन (Withdrawal Function)

हमारे विथड्रावल फंक्शन में, हम फिर से पहले बनाए गए मॉडिफायर का उपयोग करते हैं लेकिन इस बार हम चाहते हैं कि withdrawalDeadlineReached() true हो और claimDeadlineReached() false हो।

मॉडिफायर्स/पैरामीटर्स के इस सेट का मतलब है कि हम निकासी के लिए अच्छे स्थान पर हैं क्योंकि यह बिना किसी दंड के निकासी का समय है और हमें ब्याज भी मिलता है।

/*
  Withdraw function for a user to remove their staked ETH inclusive
  of both the principle balance and any accrued interest
  */
/*
	 निकासी फंक्शन किसी भी उपयोगकर्ता के लिए अपने दाँव पर लगे (staked) ETH को वापिस लेने के लिए जिसमे
   दोनों यानि मूल शेष राशि और कोई भी अर्जित ब्याज शामिल है
*/
  
  function withdraw() public withdrawalDeadlineReached(true) claimDeadlineReached(false) notCompleted{
    require(balances[msg.sender] > 0, "You have no balance to withdraw!");
    uint256 individualBalance = balances[msg.sender];
    uint256 indBalanceRewards = individualBalance + ((block.timestamp-depositTimestamps[msg.sender])*rewardRatePerSecond);
    balances[msg.sender] = 0;

    // Transfer all ETH via call! (not transfer) cc: https://solidity-by-example.org/sending-ether
    (bool sent, bytes memory data) = msg.sender.call{value: indBalanceRewards}("");
    require(sent, "RIP; withdrawal failed :( ");
  }

बाकी फंक्शन कुछ महत्वपूर्ण कदम उठाता है।

  1. यह सुनिश्चित करने के लिए जांच करता है कि ETH को वापस लेने का प्रयास करने वाले व्यक्ति की वास्तव में गैर-शून्य हिस्सेदारी (non-zero stake) है।
  2. यह डिपॉजिट से निकासी तक बीते सेकंड की संख्या लेकर और हमारे ब्याज स्थिरांक (interest constant) से गुणा करके ब्याज में देय ETH की राशि की गणना करता है।
  3. यह उपयोगकर्ता की शेष राशि ETH को 0 पर सेट करता है ताकि कोई दोहरी गणना न हो सके।
  4. यह ETH को स्मार्ट कॉन्ट्रैक्ट से वापस उपयोगकर्ता के वॉलेट में स्थानांतरित करता है।

रिपेट्रिएशन फंक्शन एक्जीक्यूट करें (Execute Repatriation Function)

यहां, हम चाहते हैं कि claimDeadlineReached() true हो क्योंकि अनुत्पादक धन (unproductive funds) का प्रत्यावर्तन केवल 4 मिनट के बाद ही हो सकता है।

इसी तरह, हम चाहते हैं कि notCompleted true हो क्योंकि यह डीऐप केवल एक ही उपयोग के लिए बनाया गया है।

/*
  Allows any user to repatriate "unproductive" funds that are left in the staking contract
  past the defined withdrawal period
  */
/*
	 परिभाषित निकासी अवधि के बाद किसी भी उपयोगकर्ता को "अनुत्पादक" निधियों (फंड्स) को प्रत्यावर्तित करने 
   की अनुमति देता है जो स्टेकिंग कॉन्ट्रैक्ट में बची हुई हैं
*/
  
  function execute() public claimDeadlineReached(true) notCompleted {
    uint256 contractBalance = address(this).balance;
    exampleExternalContract.complete{value: address(this).balance}();
  }

बाकी फ़ंक्शंस:

  1. Staker कॉन्ट्रैक्ट में ETH की वर्तमान शेष राशि प्राप्त करता है
  2. रेपो के exampleExternalContract को ETH भेजता है

यदि आप हमारे साथ सोलिडीटी लिखते आ रहे हैं, तो आपका Staker.sol इस तरह दिखना चाहिए:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.4;

import "hardhat/console.sol";
import "./ExampleExternalContract.sol";

contract Staker {

  ExampleExternalContract public exampleExternalContract;

  mapping(address => uint256) public balances;
  mapping(address => uint256) public depositTimestamps;

  uint256 public constant rewardRatePerSecond = 0.1 ether;
  uint256 public withdrawalDeadline = block.timestamp + 120 seconds;
  uint256 public claimDeadline = block.timestamp + 240 seconds;
  uint256 public currentBlock = 0;

  // Events
  event Stake(address indexed sender, uint256 amount);
  event Received(address, uint);
  event Execute(address indexed sender, uint256 amount);

  // Modifiers
  /*
  Checks if the withdrawal period has been reached or not
	यह जाँचता है कि निकासी की अवधि पूरी हो गई है या नहीं
  */
  modifier withdrawalDeadlineReached( bool requireReached ) {
    uint256 timeRemaining = withdrawalTimeLeft();
    if( requireReached ) {
      require(timeRemaining == 0, "Withdrawal period is not reached yet");
    } else {
      require(timeRemaining > 0, "Withdrawal period has been reached");
    }
    _;
  }

  /*
  Checks if the claim period has ended or not
	जांचता है कि दावा अवधि समाप्त हो गई है या नहीं
  */
  modifier claimDeadlineReached( bool requireReached ) {
    uint256 timeRemaining = claimPeriodLeft();
    if( requireReached ) {
      require(timeRemaining == 0, "Claim deadline is not reached yet");
    } else {
      require(timeRemaining > 0, "Claim deadline has been reached");
    }
    _;
  }

  /*
  Requires that the contract only be complete 
यह सुनिश्चित करता है कि कॉन्ट्रैक्ट केवल एक बार पूरा हो!
  */
  modifier notCompleted() {
    bool completed = exampleExternalContract.completed();
    require(!completed, "Stake already completed!");
    _;
  }

  constructor(address exampleExternalContractAddress){
      exampleExternalContract = ExampleExternalContract(exampleExternalContractAddress);
  }

  // Stake function for a user to stake ETH in our contract
	// हमारे कॉन्ट्रैक्ट में ETH को दांव पर लगाने के लिए एक उपयोगकर्ता के लिए स्टेक फंक्शन
  function stake() public payable withdrawalDeadlineReached(false) claimDeadlineReached(false){
    balances[msg.sender] = balances[msg.sender] + msg.value;
    depositTimestamps[msg.sender] = block.timestamp;
    emit Stake(msg.sender, msg.value);
  }

  /*
  Withdraw function for a user to remove their staked ETH inclusive
  of both principal and any accrued interest
	
	निकासी फंक्शन किसी भी उपयोगकर्ता के लिए अपने दाँव पर लगे (staked) ETH को वापिस लेने के लिए जिसमे
  दोनों यानि मूल शेष राशि और कोई भी अर्जित ब्याज शामिल है
  */
  function withdraw() public withdrawalDeadlineReached(true) claimDeadlineReached(false) notCompleted{
    require(balances[msg.sender] > 0, "You have no balance to withdraw!");
    uint256 individualBalance = balances[msg.sender];
    uint256 indBalanceRewards = individualBalance + ((block.timestamp-depositTimestamps[msg.sender])*rewardRatePerSecond);
    balances[msg.sender] = 0;

    // Transfer all ETH via call! (not transfer) cc: https://solidity-by-example.org/sending-ether
    (bool sent, bytes memory data) = msg.sender.call{value: indBalanceRewards}("");
    require(sent, "RIP; withdrawal failed :( ");
  }

  /*
  Allows any user to repatriate "unproductive" funds that are left in the staking contract
  past the defined withdrawal period

	परिभाषित निकासी अवधि के बाद किसी भी उपयोगकर्ता को "अनुत्पादक" निधियों (फंड्स) को प्रत्यावर्तित करने 
  की अनुमति देता है जो स्टेकिंग कॉन्ट्रैक्ट में बची हुई हैं
  */
  function execute() public claimDeadlineReached(true) notCompleted {
    uint256 contractBalance = address(this).balance;
    exampleExternalContract.complete{value: address(this).balance}();
  }

  /*
  READ-ONLY function to calculate the time remaining before the minimum staking period has passed
	केवल रीड-ओनली फंक्शन जो स्टेकिंग अवधि बीतने से पहले न्यूनतम शेष (बचा हुआ समय) समय की गणना कर सके 
  */
  function withdrawalTimeLeft() public view returns (uint256 withdrawalTimeLeft) {
    if( block.timestamp >= withdrawalDeadline) {
      return (0);
    } else {
      return (withdrawalDeadline - block.timestamp);
    }
  }

   /*
  READ-ONLY function to calculate the time remaining before the minimum staking period has passed
	रीड-ओनली फंक्शन जो स्टेकिंग अवधि बीतने से पहले न्यूनतम शेष(बचा हुआ समय) समय की गणना कर सके 
  */
  function claimPeriodLeft() public view returns (uint256 claimPeriodLeft) {
    if( block.timestamp >= claimDeadline) {
      return (0);
    } else {
      return (claimDeadline - block.timestamp);
    }
  }

  /*
  Time to "kill-time" on our local testnet
	हमारे स्थानीय टेस्टनेट पर "किल-टाइम" करने का समय
  */
  function killTime() public {
    currentBlock = block.timestamp;
  }

  /*
  \Function for our smart contract to receive ETH
	ETH प्राप्त करने के लिए हमारे स्मार्ट कॉन्ट्रैक्ट का फंक्शन
  cc: https://docs.soliditylang.org/en/latest/contracts.html#receive-ether-function
  */
  receive() external payable {
      emit Received(msg.sender, msg.value);
  }

}

5. फ्रेंटएन्ड तैयार करे

बहुत बढ़िया! हम बहुत सी सॉलिडिटी सीख चुके है । जब फ्रंटएंड डिस्प्ले की बात आती है, तो स्कैफोल्ड-इथ चीजों को अच्छा और सरल रखने की कोशिश करता है। इसमें बहुत सारे अलग-अलग रीएक्ट कंपोनेंट्स (react components) शामिल हैं जो उपयोगकर्ताओं को बढ़िया यूआई के लिए लो-कोड समाधान प्रदान करते हैं! हम आपको, आपके लिए उपलब्ध विभिन्न कंपोनेंट्स का उपयोग करने के लिए प्रोत्साहित करते हैं , लेकिन इस बीच, हम और अधिक सीखने जा रहे हैं।

हमारे App.jsx फ़ाइल को देखते हुए, विशेष रूप से लिंक 573 के आसपास कोड ब्लॉक पर, हम कोड का एक ब्लॉक देखते हैं जो हमारे सॉलिडिटी कॉन्ट्रैक्ट्स से उत्सर्जित इवेंट्स को प्राप्त करने और उन्हें एक सूची के रूप में प्रदर्शित करने के लिए उपयोग किया जाता है।

प्रभावी रूप से, यह हमें हमारे स्मार्ट कॉन्ट्रैक्ट्स से कॉल किये गए विभिन्न फ़ंक्शंस को लॉग करने, संग्रहीत जानकारी को पार्स करने और फिर डीऐप उपयोगकर्ताओं को उनके ऑन-चेन इतिहास को देखने की अनुमति देता है।

जबतक हम अपने सॉलिडिटी कॉन्ट्रैक्ट में इवेंट्स को एमिट करके अच्छे प्रोग्रामिंग स्टैंडर्ड्स का अभ्यास करेंगे, इस बार, हम सरलता के लिए अपने फ्रंटएंड में इवेंट्स को नजरअंदाज करेंगे, तो आइए उस कोड ब्लॉक को पूरी तरह से हटा दें।

यदि आप अपने Staker UI टैब को देखते हैं, तो आप देखेंगे कि ईवेंट बॉक्स मिटा दिया गया है।

फ्रेंटएन्ड एडिटस(Frontend Edits)

अधिकांश भाग के लिए, बहुत सारे फ्रंटएंड कोड डिफ़ॉल्ट की तरह ही रहेंगे! पिछले चरण में, हमने पहले ही इवेंट्स रिएक्ट कॉम्पोनेंट को हटा दिया था।

हमारा अंतिम लक्ष्य एक अच्छा, सरल यूआई होगा जो निम्न जैसा दिखता है:


842

ध्यान दें कि डिफ़ॉल्ट फ्रंटएंड में डिफ़ॉल्ट रेपो से कुछ विजुअल एलिमेंट्स (visual elements) गायब हैं।

प्रति सेकंड इनाम दर

इस भाग के निर्माण के लिए, हम स्टेकर कॉन्ट्रैक्ट ब्लॉक के ठीक नीचे कोड का निम्नलिखित भाग डालते हैं:

<div style={{ padding: 8, marginTop: 16 }}>
    <div>Reward Rate Per Second:</div>
    <Balance balance={rewardRatePerSecond} fontSize={64} /> ETH
</div>

यहाँ, हम स्वरूपण (formatting) में मदत करने के लिए स्कैफोल्ड-इथ के balance react component का लाभ उठाते हैं!

डेडलाइन यूआई एलिमेंट(Deadline UI Elements)

डेडलाइन यूआई एलिमेंट बनाने के लिए, हम कोड के निम्नलिखित 2 स्निपेट्स (snippets) का उपयोग करते हैं:

// ** keep track of a variable from the contract in the local React state:
// ** लॉकल रिएक्ट स्टेट (local React state) में कॉन्ट्रैक्ट से एक वेरिएबल का ट्रैक रखें:
  const claimPeriodLeft = useContractReader(readContracts, "Staker", "claimPeriodLeft");
  console.log("⏳ Claim Period Left:", claimPeriodLeft);

  const withdrawalTimeLeft = useContractReader(readContracts, "Staker", "withdrawalTimeLeft");
  console.log("⏳ Withdrawal Time Left:", withdrawalTimeLeft);
<div style={{ padding: 8, marginTop: 16, fontWeight: "bold" }}>
  <div>Claim Period Left:</div>
  {claimPeriodLeft && humanizeDuration(claimPeriodLeft.toNumber() * 1000)}
</div>

<div style={{ padding: 8, marginTop: 16, fontWeight: "bold"}}>
  <div>Withdrawal Period Left:</div>
  {withdrawalTimeLeft && humanizeDuration(withdrawalTimeLeft.toNumber() * 1000)}
</div>

जबकि दूसरा कोड स्निपेट परिचित है और जैसा हमने पहले ही देखा है उस से मेल खाता है, कोड का पहला ब्लॉक थोड़ा अनूठा है। अब हम संग्रहीत वेरिएबल वैल्यूज (stored variable values) तक पहुंचने के लिए claimPeriodLeft और withdrawalTimeLeft कॉल करते हैं।

हालाँकि, हमें वास्तव में पहले स्मार्ट कॉन्ट्रैक्ट से स्वयं मूल्यों को पढ़ना चाहिए। पहला कोड स्निपेट इस तर्क (logic) को संभालता है!

अन्य मौजूदा यूआई कंपोनेंट्स में विविध परिवर्तन (Misc. Edits to Other Existing UI components)

अब जब आपने फ़्रंटएंड यूआई कंपोनेंट्स के 2 अलग-अलग उदाहरण देखे हैं, तो क्या आप समझ सकते हैं कि फ़्रंटएंड में शेष परिवर्तन कैसे करें ताकि यह ऊपर दिए गए नमूने जैसा दिखाई दे!

आपको यहां केवल कुछ मापदंडों (parameters) को समायोजित (adjust) करने की आवश्यकता है, इसलिए बहुत अधिक न सोचें!

यूआई पर फ्रीस्टाइलिंग

जबकि स्कैफोल्ड-इथ में बहुत सारे डिफ़ॉल्ट कंपोनेंट्स हैं जो उपयोगकर्ताओं को केवल लो-कोड (low-code) समाधानों का लाभ उठाने के लिए सशक्त बनाते हैं, यह उपयोगकर्ताओं को बड़े फ्रंटएंड लाइब्रेरी तक भी पहुँच प्रदान करता है।

डिफ़ॉल्ट से, इसमें एंट डिजाइन रिएक्ट कंपोनेंट्स (Ant Design react components)(https://ant.design/components/overview/) में एक हुक होता है जो किसी को भी वहां से कंपोनेंट्स को प्राप्त करने की अनुमति देता है!

हमारे सैंपल फ्रेंटएन्ड में, हम वास्तव में विजुअल कंपोनेंट्स प्रत्येक बड़े ब्लॉक को विभाजित करने वाली "लाइनें" देखते हैं।

एंट डिज़ाइन (Ant Design) में उपलब्ध विभिन्न विकल्पों को इस्तेमाल करके इन “लाइन्स” को फिर से बनाएँ

📘

यदि आप कोई संकेत चाहते हैं, तो एंट डिवाइडर (Ant Dividers) पर एक नज़र डालें!

यह न भूलें कि हमें उस कंपोनेंट को import करने की आवश्यकता है जिसे हम अपने App.jsx फ़ाइल के शीर्ष पर उपयोग करने की योजना बना रहे हैं!

import { Alert, Button, Col, Menu, Row, List, Divider } from "antd";

यदि आपने फ्रंटएंड कोड को हमारे साथ-साथ लिखा है, तो आपका App.jsx निम्न जैसा दिखना चाहिए:

import WalletConnectProvider from "@walletconnect/web3-provider";
//import Torus from "@toruslabs/torus-embed"
import WalletLink from "walletlink";
import { Alert, Button, Col, Menu, Row, List, Divider } from "antd";
import "antd/dist/antd.css";
import React, { useCallback, useEffect, useState } from "react";
import { BrowserRouter, Link, Route, Switch } from "react-router-dom";
import Web3Modal from "web3modal";
import "./App.css";
import { Account, Address, Balance, Contract, Faucet, GasGauge, Header, Ramp, ThemeSwitch } from "./components";
import { INFURA_ID, NETWORK, NETWORKS } from "./constants";
import { Transactor } from "./helpers";
import {
  useBalance,
  useContractLoader,
  useContractReader,
  useGasPrice,
  useOnBlock,
  useUserProviderAndSigner,
} from "eth-hooks";
import { useEventListener } from "eth-hooks/events/useEventListener";
import { useExchangeEthPrice } from "eth-hooks/dapps/dex";
// import Hints from "./Hints";
import { ExampleUI, Hints, Subgraph } from "./views";

import { useContractConfig } from "./hooks";
import Portis from "@portis/web3";
import Fortmatic from "fortmatic";
import Authereum from "authereum";
import humanizeDuration from "humanize-duration";

const { ethers } = require("ethers");
/*
    Welcome to 🏗 scaffold-eth !

    Code:
    https://github.com/austintgriffith/scaffold-eth

    Support:
    https://t.me/joinchat/KByvmRe5wkR-8F_zz6AjpA
    or DM @austingriffith on Twitter or Telegram

    You should get your own Infura.io ID and put it in `constants.js`
    (this is your connection to the main Ethereum network for ENS etc.)


    🌏 EXTERNAL CONTRACTS:
    You can also bring in contract artifacts in `constants.js`
    (and then use the `useExternalContractLoader()` hook!)
*/

/// 📡 What chain are your contracts deployed to?
const targetNetwork = NETWORKS.localhost; // <------- select your target frontend network (localhost, rinkeby, xdai, mainnet)

// 😬 Sorry for all the console logging
const DEBUG = true;
const NETWORKCHECK = true;

// 🛰 providers
if (DEBUG) console.log("📡 Connecting to Mainnet Ethereum");
// const mainnetProvider = getDefaultProvider("mainnet", { infura: INFURA_ID, etherscan: ETHERSCAN_KEY, quorum: 1 });
// const mainnetProvider = new InfuraProvider("mainnet",INFURA_ID);
//
// attempt to connect to our own scaffold eth rpc and if that fails fall back to infura...
// Using StaticJsonRpcProvider as the chainId won't change see https://github.com/ethers-io/ethers.js/issues/901
const scaffoldEthProvider = navigator.onLine
  ? new ethers.providers.StaticJsonRpcProvider("https://rpc.scaffoldeth.io:48544")
  : null;
const poktMainnetProvider = navigator.onLine
  ? new ethers.providers.StaticJsonRpcProvider(
      "https://eth-mainnet.gateway.pokt.network/v1/lb/611156b4a585a20035148406",
    )
  : null;
const mainnetInfura = navigator.onLine
  ? new ethers.providers.StaticJsonRpcProvider("https://mainnet.infura.io/v3/" + INFURA_ID)
  : null;
// ( ⚠️ Getting "failed to meet quorum" errors? Check your INFURA_ID

// 🏠 Your local provider is usually pointed at your local blockchain
const localProviderUrl = targetNetwork.rpcUrl;
// as you deploy to other networks you can set REACT_APP_PROVIDER=https://dai.poa.network in packages/react-app/.env
const localProviderUrlFromEnv = process.env.REACT_APP_PROVIDER ? process.env.REACT_APP_PROVIDER : localProviderUrl;
if (DEBUG) console.log("🏠 Connecting to provider:", localProviderUrlFromEnv);
const localProvider = new ethers.providers.StaticJsonRpcProvider(localProviderUrlFromEnv);

// 🔭 block explorer URL
const blockExplorer = targetNetwork.blockExplorer;

// Coinbase walletLink init
const walletLink = new WalletLink({
  appName: "coinbase",
});

// WalletLink provider
const walletLinkProvider = walletLink.makeWeb3Provider(`https://mainnet.infura.io/v3/${INFURA_ID}`, 1);

// Portis ID: 6255fb2b-58c8-433b-a2c9-62098c05ddc9
/*
  Web3 modal helps us "connect" external wallets:
*/
const web3Modal = new Web3Modal({
  network: "mainnet", // Optional. If using WalletConnect on xDai, change network to "xdai" and add RPC info below for xDai chain.
  cacheProvider: true, // optional
  theme: "light", // optional. Change to "dark" for a dark theme.
  providerOptions: {
    walletconnect: {
      package: WalletConnectProvider, // required
      options: {
        bridge: "https://polygon.bridge.walletconnect.org",
        infuraId: INFURA_ID,
        rpc: {
          1: `https://mainnet.infura.io/v3/${INFURA_ID}`, // mainnet // For more WalletConnect providers: https://docs.walletconnect.org/quick-start/dapps/web3-provider#required
          42: `https://kovan.infura.io/v3/${INFURA_ID}`,
          100: "https://dai.poa.network", // xDai
        },
      },
    },
    portis: {
      display: {
        logo: "https://user-images.githubusercontent.com/9419140/128913641-d025bc0c-e059-42de-a57b-422f196867ce.png",
        name: "Portis",
        description: "Connect to Portis App",
      },
      package: Portis,
      options: {
        id: "6255fb2b-58c8-433b-a2c9-62098c05ddc9",
      },
    },
    fortmatic: {
      package: Fortmatic, // required
      options: {
        key: "pk_live_5A7C91B2FC585A17", // required
      },
    },
    // torus: {
    //   package: Torus,
    //   options: {
    //     networkParams: {
    //       host: "https://localhost:8545", // optional
    //       chainId: 1337, // optional
    //       networkId: 1337 // optional
    //     },
    //     config: {
    //       buildEnv: "development" // optional
    //     },
    //   },
    // },
    "custom-walletlink": {
      display: {
        logo: "https://play-lh.googleusercontent.com/PjoJoG27miSglVBXoXrxBSLveV6e3EeBPpNY55aiUUBM9Q1RCETKCOqdOkX2ZydqVf0",
        name: "Coinbase",
        description: "Connect to Coinbase Wallet (not Coinbase App)",
      },
      package: walletLinkProvider,
      connector: async (provider, _options) => {
        await provider.enable();
        return provider;
      },
    },
    authereum: {
      package: Authereum, // required
    },
  },
});

function App(props) {
  const mainnetProvider =
    poktMainnetProvider && poktMainnetProvider._isProvider
      ? poktMainnetProvider
      : scaffoldEthProvider && scaffoldEthProvider._network
      ? scaffoldEthProvider
      : mainnetInfura;

  const [injectedProvider, setInjectedProvider] = useState();
  const [address, setAddress] = useState();

  const logoutOfWeb3Modal = async () => {
    await web3Modal.clearCachedProvider();
    if (injectedProvider && injectedProvider.provider && typeof injectedProvider.provider.disconnect == "function") {
      await injectedProvider.provider.disconnect();
    }
    setTimeout(() => {
      window.location.reload();
    }, 1);
  };

  /* 💵 This hook will get the price of ETH from 🦄 Uniswap: */
  const price = useExchangeEthPrice(targetNetwork, mainnetProvider);

  /* 🔥 This hook will get the price of Gas from ⛽️ EtherGasStation */
  const gasPrice = useGasPrice(targetNetwork, "fast");
  // Use your injected provider from 🦊 Metamask or if you don't have it then instantly generate a 🔥 burner wallet.
  const userProviderAndSigner = useUserProviderAndSigner(injectedProvider, localProvider);
  const userSigner = userProviderAndSigner.signer;

  useEffect(() => {
    async function getAddress() {
      if (userSigner) {
        const newAddress = await userSigner.getAddress();
        setAddress(newAddress);
      }
    }
    getAddress();
  }, [userSigner]);

  // You can warn the user if you would like them to be on a specific network
  const localChainId = localProvider && localProvider._network && localProvider._network.chainId;
  const selectedChainId =
    userSigner && userSigner.provider && userSigner.provider._network && userSigner.provider._network.chainId;

  // For more hooks, check out 🔗eth-hooks at: https://www.npmjs.com/package/eth-hooks

  // The transactor wraps transactions and provides notificiations
  const tx = Transactor(userSigner, gasPrice);

  // Faucet Tx can be used to send funds from the faucet
  const faucetTx = Transactor(localProvider, gasPrice);

  // 🏗 scaffold-eth is full of handy hooks like this one to get your balance:
  const yourLocalBalance = useBalance(localProvider, address);

  // Just plug in different 🛰 providers to get your balance on different chains:
  const yourMainnetBalance = useBalance(mainnetProvider, address);

  const contractConfig = useContractConfig();

  // Load in your local 📝 contract and read a value from it:
  const readContracts = useContractLoader(localProvider, contractConfig);

  // If you want to make 🔐 write transactions to your contracts, use the userSigner:
  const writeContracts = useContractLoader(userSigner, contractConfig, localChainId);

  // EXTERNAL CONTRACT EXAMPLE:
  //
  // If you want to bring in the mainnet DAI contract it would look like:
  const mainnetContracts = useContractLoader(mainnetProvider, contractConfig);

  // If you want to call a function on a new block
  useOnBlock(mainnetProvider, () => {
    console.log(`⛓ A new mainnet block is here: ${mainnetProvider._lastBlockNumber}`);
  });

  // Then read your DAI balance like:
  const myMainnetDAIBalance = useContractReader(mainnetContracts, "DAI", "balanceOf", [
    "0x34aA3F359A9D614239015126635CE7732c18fDF3",
  ]);

  //keep track of contract balance to know how much has been staked total:
  const stakerContractBalance = useBalance(
    localProvider,
    readContracts && readContracts.Staker ? readContracts.Staker.address : null,
  );
  if (DEBUG) console.log("💵 stakerContractBalance", stakerContractBalance);

  const rewardRatePerSecond = useContractReader(readContracts, "Staker", "rewardRatePerSecond");
  console.log("💵 Reward Rate:", rewardRatePerSecond);

  // ** keep track of a variable from the contract in the local React state:
  const balanceStaked = useContractReader(readContracts, "Staker", "balances", [address]);
  console.log("💸 balanceStaked:", balanceStaked);

  // ** 📟 Listen for broadcast events
  const stakeEvents = useEventListener(readContracts, "Staker", "Stake", localProvider, 1);
  console.log("📟 stake events:", stakeEvents);

  const receiveEvents = useEventListener(readContracts, "Staker", "Received", localProvider, 1);
  console.log("📟 receive events:", receiveEvents);

  // ** keep track of a variable from the contract in the local React state:
  const claimPeriodLeft = useContractReader(readContracts, "Staker", "claimPeriodLeft");
  console.log("⏳ Claim Period Left:", claimPeriodLeft);

  const withdrawalTimeLeft = useContractReader(readContracts, "Staker", "withdrawalTimeLeft");
  console.log("⏳ Withdrawal Time Left:", withdrawalTimeLeft);


  // ** Listen for when the contract has been 'completed'
  const complete = useContractReader(readContracts, "ExampleExternalContract", "completed");
  console.log("✅ complete:", complete);

  const exampleExternalContractBalance = useBalance(
    localProvider,
    readContracts && readContracts.ExampleExternalContract ? readContracts.ExampleExternalContract.address : null,
  );
  if (DEBUG) console.log("💵 exampleExternalContractBalance", exampleExternalContractBalance);

  let completeDisplay = "";
  if (complete) {
    completeDisplay = (
      <div style={{padding: 64, backgroundColor: "#eeffef", fontWeight: "bold", color: "rgba(0, 0, 0, 0.85)" }} >
        -- 💀 Staking App Fund Repatriation Executed 🪦 --
        <Balance balance={exampleExternalContractBalance} fontSize={32} /> ETH locked!
      </div>
    );
  }

  /*
  const addressFromENS = useResolveName(mainnetProvider, "austingriffith.eth");
  console.log("🏷 Resolved austingriffith.eth as:", addressFromENS)
  */

  //
  // 🧫 DEBUG 👨🏻‍🔬
  //
  useEffect(() => {
    if (
      DEBUG &&
      mainnetProvider &&
      address &&
      selectedChainId &&
      yourLocalBalance &&
      yourMainnetBalance &&
      readContracts &&
      writeContracts &&
      mainnetContracts
    ) {
      console.log("_____________________________________ 🏗 scaffold-eth _____________________________________");
      console.log("🌎 mainnetProvider", mainnetProvider);
      console.log("🏠 localChainId", localChainId);
      console.log("👩‍💼 selected address:", address);
      console.log("🕵🏻‍♂️ selectedChainId:", selectedChainId);
      console.log("💵 yourLocalBalance", yourLocalBalance ? ethers.utils.formatEther(yourLocalBalance) : "...");
      console.log("💵 yourMainnetBalance", yourMainnetBalance ? ethers.utils.formatEther(yourMainnetBalance) : "...");
      console.log("📝 readContracts", readContracts);
      console.log("🌍 DAI contract on mainnet:", mainnetContracts);
      console.log("💵 yourMainnetDAIBalance", myMainnetDAIBalance);
      console.log("🔐 writeContracts", writeContracts);
    }
  }, [
    mainnetProvider,
    address,
    selectedChainId,
    yourLocalBalance,
    yourMainnetBalance,
    readContracts,
    writeContracts,
    mainnetContracts,
  ]);

  let networkDisplay = "";
  if (NETWORKCHECK && localChainId && selectedChainId && localChainId !== selectedChainId) {
    const networkSelected = NETWORK(selectedChainId);
    const networkLocal = NETWORK(localChainId);
    if (selectedChainId === 1337 && localChainId === 31337) {
      networkDisplay = (
        <div style={{ zIndex: 2, position: "absolute", right: 0, top: 60, padding: 16 }}>
          <Alert
            message="⚠️ Wrong Network ID"
            description={
              <div>
                You have <b>chain id 1337</b> for localhost and you need to change it to <b>31337</b> to work with
                HardHat.
                <div>(MetaMask -&gt; Settings -&gt; Networks -&gt; Chain ID -&gt; 31337)</div>
              </div>
            }
            type="error"
            closable={false}
          />
        </div>
      );
    } else {
      networkDisplay = (
        <div style={{ zIndex: 2, position: "absolute", right: 0, top: 60, padding: 16 }}>
          <Alert
            message="⚠️ Wrong Network"
            description={
              <div>
                You have <b>{networkSelected && networkSelected.name}</b> selected and you need to be on{" "}
                <Button
                  onClick={async () => {
                    const ethereum = window.ethereum;
                    const data = [
                      {
                        chainId: "0x" + targetNetwork.chainId.toString(16),
                        chainName: targetNetwork.name,
                        nativeCurrency: targetNetwork.nativeCurrency,
                        rpcUrls: [targetNetwork.rpcUrl],
                        blockExplorerUrls: [targetNetwork.blockExplorer],
                      },
                    ];
                    console.log("data", data);

                    let switchTx;
                    // https://docs.metamask.io/guide/rpc-api.html#other-rpc-methods
                    try {
                      switchTx = await ethereum.request({
                        method: "wallet_switchEthereumChain",
                        params: [{ chainId: data[0].chainId }],
                      });
                    } catch (switchError) {
                      // not checking specific error code, because maybe we're not using MetaMask
                      try {
                        switchTx = await ethereum.request({
                          method: "wallet_addEthereumChain",
                          params: data,
                        });
                      } catch (addError) {
                        // handle "add" error
                      }
                    }

                    if (switchTx) {
                      console.log(switchTx);
                    }
                  }}
                >
                  <b>{networkLocal && networkLocal.name}</b>
                </Button>
              </div>
            }
            type="error"
            closable={false}
          />
        </div>
      );
    }
  } else {
    networkDisplay = (
      <div style={{ zIndex: -1, position: "absolute", right: 154, top: 28, padding: 16, color: targetNetwork.color }}>
        {targetNetwork.name}
      </div>
    );
  }

  const loadWeb3Modal = useCallback(async () => {
    const provider = await web3Modal.connect();
    setInjectedProvider(new ethers.providers.Web3Provider(provider));

    provider.on("chainChanged", chainId => {
      console.log(`chain changed to ${chainId}! updating providers`);
      setInjectedProvider(new ethers.providers.Web3Provider(provider));
    });

    provider.on("accountsChanged", () => {
      console.log(`account changed!`);
      setInjectedProvider(new ethers.providers.Web3Provider(provider));
    });

    // Subscribe to session disconnection
    provider.on("disconnect", (code, reason) => {
      console.log(code, reason);
      logoutOfWeb3Modal();
    });
  }, [setInjectedProvider]);

  useEffect(() => {
    if (web3Modal.cachedProvider) {
      loadWeb3Modal();
    }
  }, [loadWeb3Modal]);

  const [route, setRoute] = useState();
  useEffect(() => {
    setRoute(window.location.pathname);
  }, [setRoute]);

  let faucetHint = "";
  const faucetAvailable = localProvider && localProvider.connection && targetNetwork.name.indexOf("local") !== -1;

  const [faucetClicked, setFaucetClicked] = useState(false);
  if (
    !faucetClicked &&
    localProvider &&
    localProvider._network &&
    localProvider._network.chainId === 31337 &&
    yourLocalBalance &&
    ethers.utils.formatEther(yourLocalBalance) <= 0
  ) {
    faucetHint = (
      <div style={{ padding: 16 }}>
        <Button
          type="primary"
          onClick={() => {
            faucetTx({
              to: address,
              value: ethers.utils.parseEther("0.01"),
            });
            setFaucetClicked(true);
          }}
        >
          💰 Grab funds from the faucet ⛽️
        </Button>
      </div>
    );
  }

  return (
    <div className="App">
      {/* ✏️ Edit the header and change the title to your project name */}
      <Header />
      {networkDisplay}
      <BrowserRouter>
        <Menu style={{ textAlign: "center" }} selectedKeys={[route]} mode="horizontal">
          <Menu.Item key="/">
            <Link
              onClick={() => {
                setRoute("/");
              }}
              to="/"
            >
              Staker UI
            </Link>
          </Menu.Item>
          <Menu.Item key="/contracts">
            <Link
              onClick={() => {
                setRoute("/contracts");
              }}
              to="/contracts"
            >
              Debug Contracts
            </Link>
          </Menu.Item>
        </Menu>

        <Switch>
          <Route exact path="/">
            {completeDisplay}

            <div style={{ padding: 8, marginTop: 16 }}>
              <div>Staker Contract:</div>
              <Address value={readContracts && readContracts.Staker && readContracts.Staker.address} />
            </div>

            <Divider />

            <div style={{ padding: 8, marginTop: 16 }}>
              <div>Reward Rate Per Second:</div>
              <Balance balance={rewardRatePerSecond} fontSize={64} /> ETH
            </div>

            <Divider />

            <div style={{ padding: 8, marginTop: 16, fontWeight: "bold" }}>
              <div>Claim Period Left:</div>
              {claimPeriodLeft && humanizeDuration(claimPeriodLeft.toNumber() * 1000)}
            </div>

            <div style={{ padding: 8, marginTop: 16, fontWeight: "bold"}}>
              <div>Withdrawal Period Left:</div>
              {withdrawalTimeLeft && humanizeDuration(withdrawalTimeLeft.toNumber() * 1000)}
            </div>

            <Divider />

            <div style={{ padding: 8, fontWeight: "bold"}}>
              <div>Total Available ETH in Contract:</div>
              <Balance balance={stakerContractBalance} fontSize={64} />
            </div>

            <Divider />

            <div style={{ padding: 8,fontWeight: "bold" }}>
              <div>ETH Locked 🔒 in Staker Contract:</div>
              <Balance balance={balanceStaked} fontSize={64} />
            </div>

            <div style={{ padding: 8 }}>
              <Button
                type={"default"}
                onClick={() => {
                  tx(writeContracts.Staker.execute());
                }}
              >
                📡 Execute!
              </Button>
            </div>

            <div style={{ padding: 8 }}>
              <Button
                type={"default"}
                onClick={() => {
                  tx(writeContracts.Staker.withdraw());
                }}
              >
                🏧 Withdraw
              </Button>
            </div>

            <div style={{ padding: 8 }}>
              <Button
                type={balanceStaked ? "success" : "primary"}
                onClick={() => {
                  tx(writeContracts.Staker.stake({ value: ethers.utils.parseEther("0.5") }));
                }}
              >
                🥩 Stake 0.5 ether!
              </Button>
            </div>

            {/*
                🎛 this scaffolding is full of commonly used components
                this <Contract/> component will automatically parse your ABI
                and give you a form to interact with it locally
            */}

            {/* uncomment for a second contract:
            <Contract
              name="SecondContract"
              signer={userProvider.getSigner()}
              provider={localProvider}
              address={address}
              blockExplorer={blockExplorer}
              contractConfig={contractConfig}
            />
            */}
          </Route>
          <Route path="/contracts">
            <Contract
              name="Staker"
              signer={userSigner}
              provider={localProvider}
              address={address}
              blockExplorer={blockExplorer}
              contractConfig={contractConfig}
            />
            <Contract
              name="ExampleExternalContract"
              signer={userSigner}
              provider={localProvider}
              address={address}
              blockExplorer={blockExplorer}
              contractConfig={contractConfig}
            />
          </Route>
        </Switch>
      </BrowserRouter>

      <ThemeSwitch />

      {/* 👨‍💼 Your account is in the top right with a wallet at connect options */}
      <div style={{ position: "fixed", textAlign: "right", right: 0, top: 0, padding: 10 }}>
        <Account
          address={address}
          localProvider={localProvider}
          userSigner={userSigner}
          mainnetProvider={mainnetProvider}
          price={price}
          web3Modal={web3Modal}
          loadWeb3Modal={loadWeb3Modal}
          logoutOfWeb3Modal={logoutOfWeb3Modal}
          blockExplorer={blockExplorer}
        />
        {faucetHint}
      </div>

      <div style={{ marginTop: 32, opacity: 0.5 }}>
        {/* Add your address here */}
        Created by <Address value={"Your...address"} ensProvider={mainnetProvider} fontSize={16} />
      </div>

      <div style={{ marginTop: 32, opacity: 0.5 }}>
        <a target="_blank" style={{ padding: 32, color: "#000" }} href="https://github.com/scaffold-eth/scaffold-eth">
          🍴 Fork me!
        </a>
      </div>

      {/* 🗺 Extra UI like gas price, eth price, faucet, and support: */}
      <div style={{ position: "fixed", textAlign: "left", left: 0, bottom: 20, padding: 10 }}>
        <Row align="middle" gutter={[4, 4]}>
          <Col span={8}>
            <Ramp price={price} address={address} networks={NETWORKS} />
          </Col>

          <Col span={8} style={{ textAlign: "center", opacity: 0.8 }}>
            <GasGauge gasPrice={gasPrice} />
          </Col>
          <Col span={8} style={{ textAlign: "center", opacity: 1 }}>
            <Button
              onClick={() => {
                window.open("https://t.me/joinchat/KByvmRe5wkR-8F_zz6AjpA");
              }}
              size="large"
              shape="round"
            >
              <span style={{ marginRight: 8 }} role="img" aria-label="support">
                💬
              </span>
              Support
            </Button>
          </Col>
        </Row>

        <Row align="middle" gutter={[4, 4]}>
          <Col span={24}>
            {
              /*  if the local provider has a signer, let's show the faucet:  */
              faucetAvailable ? (
                <Faucet localProvider={localProvider} price={price} ensProvider={mainnetProvider} />
              ) : (
                ""
              )
            }
          </Col>
        </Row>
      </div>
    </div>
  );
}

export default App;

बहुत बढ़िया!

हमने डेवलपर एन्वाइरन्मेंट्स, सॉलिडिटी, और फ्रंटएंड कोड दोनों के संदर्भ में बहुत सारे नए कंपोनेंट्स पर एक साथ काम किया है।

सत्यापित करें कि आपका डीऐप उम्मीद के मुताबिक काम करता है!

  1. क्या डीऐप में सिंगल-यूज स्टेकिंग की सुविधा है?
  2. क्या निकासी/निधि रिपेट्रिएशन (withdrawal/fund repatriation) शर्तों का पालन किया जाता है? आगे बढ़ें और yarn deploy --reset का कई बार इस्तेमाल करें समय की प्रत्येक विंडो को जांचने के लिए।

चुनौती का समय!

ठीक है, अब सबसे अच्छे हिस्से का समय है। यह देखने के लिए कि आपने यहां क्या सीखा है, क्या आप इसे पूरी तरह से समझते हैं या नहीं, यह देखने के लिए हम आपको कुछ चुनौतियों के साथ छोड़ने जा रहे हैं!

  1. Staker.sol कॉन्ट्रैक्ट में ब्याज तंत्र (interest mechanism) को अपडेट करें ताकि आपको जमा और निकासी के बीच के ब्लॉक के आधार पर ETH की "नॉन-लीनियर"(non-linear) राशि प्राप्त हो

📘

हम एक बुनियादी एक्सपोनेंशियल फंक्शन को लागू करने का सुझाव देते हैं !

  1. उपयोगकर्ताओं को ETH की किसी भी मनमानी राशि को स्मार्ट कॉन्ट्रैक्ट में जमा करने की अनुमति दें, न कि केवल 0.5 ETH ।
  2. वेनिला exampleExternalContract कॉन्ट्रैक्ट का उपयोग करने के बजाय, Staker.sol में एक फ़ंक्शन लागू करें जो आपको exampleExternalContract में लॉक किए गए ETH को पुनः प्राप्त करने और इसे वापस Staker कॉन्ट्रैक्ट में जमा करने की अनुमति देता है।
    • केवल एक पते को वाइट-लिस्ट (”white-list”) करके इस नए फ़ंक्शन को कॉल करे और यह सुनिश्चित करे की इसका उपयोग गेटेड (gated) हो !
    • सुनिश्चित करें कि आप तर्क बनाते हैं/मौजूदा कोड हटाते हैं ताकि यह सुनिश्चित हो सके कि उपयोगकर्ता बार-बार Staker कॉन्ट्रैक्ट के साथ बातचीत करने में सक्षम हैं! हम Staker --> ExampleExternalContract से बार-बार पिंग-पोंग करने में सक्षम होना चाहते हैं!

एक बार जब आप चुनौती पूरी कर लेते हैं, तो ट्विटर पर @AlchemyPlatform और लेखक @crypt0zeke को हैशटैग #roadtoweb3 का उपयोग करके टैग करके इसके बारे में ट्वीट करें!

इसके अलावा, निचे दिए गए फॉर्म को सबमिट करके प्रूफ ऑफ नॉलेज एनएफटी (Proof of Knowledge NFT) के लिए अपनी चुनौती को रिडीम करें: https://forms.gle/uYhMS3brabNg3RVs7

शांति और प्यार। हैप्पी बिल्डिंग! 🏗️🚀


ReadMe