<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: dhxmo</title>
    <description>The latest articles on DEV Community by dhxmo (@zdhxmo).</description>
    <link>https://dev.to/zdhxmo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F876264%2F06ff6f1a-4847-43bf-94c6-a5cede1081f9.png</url>
      <title>DEV Community: dhxmo</title>
      <link>https://dev.to/zdhxmo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zdhxmo"/>
    <language>en</language>
    <item>
      <title>AWS Scheduled Scrape using Python</title>
      <dc:creator>dhxmo</dc:creator>
      <pubDate>Fri, 26 Aug 2022 09:49:00 +0000</pubDate>
      <link>https://dev.to/zdhxmo/automated-randomized-news-reader-2231</link>
      <guid>https://dev.to/zdhxmo/automated-randomized-news-reader-2231</guid>
      <description>&lt;p&gt;I hate reading the news. What I hate more than anything is opening a news site and being overwhelmed by all the stories, so I decided to build a scraper to send me a couple top news items everyday&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/zdhxmo/toolbox/tree/main/newsScraper"&gt;Github repo for the project is here&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First thing I did was learn about cron jobs&lt;/p&gt;

&lt;p&gt;so for now I set up a cronjob that'll log every 1 minute. just so I can debug the program&lt;/p&gt;

&lt;p&gt;here's the command for that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*/1 * * * * /home/dhruv/bin/python ~/Desktop/projects/newsScraper/scraper.py &amp;gt;&amp;gt; ~/Desktop/projects/newsScraper/cron.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the scheduler is a little complex to understand but gotta figure it out to use this tool. &lt;/p&gt;

&lt;p&gt;next up is to set up a scraper and output a text file of the top stories.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I decided I'll pick all the subsections of markets and get all their top neww.&lt;/p&gt;

&lt;p&gt;for now the output gives me the link to the news and news title&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import requests, random
from bs4 import BeautifulSoup  # web scraping

content = ''
urls_dict = {
    'telecom': 'https://economictimes.indiatimes.com/industry/telecom',
    'transport': 'https://economictimes.indiatimes.com/industry/transportation',
    'services': 'https://economictimes.indiatimes.com/industry/services',
    'biotech': 'https://economictimes.indiatimes.com/industry/healthcare/biotech',
    'svs': 'https://economictimes.indiatimes.com/industry/indl-goods/svs',
    'energy': 'https://economictimes.indiatimes.com/industry/energy',
    'consumer_products': 'https://economictimes.indiatimes.com/industry/cons-products',
    'finance': 'https://economictimes.indiatimes.com/industry/banking/finance',
    'automobiles': 'https://economictimes.indiatimes.com/industry/auto'
}

todays_url = random.choice(list(urls_dict.values()))
response = requests.get(todays_url)
content = response.content
soup = BeautifulSoup(content, 'html.parser')
headline_data = soup.find("ul", class_="list1")
url = 'https://economictimes.indiatimes.com'
for i, news in enumerate(headline_data.find_all("li")):
    link = '%s%s' % (url, news.a.get('href'))
    print(i+1, link, news.text, end=" \n")

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;lets prettify this so I feel like a pro&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import random
import requests
from bs4 import BeautifulSoup

# email content placeholder
content = ''

urls_dict = {
    'telecom': 'https://economictimes.indiatimes.com/industry/telecom',
    'transport': 'https://economictimes.indiatimes.com/industry/transportation',
    'services': 'https://economictimes.indiatimes.com/industry/services',
    'biotech': 'https://economictimes.indiatimes.com/industry/healthcare/biotech',
    'svs': 'https://economictimes.indiatimes.com/industry/indl-goods/svs',
    'energy': 'https://economictimes.indiatimes.com/industry/energy',
    'consumer_products': 'https://economictimes.indiatimes.com/industry/cons-products',
    'finance': 'https://economictimes.indiatimes.com/industry/banking/finance',
    'automobiles': 'https://economictimes.indiatimes.com/industry/auto'
}


def extract_news():
    todays_url = random.choice(list(urls_dict.values()))
    response = requests.get(todays_url)
    content = response.content
    soup = BeautifulSoup(content, 'html.parser')
    headline_data = soup.find("ul", class_="list1")

    email_body = ''

    url = 'https://economictimes.indiatimes.com'
    for i, news in enumerate(headline_data.find_all("li")):
        link = '%s%s' % (url, news.a.get('href'))
        email_body += str(i + 1) + '. ' + '&amp;lt;a href="' + link + '"&amp;gt;' + news.text + '&amp;lt;/a&amp;gt;' + '\n\n\n' + '&amp;lt;br /&amp;gt;'

    return email_body
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I want to introduce some more randomness to what I read. So I decided to add these things I receive into a list and shuffle it up and then send me only 5 of the news items:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def extract_news():
    todays_url = random.choice(list(urls_dict.values()))
    response = requests.get(todays_url)
    content = response.content
    soup = BeautifulSoup(content, 'html.parser')
    headline_data = soup.find("ul", class_="list1")

    email_body = ''

    email_body += 'Good Morning kiddo. Today we read Economics Times. Heres whats happening today: &amp;lt;br /&amp;gt;\n &amp;lt;br /&amp;gt;\n'

    all_news = []

    url = 'https://economictimes.indiatimes.com'
    for i, news in enumerate(headline_data.find_all("li")):
        body = ''
        link = '%s%s' % (url, news.a.get('href'))
        body += '&amp;lt;a href="' + link + '"&amp;gt;' \
                + news.text + '&amp;lt;/a&amp;gt;' + '&amp;lt;br /&amp;gt;\n' + '&amp;lt;br /&amp;gt;\n'
        # add items to a list
        all_news.append(body)

    # shuffle the list
    random.shuffle(all_news)

    n = 5
    # iterate over the first 5 elements of the randomized list
    for i in itertools.islice(all_news, n):
        email_body += '- ' + i

    email_body += '&amp;lt;br&amp;gt;---------------------------------&amp;lt;br&amp;gt;'
    email_body += '&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;Thats all for today. Byeeee'

    return email_body
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;now it's time to send SMTP mails&lt;/p&gt;

&lt;p&gt;Google has changed it's policy so I went here and reconfigured my account settings&lt;/p&gt;

&lt;p&gt;&lt;a href="https://support.google.com/accounts/answer/6010255?hl=en-GB&amp;amp;visit_id=637970887869842501-2539226343&amp;amp;p=less-secure-apps&amp;amp;rd=1"&gt;https://support.google.com/accounts/answer/6010255?hl=en-GB&amp;amp;visit_id=637970887869842501-2539226343&amp;amp;p=less-secure-apps&amp;amp;rd=1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;after that's done, I stored my password in a .env file and ---&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import os
from dotenv import load_dotenv
import smtplib

# email body
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
# system date and time manipulation
import datetime

now = datetime.datetime.now()
load_dotenv()


def send_mail(news_body):
    SERVER = 'smtp.gmail.com'
    PORT = 587
    FROM = 'homesanct@gmail.com'
    TO = 'dhrvmohapatra@gmail.com'
    PASSWORD = os.getenv('password')

    msg = MIMEMultipart()
    msg['Subject'] = 'Good Morning Champ' + ' ' + str(now.day) + '-' + str(now.month) + '-' + str(
        now.year)
    msg['From'] = FROM
    msg['To'] = TO
    msg.attach(MIMEText(news_body, 'html'))

    print('initializing server')

    server = smtplib.SMTP(SERVER, PORT)
    server.set_debuglevel(1)
    server.ehlo()
    server.starttls()
    server.login(FROM, PASSWORD)
    server.sendmail(FROM, TO, msg.as_string())

    print('Email Sent...')

    server.quit()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I finished up with the classic pypy&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if __name__ == "__main__":
    data = extract_news()
    send_mail(data)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 6:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;last but not the least I had to set up the proper cronjob&lt;/p&gt;

&lt;p&gt;I changed up the project location so things changed a little, but now I'll get a random sample of news from Economics Times at 6:55 am on Monday and Thursday!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;55 6 * * 1,4 /home/dhruv/Desktop/projects/toolbox/newsScraper/venv/bin/python ~/Desktop/projects/toolbox/newsScraper/newsReader01.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also wrote scripts for Times Of India and Reuters, but that would be redundant to add here.&lt;/p&gt;

&lt;p&gt;Now comes the little complex parts. I don't want to keep my laptop on everyday just so I can get a ruddy email, so I decided to send this script to the cloud.&lt;/p&gt;

&lt;p&gt;After a bit of research, I found that AWS Lambda is the most efficient solution to execute this, so I spent a while understanding it.&lt;/p&gt;

&lt;p&gt;next, it came time to upload the script..&lt;/p&gt;

&lt;p&gt;and everything crashed&lt;/p&gt;

&lt;p&gt;like a 100 times&lt;/p&gt;

&lt;p&gt;before I finally was able to figure it out. anyways,&lt;/p&gt;

&lt;p&gt;here's the steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First sign into AWS &lt;/p&gt;

&lt;p&gt;then open up Lambda&lt;/p&gt;

&lt;p&gt;then press Create Function&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Mw0lKkln--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/snj7i2b3cyva6fv13jyu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Mw0lKkln--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/snj7i2b3cyva6fv13jyu.png" alt="Image description" width="880" height="75"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;fill in a function name, choose python runtime&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JEBX1Wi6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/urt6mubm9xyadhr2sztv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JEBX1Wi6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/urt6mubm9xyadhr2sztv.png" alt="Image description" width="880" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and create function&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 8:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;once the function is created, scroll down to the code source window and edit our preexisting code a little to fit AWS template&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import json
import random
import requests
from bs4 import BeautifulSoup
import os
import smtplib
import itertools

# email body
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
# system date and time manipulation
import datetime

now = datetime.datetime.now()

def lambda_handler(event, context):
    # email content placeholder
    content = ''

    urls_dict = {
        'telecom': 'https://economictimes.indiatimes.com/industry/telecom',
        'transport': 'https://economictimes.indiatimes.com/industry/transportation',
        'services': 'https://economictimes.indiatimes.com/industry/services',
        'biotech': 'https://economictimes.indiatimes.com/industry/healthcare/biotech',
        'svs': 'https://economictimes.indiatimes.com/industry/indl-goods/svs',
        'energy': 'https://economictimes.indiatimes.com/industry/energy',
        'consumer_products': 'https://economictimes.indiatimes.com/industry/cons-products',
        'finance': 'https://economictimes.indiatimes.com/industry/banking/finance',
        'automobiles': 'https://economictimes.indiatimes.com/industry/auto'
    }


    def extract_news():
        todays_url = random.choice(list(urls_dict.values()))
        response = requests.get(todays_url)
        content = response.content
        soup = BeautifulSoup(content, 'html.parser')
        headline_data = soup.find("ul", class_="list1")

        email_body = ''

        email_body += 'Good Morning kiddo. Today we read Economics Times: &amp;lt;br /&amp;gt;\n &amp;lt;br /&amp;gt;\n'

        all_news = []

        url = 'https://economictimes.indiatimes.com'
        for i, news in enumerate(headline_data.find_all("li")):
            body = ''
            link = '%s%s' % (url, news.a.get('href'))
            body += '&amp;lt;a href="' + link + '"&amp;gt;' \
                    + news.text + '&amp;lt;/a&amp;gt;' + '&amp;lt;br /&amp;gt;\n' + '&amp;lt;br /&amp;gt;\n'
            # add items to a list
            all_news.append(body)

        # shuffle the list
        random.shuffle(all_news)

        n = 3
        # iterate over the first 5 elements of the randomized list
        for i in itertools.islice(all_news, n):
            email_body += '- ' + i

        email_body += '&amp;lt;br&amp;gt;---------------------------------&amp;lt;br&amp;gt;'
        email_body += '&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;Thats all for today. Byeeee'

        return email_body

    def send_mail(news_body):
        SERVER = 'smtp.gmail.com'
        PORT = 587
        FROM = 'homesanct@gmail.com'
        TO = 'dhrvmohapatra@gmail.com'
        PASSWORD = os.environ.get('password')

        msg = MIMEMultipart()
        msg['Subject'] = 'Economic Times' + ' ' + str(now.day) + '-' + str(now.month) + '-' + str(
            now.year)
        msg['From'] = FROM
        msg['To'] = TO
        msg.attach(MIMEText(news_body, 'html'))

        print('initializing server')

        server = smtplib.SMTP(SERVER, PORT)
        server.set_debuglevel(1)
        server.ehlo()
        server.starttls()
        server.login(FROM, PASSWORD)
        server.sendmail(FROM, TO, msg.as_string())

        print('Email Sent...')

        server.quit()

    news_body = extract_news()
    send_mail(news_body)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;save and press test to test out the code.&lt;/p&gt;

&lt;p&gt;a configure test event window pops up.&lt;/p&gt;

&lt;p&gt;give your new event a name and save it.&lt;/p&gt;

&lt;p&gt;run the function again and it errors out&lt;/p&gt;

&lt;p&gt;cuz there's two things missing&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 9:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;first thing missing is the password in our environment variables&lt;/p&gt;

&lt;p&gt;we add that by going to the configuration tab and adding an environment variable here&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ashSeZZu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o059x8sgbc9uovc7tzml.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ashSeZZu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o059x8sgbc9uovc7tzml.png" alt="Image description" width="880" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;the next thing missing is all the packages needed for the scrape. Requests and BeautifulSoup does just live on the AWS cloud so we need to add them to our project.&lt;/p&gt;

&lt;p&gt;this one took a while to figure out as well.&lt;/p&gt;

&lt;p&gt;here's my solution&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 10:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I went to the directory I had written my project locally in and made a directory named packages&lt;/p&gt;

&lt;p&gt;still in the project directory I opened up my terminal and ran the command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install -t packages requests
$ pip install -t packages beautifulsoup4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;now I copied the code from the AWS code source and made a new file inside the 'packages' directory and called it lambda_function.py&lt;/p&gt;

&lt;p&gt;now we are ready.&lt;/p&gt;

&lt;p&gt;I Ctrl+A to select all and compress to a zip folder.&lt;/p&gt;

&lt;p&gt;Back in the AWS Lambda console, there is an option that reads Upload From (right above the IDE). Upload this zip folder. Now you see this &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CmlpjkFw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8oips38prlhzw2i1tv72.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CmlpjkFw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8oips38prlhzw2i1tv72.png" alt="Image description" width="880" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 11:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now when I pressed the test button, I got a message in my inbox &lt;br&gt;
WOOHOO&lt;/p&gt;

&lt;p&gt;but there was one last thing left.&lt;/p&gt;

&lt;p&gt;I had to automate this task&lt;/p&gt;

&lt;p&gt;So over to the Amazon EventBridge we go&lt;/p&gt;

&lt;p&gt;here in the Rules submenu, I created a new rule&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FDdoA2jv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dmg71wi8i8mvamgyqf2g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FDdoA2jv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dmg71wi8i8mvamgyqf2g.png" alt="Image description" width="880" height="757"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--d6MCCKEx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q9gr8yenl66zdagxoorr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--d6MCCKEx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q9gr8yenl66zdagxoorr.png" alt="Image description" width="880" height="737"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nX3KwP_i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kpadi1uekkxfxy95e27k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nX3KwP_i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kpadi1uekkxfxy95e27k.png" alt="Image description" width="880" height="834"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KMfeFDKJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/etfil0j9jprhlzq2xjr8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KMfeFDKJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/etfil0j9jprhlzq2xjr8.png" alt="Image description" width="875" height="835"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bTshQXzT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k2kyy9b4l7blfzscdlee.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bTshQXzT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k2kyy9b4l7blfzscdlee.png" alt="Image description" width="874" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Xq6P-iqN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zx5f6r7lc9cpuhciez8g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Xq6P-iqN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zx5f6r7lc9cpuhciez8g.png" alt="Image description" width="697" height="764"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;now I came back to the AWS Lambdas console and saw that the EventBridge trigger had been added to my function. Sweet sauce.&lt;/p&gt;

&lt;p&gt;Time for the final step&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 12:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Press Deploy.&lt;/p&gt;

&lt;p&gt;A final note for the curious: The logs of all the functions can be seen in Amazon CloudWatch Console&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--L7wRsZGD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qsduwm6b82nv3e8wwqmf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--L7wRsZGD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qsduwm6b82nv3e8wwqmf.png" alt="Image description" width="880" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;aaaaand... that was it. I need to learn about docker and stuff cuz I've heard upload images is much smoother so maybe I'll spend some time going down that hole. &lt;/p&gt;

&lt;p&gt;Also, hopefully reading the news will be as fun as creating this project was ✌️&lt;/p&gt;

</description>
      <category>python</category>
      <category>aws</category>
    </item>
    <item>
      <title>Crowdfunding dApp with Solidity and NextJS</title>
      <dc:creator>dhxmo</dc:creator>
      <pubDate>Mon, 13 Jun 2022 03:32:23 +0000</pubDate>
      <link>https://dev.to/zdhxmo/crowdfunding-dapp-with-solidity-and-nextjs-4khi</link>
      <guid>https://dev.to/zdhxmo/crowdfunding-dapp-with-solidity-and-nextjs-4khi</guid>
      <description>&lt;p&gt;I’m a fairly new developer and I’ve found that learning to code is easiest when I’m building a project so I decided to build one.&lt;/p&gt;

&lt;p&gt;I built out a crowd funding site that requires no intermediaries. End users come in and just start interacting with the platform. You can &lt;a href="https://crowd-fund-darkmatterchimpanzee.vercel.app/"&gt;check it out here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ok, enough talk let’s get to it.&lt;/p&gt;

&lt;p&gt;We start with initializing truffle with my main directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;truffle init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I like working with ganache appimage, so start that and it whips up a local testnet for us on port 8545.&lt;/p&gt;

&lt;p&gt;Go into my truffle.config.js and make the necessary changes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const HDWalletProvider = require('@truffle/hdwallet-provider');
const fs = require('fs');
const mnemonic = fs.readFileSync(".secret").toString().trim();
require('dotenv').config();
const projectID = process.env.PROJECT_ID;
module.exports = {
networks: {
   development: {
      host: "127.0.0.1",     // Localhost (default: none)
      port: 8545,            // Standard Ethereum port (default: none)
      network_id: "*",       // Any network (default: none)
   },
   rinkeby: {
// 1.
      provider: () =&amp;gt; new HDWalletProvider(mnemonic, `https://rinkeby.infura.io/v3/${projectID}`),
      network_id: "4", // Rinkeby ID 4
      gas: 4465030,
      gasPrice: 10000000000,
   }
},
// Set default mocha options here, use special reporters etc.
mocha: {
// timeout: 100000
},
// Configure your compilers
compilers: {
      solc: {
         version: "0.8.4",      // Fetch exact version from solc-bin (default: truffle's version)
      settings: {          
         optimizer: {
            enabled: true,
            runs: 200
         },
      }
   }
  },
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ended up hosting the project on rinkeby testnet so I added the infura endpoints here.&lt;/p&gt;

&lt;p&gt;Go to infura dashboard, sign up and set up an endpoint for your project. &lt;a href="https://blog.infura.io/post/introducing-the-infura-dashboard-8969b7ab94e7"&gt;The instructions are here&lt;/a&gt;. Once you have a project set up, go into the settings and copy the project’s ID and paste it into a .env file in the root directory&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PROJECT_ID=db0b4735bad24926a761d909e1f82576
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;since this project is out in the open, I put this here. You don’t want to do this for production stuff. Something to do with compromising security and the lot. I don’t know the exact details why, but delving into security is my next move so someday i’ll write a post and I’ll know why the hell this is unsafe.&lt;/p&gt;

&lt;p&gt;the mnemonic is the private key to the account that ends up deploying this to the testnet, so make sure you get some fake ETH from a rinkeby faucet before deploying.&lt;/p&gt;

&lt;p&gt;I’ll slap the code here because it’s well documented and self-explanatory:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/darkMatterChimpanzee/crowdFund"&gt;The github repo is here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I usually like to define all the events in the contract in the beginning because it gives me a sense of all the things that are going to happen in this contract&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract CrowdFund is ReentrancyGuard {
    using SafeMath for uint256;
/*===== Events =====*/
    event NewProjectCreated(
        uint256 indexed id,
        address projectCreator,
        string projectTitle,
        string projectDesc,
        uint256 projectDeadline,
        uint256 goalAmount
    );
event ExpireFundraise(
        uint256 indexed id,
        string name,
        uint256 projectDeadline,
        uint256 goal
    );

    event SuccessFundRaise(
        uint256 indexed id,
        string name,
        uint256 projectDeadline,
        uint256 goal
    );

    event SuccessButContinueFundRaise(
        uint256 indexed id,
        string name,
        uint256 projectDeadline,
        uint256 goal
    );
event FundsReceive(
        uint256 indexed id,
        address contributor,
        uint256 amount,
        uint256 totalPledged
    );
event NewWithdrawalRequest(
        uint256 indexed id,
        string description,
        uint256 amount
    );
event GenerateRefund(
        uint256 indexed id,
        address refundRequestUser,
        uint256 refundAmt
    );
event ApproveRequest(uint256 indexed _id, uint32 _withdrawalRequestIndex);
event RejectRequest(uint256 indexed _id, uint32 _withdrawalRequestIndex);
event TransferRequestFunds(
        uint256 indexed _id,
        uint32 _withdrawalRequestIndex
    );
event PayCreator( uint256 indexed _id, uint32 _withdrawalRequestIndex, uint256 _amountTransfered);
event PayPlatform(uint256 indexed _id, uint32 _withdrawalRequestIndex, uint256 _amountTransfered);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we define all the state variables. These are all the things that have a state and will have some sort of transformation during the interaction with the users.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/*===== State variables =====*/
    address payable platformAdmin;
enum State {
        Fundraise,
        Expire,
        Success
    }
enum Withdrawal {
        Allow,
        Reject
    }
struct Project {
        // project ID
        uint256 id;
        // address of the creator of project
        address payable creator;
        // name of the project
        string name;
        // description of the project
        string description;
        // end of fundraising date
        uint256 projectDeadline;
        // total amount that has been pledged until this point
        uint256 totalPledged;
        // total amount needed for a successful campaign
        uint256 goal;
        // number of depositors
        uint256 totalDepositors;
        // total funds withdrawn from project
        uint256 totalWithdrawn;
        // current state of the fundraise
        State currentState;
        // holds URL of IPFS upload
        // string ipfsURL;
    }
struct WithdrawalRequest {
        uint32 index;
        // purpose of withdrawal
        string description;
        // amount of withdrawal requested
        uint256 withdrawalAmount;
        // project owner address
        address payable recipient;
        // total votes received for request
        uint256 approvedVotes;
        // current state of the withdrawal request
        Withdrawal currentWithdrawalState;
        // hash of the ipfs storage
        // string ipfsHash;
        // boolean to represent if amount has been withdrawn
        bool withdrawn;
    }
mapping(address =&amp;gt; uint) balances;
// project states
    uint256 public projectCount;
    mapping(uint256 =&amp;gt; Project) public idToProject;
    // project id =&amp;gt; contributor =&amp;gt; contribution
    mapping(uint256 =&amp;gt; mapping(address =&amp;gt; uint256)) public contributions;
// withdrawal requests
    mapping(uint256 =&amp;gt; WithdrawalRequest[]) public idToWithdrawalRequests;
    // project ID =&amp;gt; withdrawal request Index
    mapping(uint256 =&amp;gt; uint32) latestWithdrawalIndex;
// project id =&amp;gt; request number =&amp;gt; address of contributors
    mapping(uint256 =&amp;gt; mapping(uint32 =&amp;gt; address[])) approvals;
    mapping(uint256 =&amp;gt; mapping(uint32 =&amp;gt; address[])) rejections;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we put some modifiers in to put strict limitations on who can interact with the contract functions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/*===== Modifiers =====*/
    modifier checkState(uint256 _id, State _state) {
        require(
            idToProject[_id].currentState == _state,
            "Unmatching states. Invalid operation"
        );
        _;
    }
modifier onlyAdmin() {
        require(
            msg.sender == platformAdmin,
            "Unauthorized access. Only admin can use this function"
        );
        _;
    }
modifier onlyProjectOwner(uint256 _id) {
        require(
            msg.sender == idToProject[_id].creator,
            "Unauthorized access. Only project owner can use this function"
        );
        _;
    }
modifier onlyProjectDonor(uint256 _id) {
        require(
            contributions[_id][msg.sender] &amp;gt; 0,
            "Unauthorized access. Only project funders can use this function."
        );
        _;
    }
modifier checkLatestWithdrawalIndex(
        uint256 _id,
        uint32 _withdrawalRequestIndex
    ) {
        require(
            latestWithdrawalIndex[_id] == _withdrawalRequestIndex,
            "This is not the latest withdrawal request. Please check again and try later"
        );
        _;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I added a reentrancy guard cuz I just don’t want people to be doing greed shit when it comes to this public good.&lt;/p&gt;

&lt;p&gt;The fallback for this contract is the platform admin. If there’s some ethers floating around, might as well send it to the admin than go to waste sitting in the contract.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;constructor() ReentrancyGuard() {
        platformAdmin = payable(msg.sender);
        projectCount = 0;
    }
// make contract payable
    fallback() external payable {}
    receive() external payable {
        platformAdmin.transfer(msg.value);
    }
/*===== Functions  =====*/
/** @dev Function to start a new project.
     * @param _name Name of the project
     * @param _description Project Description
     * @param _projectDeadline Total days to end of fundraise
     * @param _goalEth Project goal in ETH
     */
    function createNewProject(
        string memory _name,
        string memory _description,
        uint256 _projectDeadline,
        uint256 _goalEth
    ) public {
        // update ID
        projectCount += 1;
        // log goal as wei
        uint256 _goal = _goalEth * 1e18;
        // create new fundraise object
        Project memory newFR = Project({
            id: projectCount,
            creator: payable(msg.sender),
            name: _name,
            description: _description,
            projectDeadline: _projectDeadline,
            totalPledged: 0,
            goal: _goal,
            currentState: State.Fundraise,
            totalDepositors: 0,
            totalWithdrawn: 0
        });
        // update mapping of id to new project
        idToProject[projectCount] = newFR;
        // initiate total withdrawal requests 
        latestWithdrawalIndex[projectCount] = 0;
        // emit event
        emit NewProjectCreated(
            projectCount,
            msg.sender,
            _name,
            _description,
            _projectDeadline,
            _goal
        );
    }
/** @dev Function to make a contribution to the project
     * @param _id Project ID where contributions are to be made
     */
    function contributeFunds(uint256 _id)
        public
        payable
        checkState(_id, State.Fundraise)
        nonReentrant()
    {
        require(_id &amp;lt;= projectCount, "Project ID out of range");
        require(
            msg.value &amp;gt; 0,
            "Invalid transaction. Please send valid amounts to the project"
        );
        require(
            block.timestamp &amp;lt;= idToProject[_id].projectDeadline,
            "Contributions cannot be made to this project anymore."
        );
        // transfer contributions to contract address
        balances[address(this)] += msg.value;
       // add to contribution
        contributions[_id][msg.sender] += msg.value;
        // increase total contributions pledged to the project
        idToProject[_id].totalPledged += msg.value;
        // add one to total number of depositors for this project
        idToProject[_id].totalDepositors += 1;
        emit FundsReceive(
            _id,
            msg.sender,
            msg.value,
            idToProject[_id].totalPledged
        );
    }
/** @dev Function to end fundraising drive
    * @param _id Project ID
    */
    function endContributionsExpire(uint256 _id) 
        public 
        onlyProjectDonor(_id)
        checkState(_id, State.Fundraise) 
        {
            require(
                block.timestamp &amp;gt; idToProject[_id].projectDeadline,
                "Invalid request. Can only be called after project deadline is reached"
            );
            idToProject[_id].currentState = State.Expire;
            emit ExpireFundraise(_id,
                idToProject[_id].name,
                idToProject[_id].projectDeadline,
                idToProject[_id].goal
            );
        }

    /** @dev Function to end fundraising drive with success is total pledged higher than goal. Irrespective of deadline
    * @param _id Project ID
    */
    function endContributionsSuccess(uint256 _id) 
        public 
        onlyProjectOwner(_id)
        checkState(_id, State.Fundraise) 
        {
            require(idToProject[_id].totalPledged &amp;gt;= idToProject[_id].goal, "Did not receive enough funds");
            idToProject[_id].currentState = State.Success;
            emit SuccessFundRaise(
                _id,
                idToProject[_id].name,
                idToProject[_id].projectDeadline,
                idToProject[_id].goal
            );                
        }
/** @dev Function to get refund on expired projects
     * @param _id Project ID
     */
    function getRefund(uint256 _id)
        public
        payable
        onlyProjectDonor(_id)
        checkState(_id, State.Expire)
        nonReentrant()
    {
        require(
            block.timestamp &amp;gt; idToProject[_id].projectDeadline,
            "Project deadline hasn't been reached yet"
        );
        address payable _contributor = payable(msg.sender);
        uint256 _amount = contributions[_id][msg.sender];
        (bool success, ) = _contributor.call{value: _amount}("");
        require(success, "Transaction failed. Please try again later.");
        emit GenerateRefund(_id, _contributor, _amount);
        // update project state
        idToProject[_id].totalPledged -= _amount;
        idToProject[_id].totalDepositors -= 1;
    }
/** @dev Function to create a request for withdrawal of funds
    * @param _id Project ID
    * @param _requestNumber Index of the request
    * @param _description  Purpose of withdrawal
    * @param _amount Amount of withdrawal requested in ETH
    */
    function createWithdrawalRequest(
        uint256 _id,
        uint32 _requestNumber,
        string memory _description,
        uint256 _amount
    ) public onlyProjectOwner(_id) checkState(_id, State.Success){
        require(idToProject[_id].totalWithdrawn &amp;lt; idToProject[_id].totalPledged, "Insufficient funds");
        require(_requestNumber == latestWithdrawalIndex[_id] + 1, "Incorrect request number");
        // convert ETH to Wei units
        uint256 _withdraw = _amount * 1e18;
        // create new withdrawal request
        WithdrawalRequest memory newWR = WithdrawalRequest({
            index: _requestNumber,
            description: _description,
            withdrawalAmount: _withdraw,
            // funds withdrawn to project owner
            recipient: idToProject[_id].creator,
            // initialized with no votes for request
            approvedVotes: 0,
            // state changes on quorum
            currentWithdrawalState: Withdrawal.Reject,
            withdrawn: false
        });
        // update project to request mapping
        idToWithdrawalRequests[_id].push(newWR);

        latestWithdrawalIndex[_id] += 1;
        // emit event
        emit NewWithdrawalRequest(_id, _description, _amount);
    }
/** @dev Function to check whether a given address has approved a specific request
    * @param _id Project ID
    * @param _withdrawalRequestIndex Index of the withdrawal request
    * @param _checkAddress Address of the request initiator
    */
    function _checkAddressInApprovalsIterator(
        uint256 _id,
        uint32 _withdrawalRequestIndex, 
        address _checkAddress
    )
        internal
        view 
        returns(bool approved) 
    {
        // iterate over the array specific to this id and withdrawal request
        for (uint256 i = 0; i &amp;lt; approvals[_id][_withdrawalRequestIndex - 1].length; i++) {
            // if address is in the array, return true
            if(approvals[_id][_withdrawalRequestIndex - 1][i] == _checkAddress) {
                approved = true;
            }
        }
    }
/** @dev Function to check whether a given address has rejected a specific request
    * @param _id Project ID
    * @param _withdrawalRequestIndex Index of the withdrawal request
    * @param _checkAddress Address of the request initiator
    */
    function _checkAddressInRejectionIterator(
        uint256 _id,
        uint32 _withdrawalRequestIndex, 
        address _checkAddress
    ) 
        internal
        view
        returns(bool rejected) 
    {
        // iterate over the array specific to this id and withdrawal request
        for (uint256 i = 0; i &amp;lt; rejections[_id][_withdrawalRequestIndex - 1].length; i++) {
            // if address is in the array, return true
            if(rejections[_id][_withdrawalRequestIndex - 1][i] == _checkAddress) {
                rejected = true;
            }
        }
    }
/** @dev Function to approve withdrawal of funds
    * @param _id Project ID
    * @param _withdrawalRequestIndex Index of withdrawal request
    */
    function approveWithdrawalRequest(
        uint256 _id,
        uint32 _withdrawalRequestIndex
    )
        public
        onlyProjectDonor(_id)
        checkState(_id, State.Success)
        checkLatestWithdrawalIndex(_id, _withdrawalRequestIndex)
    {
        // confirm msg.sender hasn't approved request yet
        require(!_checkAddressInApprovalsIterator(_id, _withdrawalRequestIndex, msg.sender), 
                "Invalid operation. You have already approved this request");
         require(!_checkAddressInRejectionIterator(_id, _withdrawalRequestIndex, msg.sender), 
                "Invalid operation. You have rejected this request");
        // get total withdrawal requests made
        uint256 _lastWithdrawal = latestWithdrawalIndex[_id];
       // iterate over all requests for this project
        for (uint256 i = 0; i &amp;lt; _lastWithdrawal; i++) {
            // if request number is equal to index
            if(i + 1 == _withdrawalRequestIndex) {
                // increment approval count
                idToWithdrawalRequests[_id][i].approvedVotes += 1;
            }
        }
        // push msg.sender to approvals list for this request
        approvals[_id][_withdrawalRequestIndex - 1].push(msg.sender);

        emit ApproveRequest(_id, _withdrawalRequestIndex);
    }
/** @dev Function to reject withdrawal of funds
     * @param _id Project ID
     * @param _withdrawalRequestIndex Index of withdrawal request
     */
    function rejectWithdrawalRequest(
        uint256 _id,
        uint32 _withdrawalRequestIndex
    )
        public
        onlyProjectDonor(_id)
        checkState(_id, State.Success)
        checkLatestWithdrawalIndex(_id, _withdrawalRequestIndex)
    {
        // confirm user hasn't approved request
        require(!_checkAddressInApprovalsIterator(_id, _withdrawalRequestIndex, msg.sender), 
                "Invalid operation. You have approved this request");
        require(!_checkAddressInRejectionIterator(_id, _withdrawalRequestIndex, msg.sender), 
                "Invalid operation. You have already rejected this request");
        // get total withdrawal requests made
        uint256 _lastWithdrawal = latestWithdrawalIndex[_id];
        // iterate over all requests for this project
        for (uint256 i = 0; i &amp;lt; _lastWithdrawal; i++) {
            // if request number is equal to index
            if(i + 1 == _withdrawalRequestIndex) {
                // if there hve been approvals, decrement
                if(idToWithdrawalRequests[_id][i].approvedVotes != 0) {
                    // decrement approval count
                    idToWithdrawalRequests[_id][i].approvedVotes -= 1;
                } 
                    // else if no one has approved request yet, keep approvals to 0
                else {
                    idToWithdrawalRequests[_id][i].approvedVotes == 0;
                }
            }
        }
        // add msg.sender to rejections list for this request
        rejections[_id][_withdrawalRequestIndex - 1].push(msg.sender);
emit RejectRequest(_id, _withdrawalRequestIndex);
    }
/** @dev Function to transfer funds to project creator
     * @param _id Project ID
     * @param _withdrawalRequestIndex Index of withdrawal request
     */
    function transferWithdrawalRequestFunds(
        uint256 _id,
        uint32 _withdrawalRequestIndex
    )
        public
        payable
        onlyProjectOwner(_id)
        checkLatestWithdrawalIndex(_id, _withdrawalRequestIndex)
        nonReentrant()
    {
        require(
     // _withdrawalRequestIndex - 1 to accomodate 0 start of arrays
            idToWithdrawalRequests[_id][_withdrawalRequestIndex - 1].approvedVotes &amp;gt; (idToProject[_id].totalDepositors).div(2),
            "More than half the total depositors need to approve withdrawal request"
        );
        require(idToWithdrawalRequests[_id][_withdrawalRequestIndex - 1].withdrawn == false, "Withdrawal has laready been made for this request");
         require(idToWithdrawalRequests[_id][_withdrawalRequestIndex - 1].withdrawalAmount &amp;lt; idToProject[_id].totalPledged, "Insufficient funds");
          WithdrawalRequest storage cRequest = idToWithdrawalRequests[_id][_withdrawalRequestIndex - 1];
        // flat 0.3% platform fee
        uint256 platformFee = (cRequest.withdrawalAmount.mul(3)).div(1000);
        (bool pfSuccess, ) = payable(platformAdmin).call{value: platformFee}("");
        require(pfSuccess, "Transaction failed. Please try again later.");
        emit PayPlatform(_id, _withdrawalRequestIndex, platformFee);

        // transfer funds to creator
        address payable _creator = idToProject[_id].creator;
        uint256 _amount = cRequest.withdrawalAmount - platformFee;
        (bool success, ) = _creator.call{value: _amount}("");
        require(success, "Transaction failed. Please try again later.");
        emit PayCreator(_id, _withdrawalRequestIndex, _amount);
        // update states
        cRequest.withdrawn = true;
        idToProject[_id].totalWithdrawn += cRequest.withdrawalAmount;
        emit TransferRequestFunds(_id, _withdrawalRequestIndex);
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That would be the base functionality. Next up would be the view functions that allow the frontend to query the blockchain and display states.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/*===== Blockchain get functions =====*/
/** @dev Function to get project details
     * @param _id Project ID
     */
    function getProjectDetails(uint256 _id)
        public
        view
        returns (
            address creator,
            string memory name,
            string memory description,
            uint256 projectDeadline,
            uint256 totalPledged,
            uint256 goal,
            uint256 totalDepositors,
            uint256 totalWithdrawn,
            State currentState
)
    {
        creator = idToProject[_id].creator;
        name = idToProject[_id].name;
        description = idToProject[_id].description;
        projectDeadline = idToProject[_id].projectDeadline;
        totalPledged = idToProject[_id].totalPledged;
        goal = idToProject[_id].goal;
        totalDepositors = idToProject[_id].totalDepositors;
        totalWithdrawn = idToProject[_id].totalWithdrawn;
        currentState = idToProject[_id].currentState;
    }
function getAllProjects() public view returns (Project[] memory) {
        uint256 _projectCount = projectCount;
        Project[] memory projects = new Project[](_projectCount);
        for (uint256 i = 0; i &amp;lt; _projectCount; i++) {
            uint256 currentId = i + 1;
            Project storage currentItem = idToProject[currentId];
            projects[i] = currentItem;
        }
        return projects;
    }
function getProjectCount() public view returns (uint256 count) {
        count = projectCount;
    }
function getAllWithdrawalRequests(uint256 _id)
        public
        view
        returns (WithdrawalRequest[] memory)
    {
        uint256 _lastWithdrawal = latestWithdrawalIndex[_id];
        WithdrawalRequest[] memory withdrawals = new WithdrawalRequest[](
            _lastWithdrawal
        );
        for (uint256 i = 0; i &amp;lt; _lastWithdrawal; i++) {
            WithdrawalRequest storage currentRequest = idToWithdrawalRequests[_id][i];
            withdrawals[i] = currentRequest;
        }
        return withdrawals;
    }
function getContributions(uint256 _id, address _contributor) public view returns(uint256) {
        return contributions[_id][_contributor];
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the contract is written, compile and migrate it onto the testnet with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;truffle migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the transaction receipt, copy the contract address and create a file called config.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const contractAddress = "0x53692f4BB9E072d481D42Ce2c9d919E2945aDac6";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This address is where I ended up deploying it to the rinkeby testenet.&lt;/p&gt;

&lt;p&gt;Yeeeeesh, that was a lot of code.&lt;/p&gt;

&lt;p&gt;Go walk a little. kiss your partner. If you don’t have a partner, go talk to someone you’re interested in and when the disappointment of that interaction sinks in… Get started with the frontend.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://tailwindcss.com/docs/guides/nextjs"&gt;Initialize Tailwind with NextJS by following the instructions given here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Don’t be hasty and don’t miss a step. Correct configuration is really important for the whole build to work out in the end.&lt;/p&gt;

&lt;p&gt;so I worked with the localhost settings and then commented them out to have the semi-real world settings of testnet activated. But when you’re working on your development environment, reverse the process and poke around a lot more.&lt;/p&gt;

&lt;p&gt;I’m coming to love NextJS cuz it pretty much takes away all the unnecessary work away from me and I can focus on building the product.&lt;/p&gt;

&lt;p&gt;Before we start, let’s have our package.json files looking the same&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "crowdfund",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "resolutions": {
    "async": "^2.6.4",
    "node-fetch": "^2.6.7",
    "lodash": "^4.17.21",
    "underscore": "^1.12.1",
    "yargs-parser": "^5.0.1"
  },
  "dependencies": {
    "@openzeppelin/contracts": "^4.6.0",
    "@openzeppelin/test-helpers": "^0.5.15",
    "@truffle/hdwallet-provider": "^2.0.8",
    "@walletconnect/web3-provider": "^1.7.8",
    "bignumber.js": "^9.0.2",
    "dotenv": "^16.0.1",
    "ipfs-http-client": "^56.0.3",
    "next": "12.1.6",
    "nextjs-progressbar": "^0.0.14",
    "react": "18.1.0",
    "react-dom": "18.1.0",
    "true-json-bigint": "^1.0.1",
    "web3": "^1.7.3",
    "web3modal": "^1.9.7"
  },
  "devDependencies": {
    "@babel/plugin-syntax-top-level-await": "^7.14.5",
    "@openzeppelin/truffle-upgrades": "^1.15.0",
    "autoprefixer": "^10.4.7",
    "chai": "^4.3.6",
    "eslint": "8.16.0",
    "eslint-config-next": "12.1.6",
    "ethereum-waffle": "^3.0.0",
    "ethers": "^5.6.8",
    "postcss": "^8.4.14",
    "tailwindcss": "^3.0.24"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;now run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this will install all the dependencies needed for this project.&lt;/p&gt;

&lt;p&gt;Next, go into the pages directory and in the _app.js file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { AccountContext } from '../context.js'
import { useState } from 'react'
import Link from 'next/link'
import Head from 'next/head'
import NextNProgress from "nextjs-progressbar";
import { ethers } from 'ethers'
import Web3Modal from 'web3modal'
import WalletConnectProvider from '@walletconnect/web3-provider'
import '../styles/globals.css'
function MyApp({ Component, pageProps }) {
  const [account, setAccount] = useState(null)
// 1.
   async function getWeb3Modal() {
    const web3Modal = new Web3Modal({
      network: 'rinkeby',
      cacheProvider: false,
      providerOptions: {
        walletconnect: {
          package: WalletConnectProvider,
          // testnet deployement
          options: {
            infuraId: process.env.PROJECT_ID
          },
          // localhost for dev
          // options: {
          //   rpc: { 1337: 'http://localhost:8545', },
          //   chainId: 1337,
          // }
        },
      },
    })
    return web3Modal
  }
//2.
   async function web3connect() {
    try {
      const web3Modal = await getWeb3Modal()
      const connection = await web3Modal.connect()
      const provider = new ethers.providers.Web3Provider(connection)
      const accounts = await provider.listAccounts()
      return accounts;
    } catch (err) {
      console.log('error:', err)
    }
  }
// 3.
   // connect to wallet
  async function connect() {
    const accounts = await web3connect()
    setAccount(accounts)
  }
   return (
    &amp;lt;div className='min-h-screen w-screen font-mono'&amp;gt;
      &amp;lt;Head&amp;gt;
        &amp;lt;title&amp;gt;iFund&amp;lt;/title&amp;gt;
        &amp;lt;meta name="description" content="Create New Fundraising Campaign" /&amp;gt;
        &amp;lt;link rel="icon" href="/logo.png" /&amp;gt;
      &amp;lt;/Head&amp;gt;
      &amp;lt;div className='sm:h-10'&amp;gt;
        &amp;lt;nav className='flex mx-auto text-black-20/100'&amp;gt;
          &amp;lt;Link href="/"&amp;gt;
            &amp;lt;a&amp;gt;
              &amp;lt;img src="/logo.png" alt="crowdFund logo" className='h-20 object-contain my-5 ml-5' /&amp;gt;
            &amp;lt;/a&amp;gt;
          &amp;lt;/Link&amp;gt;
          &amp;lt;div className='flex'&amp;gt;
// 4.
            {
              !account ?
                &amp;lt;div className='my-10 mx-10' &amp;gt;
                  &amp;lt;p&amp;gt;Pls connect to interact with this app&amp;lt;/p&amp;gt;
                  &amp;lt;button className='rounded-md bg-pink-500 text-white p-3 ml-20' onClick={connect}&amp;gt;Connect&amp;lt;/button&amp;gt;
                &amp;lt;/div&amp;gt; :
                &amp;lt;p className='rounded-md my-10 bg-pink-500 text-white p-3 ml-20' &amp;gt;
                  {account[0].substr(0, 10) + "..."}
                &amp;lt;/p&amp;gt;
            }
            {/* if you compile right now it will give an error as we haven't created this page yet. this is coming up next. */}
            &amp;lt;Link href="/create"&amp;gt;
              &amp;lt;button className='rounded-md my-10 bg-pink-500 text-white p-3 ml-20' &amp;gt;Create New Fundraising Project&amp;lt;/button&amp;gt;
            &amp;lt;/Link&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/nav&amp;gt;
      &amp;lt;/div&amp;gt;
// 5. 
    {/* drip account into the app */}
      &amp;lt;AccountContext.Provider value={account}&amp;gt;
        &amp;lt;NextNProgress /&amp;gt;
        {account &amp;amp;&amp;amp; &amp;lt;Component {...pageProps} connect={connect} /&amp;gt;}
      &amp;lt;/AccountContext.Provider&amp;gt;
    &amp;lt;/div&amp;gt;)
}
export default MyApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;contacts the blockchain&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;calls the blockchain from our metamask infura endpoint&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;updates state of the account currently talking to the blockchain&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;is pretty straight forward. If no account has been connected yet, say connect. If something gets connected, print out a few of the first characters of the account&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AccountContext is a context we add so that the app stays informed of any changes in the user connecting to it&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;(&lt;a href="https://reactjs.org/docs/context.html#reactcreatecontext"&gt;you can read more about contexts here&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;outside the pages directory create a file called context.js and add this to it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { createContext } from 'react'
export const AccountContext = createContext(null)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we create the page for fundraise creation inside the pages directory called create.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Link from "next/link"
import { useEffect, useState } from 'react' // new
import { useRouter } from 'next/router'
import Web3Modal from 'web3modal'
import { ethers } from 'ethers'
// 1.
import CrowdFund from "../build/contracts/CrowdFund.json"
import { contractAddress } from '../config'
// 2.
const initialState = { name: '', description: "'', projectDeadline: '', goal: 0 };"
const Create = () =&amp;gt; {
    // router to route back to home page
    const router = useRouter()
    const [project, setProject] = useState(initialState)
    const [contract, setContract] = useState();

// 3.
    useEffect(() =&amp;gt; {
        // function to get contract address and update state
        async function getContract() {
            const web3Modal = new Web3Modal()
            const connection = await web3Modal.connect()
            const provider = new ethers.providers.Web3Provider(connection)
            const signer = provider.getSigner()
            let _contract = new ethers.Contract(contractAddress, CrowdFund.abi, signer)
            setContract(_contract);
        }
        getContract();
    })

// 4.
     async function saveProject() {
        // destructure project 
        const { name, description, projectDeadline, goal } = project
        try {
            // create project
            let transaction = await contract.createNewProject(name, description, projectDeadline, goal)
// await successful transaction and reroute to home
            const x = await transaction.wait()
            if (x.status == 1) {
                router.push('/')
            }
        } catch (err) {
            window.alert(err)
        }
    }
return (
        &amp;lt;div className="min-h-screen my-20 w-screen p-5"&amp;gt;
            &amp;lt;main&amp;gt;
                &amp;lt;div className="rounded-md my-10 bg-pink-500 text-white p-3 w-20"&amp;gt;&amp;lt;Link href="/"&amp;gt; Home &amp;lt;/Link&amp;gt;&amp;lt;/div&amp;gt;
                &amp;lt;p className="text-center text-lg my-5"&amp;gt;Create a new campaign!&amp;lt;/p&amp;gt;

// 5.
               &amp;lt;div className="bg-pink-500 text-black h-50 p-10 flex flex-col"&amp;gt;
                    &amp;lt;input
                        onChange={e =&amp;gt; setProject({ ...project, name: e.target.value })}
                        name='title'
                        placeholder='Give it a name ...'
                        className='p-2 my-2 rounded-md'
                        value={project.name}
                    /&amp;gt;
                    &amp;lt;textarea
                        onChange={e =&amp;gt; setProject({ ...project, description: "e.target.value })}"
                        name='description'
                        placeholder='Give it a description ...'
                        className='p-2 my-2 rounded-md'
                    /&amp;gt;
                    &amp;lt;input
                        onChange={e =&amp;gt; setProject({ ...project, projectDeadline: Math.floor(new Date() / 1000) + (e.target.value * 86400) })}
                        name='projectDeadline'
                        placeholder='Give it a deadline ... (in days)'
                        className='p-2 my-2 rounded-md'
                    /&amp;gt;
                    &amp;lt;input
                        onChange={e =&amp;gt; setProject({ ...project, goal: e.target.value })}
                        name='goalEth'
                        placeholder='Give it a goal ... (in ETH). Only integer values are valid'
                        className='p-2 my-2 rounded-md'
                    /&amp;gt;
                    &amp;lt;button type='button' className="w-20 text-white rounded-md my-10 px-3 py-2 shadow-lg border-2" onClick={saveProject}&amp;gt;Submit&amp;lt;/button&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/main&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}
export default Create
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;is where the contract details are fetched for the frontend. We need 3 things to talk to this contract. The blockchain it’s on, the address of the contract, it’s contents and who’s trying to interact with it. The blockchain and who’s talking to it, we got in _app.js. contract address and its contents (ABI) we fetch from the build that truffle migrate gave us.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I set the initial state as an object of what entries I wanted the fundraise to have. This makes it easy to update state through inputs later on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;since NextJS makes everythig into HTML on the server side if we don’t mention this piece of code in useEffect, we get an error, because of course only when the provider and signer have been set can the contract be contacted.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;this simply calls the createNewProject function that we defined in the contract&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;now we get user input and update state and send this onto the blockchain, hence creating a new project&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;now, this will compile and we’ll have a home page and a create project page.&lt;/p&gt;

&lt;p&gt;Now that we can create projects, we need to view all the projects that the creators whip up. So in the home page (index.js) we write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {
    contractAddress
} from '../config'
import CrowdFund from "../build/contracts/CrowdFund.json"
import { ethers, BigNumber } from 'ethers'
import Link from 'next/link'
// 1.
const infuraKey = process.env.PROJECT_ID;
export default function Home({ projects }) {
    return (
        &amp;lt;div className='min-h-screen my-20 w-screen p-5'&amp;gt;
            &amp;lt;p className='text-center font-bold'&amp;gt;test project --- Please connect to the Rinkeby testnet&amp;lt;/p&amp;gt;
&amp;lt;div className='bg-pink-500 text-white p-10 rounded-md'&amp;gt;
                &amp;lt;div&amp;gt;
                    &amp;lt;p className='font-bold m-5'&amp;gt;How it Works&amp;lt;/p&amp;gt;
                    &amp;lt;p className='my-3'&amp;gt;1. Creator creates a new project &amp;lt;/p&amp;gt;
&amp;lt;p className='my-3'&amp;gt;2. Contributors contribute until deadline&amp;lt;/p&amp;gt;
                    &amp;lt;p className='my-3'&amp;gt;3. If total pledged doesn&amp;amp;apos;t get met on deadline date, contributors expire the project and refund donated funds back&amp;lt;/p&amp;gt;
                    &amp;lt;p className='my-3'&amp;gt;4. If total amount pledged reaches the goal, creator declares the fundraise a success&amp;lt;/p&amp;gt;
                    &amp;lt;div className='my-3'&amp;gt;
                        &amp;lt;p className='my-3 ml-10'&amp;gt;a. creator makes a withdrawal request&amp;lt;/p&amp;gt;
                        &amp;lt;p className='my-3 ml-10'&amp;gt;b. contributors vote on the request&amp;lt;/p&amp;gt;
                        &amp;lt;p className='my-3 ml-10'&amp;gt;c. if approved, creator withdraws the amount requested to work on the project&amp;lt;/p&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div className='text-black'&amp;gt;
// 2.
                &amp;lt;div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 pt-6"&amp;gt;
                    {
                        projects.map((project, i) =&amp;gt; (
                            &amp;lt;div key={i} className="border shadow rounded-xl overflow-hidden"&amp;gt;
                                &amp;lt;div className="p-4"&amp;gt;
                                    &amp;lt;p&amp;gt;ID: {BigNumber.from(project[0]).toNumber()}&amp;lt;/p&amp;gt;
                                    &amp;lt;p className="my-6 text-2xl font-semibold"&amp;gt;{project[2]}&amp;lt;/p&amp;gt;
                                    &amp;lt;div&amp;gt;
                                        &amp;lt;p className="my-3 text-gray-400"&amp;gt;{project[3]?.substr(0, 20) + "..."}&amp;lt;/p&amp;gt;
                                        &amp;lt;p className="my-3"&amp;gt; Deadline:  {new Date((BigNumber.from(project[4]).toNumber()) * 1000).toLocaleDateString()} &amp;lt;/p&amp;gt;
                                        &amp;lt;p className="my-3"&amp;gt; Total Pledged:  {Math.round(ethers.utils.formatEther(project[5]))} ETH&amp;lt;/p&amp;gt;
                                        &amp;lt;p className="my-3"&amp;gt; Goal:  {ethers.utils.formatEther(project[6])} ETH &amp;lt;/p&amp;gt;
                                    &amp;lt;/div&amp;gt;
// 3. 
&amp;lt;Link href={`project/${BigNumber.from(project[0]).toNumber()}`} key={i}&amp;gt;
                                        &amp;lt;button className='rounded-md my-5 bg-pink-500 text-white p-3 mx-1'&amp;gt;Details&amp;lt;/button&amp;gt;
                                    &amp;lt;/Link&amp;gt;
                                &amp;lt;/div&amp;gt;
                            &amp;lt;/div&amp;gt;
                        ))
                    }
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div &amp;gt;
    )
}
// 4.
export async function getServerSideProps() {
    let provider = new ethers.providers.JsonRpcProvider(`https://rinkeby.infura.io/v3/${infuraKey}`)
    // localhost
    // let provider = new ethers.providers.JsonRpcProvider()
const contract = new ethers.Contract(contractAddress, CrowdFund.abi, provider)
    const data = await contract.getAllProjects()
    return {
        props: {
            projects: JSON.parse(JSON.stringify(data))
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;gets the infura key from the .env file&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;props that we get from the server-side render gets destructured and come as projects. We map over this and can isolate indiviual project that have been created by users on this contract.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;this is the page we create next. the page will error out for now&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;is where NextJS shines. It fetches all the projects on the server side and ‘hydrates’ (populates) it pre-render. This makes the whole render super fast.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I swear to god, I wish I could make this codeblock a little wider for yall, but alas, such is the formatting. You must contend with it or check out the code on github.&lt;/p&gt;

&lt;p&gt;I like dynamic routing a lot so we will use that. in the pages directory, create a directory named project. inside that create a file named [id].js&lt;/p&gt;

&lt;p&gt;what this essentially allows us to do is run functions getStaticPaths and getStaticProps which in effect populates all the possible routes that we’ve decided to hook. In this example we hook the project ID with the route.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { BigNumber, ethers, web3 } from 'ethers'
import Web3Modal from 'web3modal'
import { useState, useContext, useEffect } from 'react' // new
import { useRouter } from 'next/router'
import Link from 'next/link'
import {
    contractAddress
} from '../../config'
import { AccountContext } from '../../context'
import CrowdFund from "../../build/contracts/CrowdFund.json"
const infuraKey = process.env.PROJECT_ID;
export default function Project({ project, projectID }) {
    useContext(AccountContext);
    const router = useRouter()
    const [contributionValue, setContributionValue] = useState(0);
    const [contract, setContract] = useState();
    useEffect(() =&amp;gt; {
        // function to get contract address and update state
        async function getContract() {
            const web3Modal = new Web3Modal()
            const connection = await web3Modal.connect()
            const provider = new ethers.providers.Web3Provider(connection)
            const signer = provider.getSigner()
            let _contract = new ethers.Contract(contractAddress, CrowdFund.abi, signer)
            setContract(_contract);
        }
        getContract();
    })
    // Function to contribute funds to the project
    async function contribute() {
        try {
            // send contribution
            let transaction = await contract.contributeFunds(projectID, {
                value: ethers.utils.parseUnits(contributionValue, "ether")
            })
            // await transaction
            let x = await transaction.wait()
            // reroute to home page
            if (x.status == 1) {
                router.push('/')
            }
        } catch (err) {
            window.alert(err.message)
        }
    }
    // function to declare fundraise a success
    async function changeStateToSuccess() {
        try {
            let tx = await contract.endContributionsSuccess(projectID);
            let x = await tx.wait()
            if (x.status == 1) {
                router.push(`/project/${projectID}`);
                window.alert('Project state was successfully changed to : Success')
            }
        } catch (err) {
            window.alert(err.message)
        }
    }
    // function to declare fundraise a failure
    async function changeStateToExpire() {
        try {
            let tx = await contract.endContributionsExpire(projectID);
            let x = await tx.wait()
            if (x.status == 1) {
                window.alert('Project state was successfully changed to : Expire')
            }
        } catch (err) {
            window.alert(err.message)
        }
    }
// function to process a refund on failed fundraise
    async function processRefund() {
        try {
            let tx = await contract.getRefund(projectID);
            let x = await tx.wait()
            if (x.status == 1) {
                window.alert('Successful Refund')
                router.push('/');
            }
        } catch (err) {
            window.alert(err.message)
        }
    }
// 1.
    if (router.isFallback) {
        return &amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;
    }
return (
        &amp;lt;div className='mt-20'&amp;gt;
            &amp;lt;div className='bg-pink-500 text-white p-20 rounded-md mx-5 mt-40'&amp;gt;
                &amp;lt;p className='my-6'&amp;gt;&amp;lt;span className='font-bold'&amp;gt; Project Number: &amp;lt;/span&amp;gt; {projectID}&amp;lt;/p&amp;gt;
                &amp;lt;p className='my-6'&amp;gt;&amp;lt;span className='font-bold'&amp;gt; Creator: &amp;lt;/span&amp;gt; {project.creator}&amp;lt;/p&amp;gt;
                &amp;lt;p className='my-6'&amp;gt;&amp;lt;span className='font-bold'&amp;gt; Project Name: &amp;lt;/span&amp;gt; {project.name}&amp;lt;/p&amp;gt;
                &amp;lt;div className='break-words'&amp;gt;
                    &amp;lt;p className='my-6'&amp;gt;&amp;lt;span className='font-bold'&amp;gt;Description:&amp;lt;/span&amp;gt; {project.description}&amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;p className='my-6'&amp;gt;&amp;lt;span className='font-bold'&amp;gt;Crowdfund deadline:&amp;lt;/span&amp;gt; {new Date((BigNumber.from(project.projectDeadline).toNumber()) * 1000).toLocaleDateString()}&amp;lt;/p&amp;gt;
                &amp;lt;p className='my-6'&amp;gt;&amp;lt;span className='font-bold'&amp;gt;Total ETH pledged:&amp;lt;/span&amp;gt; {project.totalPledged} ETH&amp;lt;/p&amp;gt;
                &amp;lt;p className='my-6'&amp;gt;&amp;lt;span className='font-bold'&amp;gt;Fundraise Goal:&amp;lt;/span&amp;gt; {project.goal} ETH&amp;lt;/p&amp;gt;
                &amp;lt;p className='my-6'&amp;gt;&amp;lt;span className='font-bold'&amp;gt;Total Contributors:&amp;lt;/span&amp;gt; {project.totalDepositors}&amp;lt;/p&amp;gt;
                &amp;lt;p className='my-6'&amp;gt;&amp;lt;span className='font-bold'&amp;gt;Current State:&amp;lt;/span&amp;gt; {project.currentState === 0 ? 'Fundraise Active' : (project.currentState === 1) ? 'Fundraise Expired' : 'Fundraise Success'}&amp;lt;/p&amp;gt;
&amp;lt;p className='my-6'&amp;gt;&amp;lt;span className='font-bold'&amp;gt;Total Withdrawals:&amp;lt;/span&amp;gt; {project.totalWithdrawn} ETH&amp;lt;/p&amp;gt;
&amp;lt;div className='text-center'&amp;gt;
                    &amp;lt;input
                        onChange={e =&amp;gt; setContributionValue(e.target.value)}
                        type='number'
                        className='p-2 my-2 rounded-md text-black'
                        value={contributionValue}
                    /&amp;gt;
                    &amp;lt;button onClick={contribute} className='rounded-md mt-20 my-10 bg-white text-pink-500 p-3 mx-4 shadow-md'&amp;gt;Contribute&amp;lt;/button&amp;gt;
                &amp;lt;/div&amp;gt;
&amp;lt;div className='grid sm:grid-col-1 md:grid-cols-2 sm:text-sm'&amp;gt;
                    &amp;lt;div className='grid grid-cols-1 px-10 sm:w-200 place-content-stretch'&amp;gt;
                        &amp;lt;button onClick={changeStateToSuccess} className='rounded-md mt-20 my-10 bg-white text-pink-500 p-3 shadow-lg flex-wrap'&amp;gt;Click here if fundraise was a success (project owner only)&amp;lt;/button&amp;gt;
&amp;lt;Link href={`withdrawal/${projectID}`}&amp;gt;
                            &amp;lt;button className='rounded-md mt-20 my-10 bg-white text-pink-500 p-3 shadow-lg min-w-50'&amp;gt;Create Withdrawal Request&amp;lt;/button&amp;gt;
                        &amp;lt;/Link&amp;gt;
&amp;lt;Link href={`requests/${projectID}`}&amp;gt;
                            &amp;lt;button className='rounded-md mt-20 my-10 bg-white text-pink-500 p-3 shadow-lg flex-wrap'&amp;gt;Approve / Reject / Withdraw&amp;lt;/button&amp;gt;
                        &amp;lt;/Link&amp;gt;
                    &amp;lt;/div&amp;gt;
&amp;lt;div className='grid grid-cols-1 px-10'&amp;gt;
                        &amp;lt;button onClick={changeStateToExpire} className='rounded-md mt-20 my-10 bg-white text-pink-500 p-3 shadow-lg w-50'&amp;gt;Click here if fundraise needs to be expired (contributors only)&amp;lt;/button&amp;gt;
&amp;lt;button onClick={processRefund} className='rounded-md mt-20 my-10 bg-white text-pink-500 p-3 shadow-lg w-50'&amp;gt;Request Refund&amp;lt;/button&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div &amp;gt;
    )
}
// 2.
export async function getStaticPaths() {
    let provider = new ethers.providers.JsonRpcProvider(`https://rinkeby.infura.io/v3/${infuraKey}`)
// let provider = new ethers.providers.JsonRpcProvider()
    const contract = new ethers.Contract(contractAddress, CrowdFund.abi, provider)
    const data = await contract.getAllProjects()
// populate the dynamic routes with the id
    const paths = data.map(d =&amp;gt; ({ params: { id: BigNumber.from(d[0]).toString() } }))
return {
        paths,
        fallback: true
    }
}
// 3.
// local fetch - change to ropsten/mainnet on deployement time
export async function getStaticProps({ params }) {
    // isolate ID from params
    const { id } = params
    // contact the blockchain
    let provider = new ethers.providers.JsonRpcProvider(`https://rinkeby.infura.io/v3/${infuraKey}`)
     // localhost
    // let provider = new ethers.providers.JsonRpcProvider()
    const contract = new ethers.Contract(contractAddress, CrowdFund.abi, provider)
    const data = await contract.getProjectDetails(id);
    // parse received data into JSON
    let projectData = {
        creator: data.creator,
        name: data.name,
        description: data.description,
        projectDeadline: BigNumber.from(data.projectDeadline).toNumber(),
        totalPledged: ethers.utils.formatEther(data.totalPledged),
        goal: ethers.utils.formatEther(data.goal),
        totalDepositors: BigNumber.from(data.totalDepositors).toNumber(),
        totalWithdrawn: ethers.utils.formatEther(data.totalWithdrawn),
        currentState: data.currentState
    }
// return JSON data belonging to this route
    return {
        props: {
            project: projectData,
            projectID: id
        },
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;while routing is happening, we need a fallback otherwise the page has nothing to render and errors out&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;fetches the project IDs and tells nextJS what the ID route will be hooking on to. in our case it is the project’s ID.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;next we pass the params obtained in the last getStaticPaths call to fetch data of the project. We create a JSON object and returns it as props which gets pulled into the main function’s input at the top.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the project is a success, the creator needs to submit a withdrawal request. For that we create a new folder in the project directory called withdrawal. Inside this we create a file called [id].js and&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { BigNumber, ethers } from 'ethers'
import { useEffect, useState } from 'react'
import Web3Modal from 'web3modal'
import { useRouter } from 'next/router'
import {
    contractAddress
} from '../../../config'
import CrowdFund from "../../../build/contracts/CrowdFund.json"
const infuraKey = process.env.PROJECT_ID;
const initialState = { requestNo: '', requestDescription: '', amount: 0 };
export default function Withdrawal({ projectID }) {
    const router = useRouter()
    const [withdrawalRequest, setWithdrawalRequest] =    useState(initialState)
    const [contract, setContract] = useState();
    useEffect(() =&amp;gt; {
        // function to get contract address and update state
        async function getContract() {
            const web3Modal = new Web3Modal()
            const connection = await web3Modal.connect()
            const provider = new ethers.providers.Web3Provider(connection)
            const signer = provider.getSigner()
            let _contract = new ethers.Contract(contractAddress, CrowdFund.abi, signer)
            setContract(_contract);
        }
        getContract();
    })
     async function requestWithdrawal() {
        const { requestNo, requestDescription, amount } = withdrawalRequest;
        try {
            let transaction = await contract.createWithdrawalRequest(projectID, requestNo, requestDescription, amount)
            const x = await transaction.wait()
            if (x.status == 1) {
                router.push(`/project/${projectID}`)
            }
        } catch (err) {
            window.alert(err.message)
        }
    }
    if (router.isFallback) {
        return &amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;
    }
    return (
        &amp;lt;div className='grid sm:grid-cols-1 lg:grid-cols-1 mt-20 '&amp;gt;
            &amp;lt;p className='text-center'&amp;gt;Only project creator can access this functionality on goal reached&amp;lt;/p&amp;gt;
            &amp;lt;div className='bg-pink-500 text-black p-20 text-center rounded-md mx-5 flex flex-col'&amp;gt;
                &amp;lt;input
                    type='number'
                    onChange={e =&amp;gt; setWithdrawalRequest({ ...withdrawalRequest, requestNo: e.target.value })}
                    name='requestNo'
                    placeholder='Request number...'
                    className='p-2 mt-5 rounded-md'
                    value={withdrawalRequest.requestNo} /&amp;gt;
                &amp;lt;textarea
                    onChange={e =&amp;gt; setWithdrawalRequest({ ...withdrawalRequest, requestDescription: e.target.value })}
                    name='requestDescription'
                    placeholder='Give it a description ...'
                    className='p-2 mt-5 rounded-md'
                /&amp;gt;
                &amp;lt;input
                    onChange={e =&amp;gt; setWithdrawalRequest({ ...withdrawalRequest, amount: e.target.value })}
                    name='amount'
                    placeholder='Withdrawal amount ... (in ETH). Only integer values are valid'
                    className='p-2 mt-5 rounded-md'
                /&amp;gt;
                &amp;lt;button type='button' className="w-20 bg-white rounded-md my-10 px-3 py-2 shadow-lg border-2" onClick={requestWithdrawal}&amp;gt;Submit&amp;lt;/button&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div &amp;gt;
    )
}
export async function getStaticPaths() {
    let provider = new ethers.providers.JsonRpcProvider(`https://rinkeby.infura.io/v3/${infuraKey}`)
// localhost 
    // let provider = new ethers.providers.JsonRpcProvider()
    const contract = new ethers.Contract(contractAddress, CrowdFund.abi, provider)
    const data = await contract.getAllProjects()
// populate the dynamic routes with the id
    const paths = data.map(d =&amp;gt; ({ params: { id: BigNumber.from(d[0]).toString() } }))
return {
        paths,
        fallback: true
    }
}
// local fetch - change to ropsten/mainnet on deployement time
export async function getStaticProps({ params }) {
    // isolate ID from params
    const { id } = params
// return JSON data belonging to this route
    return {
        props: {
            projectID: id
        },
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the withdrawal requests that have been created need to separated out and individually approved or rejected and the creator needs to be able to withdraw the sum if request gets approved.&lt;/p&gt;

&lt;p&gt;In the project directory, create a new directory called requests. Create a new file called [id].js and&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ethers, BigNumber } from 'ethers'
import Web3Modal from 'web3modal'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import {
    contractAddress
} from '../../../config'
import CrowdFund from "../../../build/contracts/CrowdFund.json"
const infuraKey = process.env.PROJECT_ID;
export default function Requests({ project, projectID }) {
    const router = useRouter()
    const [withdrawalRequests, setWithdrawalRequests] = useState([])
    const [contract, setContract] = useState();
    useEffect(() =&amp;gt; {
        // function to get contract address and update state
        async function getContract() {
            const web3Modal = new Web3Modal()
            const connection = await web3Modal.connect()
            const provider = new ethers.providers.Web3Provider(connection)
            const signer = provider.getSigner()
            let _contract = new ethers.Contract(contractAddress, CrowdFund.abi, signer)
            setContract(_contract);
        }
        getContract();
    })

    // function to get all requests made by the creator
    async function getRequests() {
        try {
            let x = await contract.getAllWithdrawalRequests(projectID)
            setWithdrawalRequests(x);
        } catch (err) {
            window.alert(err.message)
        }
    }

    // function to approve a specific request
    async function approveRequest(r, projectID) {
        try {
            let tx = await contract.approveWithdrawalRequest(projectID, r[0])
            let x = await tx.wait()
if (x.status == 1) {
                router.push(`/project/${projectID}`)
            }
        } catch (err) {
            window.alert(err.message)
        }
    }

    // function to reje a specific request
    async function rejectRequest(r, projectID) {
        try {
            let tx = await contract.rejectWithdrawalRequest(projectID, r[0])
            let x = await tx.wait()
if (x.status == 1) {
                router.push(`/project/${projectID}`)
            }
        } catch (err) {
            window.alert(err.message)
        }
    }  
    // function to transfer funds to the creator if requests were approved
    async function transferFunds(r, projectID) {
        try {
            let tx = await contract.transferWithdrawalRequestFunds(projectID, r[0])
            let x = await tx.wait()
if (x.status == 1) {
                router.push(`/project/${projectID}`)
            }
        } catch (err) {
            window.alert(err.message)
        }
    }
    if (router.isFallback) {
        return &amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;
    }
    return (
        &amp;lt;div className='grid sm:grid-cols-1 lg:grid-cols-1 mt-20 '&amp;gt;
            &amp;lt;p className='text-center'&amp;gt;Only project contributors can access approve/reject functionality&amp;lt;/p&amp;gt;
            &amp;lt;p className='text-center mt-3 mb-3'&amp;gt;Creators need more than 50% of the contributors to approve a request before withdrawal can be made&amp;lt;/p&amp;gt;
&amp;lt;div className='bg-pink-500 text-white p-20 text-center rounded-md'&amp;gt;
                &amp;lt;button onClick={getRequests} className="bg-white text-black rounded-md my-10 px-3 py-2 shadow-lg border-2 w-80"&amp;gt;Get all withdrawal requests&amp;lt;/button&amp;gt;
&amp;lt;p className='font-bold'&amp;gt;All Withdrawal requests&amp;lt;/p&amp;gt;
&amp;lt;div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 pt-6'&amp;gt;
                    {
                        withdrawalRequests.map(request =&amp;gt;
                            &amp;lt;div className='border shadow rounded-xl text-left grid grid-cols-1 lg:grid-col-2' key={request[0]}&amp;gt;
                                &amp;lt;div className='p-4'&amp;gt;
                                    &amp;lt;p className='py-4'&amp;gt;request number: {request[0]}&amp;lt;/p&amp;gt;
                                    &amp;lt;p className='py-4 break-words'&amp;gt;description: {request[1]}&amp;lt;/p&amp;gt;
                                    &amp;lt;p className='py-4'&amp;gt;amount: {ethers.utils.formatEther(request[2])} ETH&amp;lt;/p&amp;gt;
                                    &amp;lt;p className='py-4'&amp;gt;total approvals: {BigNumber.from(request[4]).toNumber()}&amp;lt;/p&amp;gt;
                                    &amp;lt;p className='py-4'&amp;gt;total depositor: {project.totalDepositors}&amp;lt;/p&amp;gt;
&amp;lt;div className='sm:grid sm:grid-cols-1 xs:grid xs:grid-cols-1'&amp;gt;
                                        &amp;lt;button onClick={() =&amp;gt; approveRequest(request, projectID)} className="bg-white text-black rounded-md my-10 mx-1 px-3 py-2 shadow-lg border-2"&amp;gt;Approve&amp;lt;/button&amp;gt;
&amp;lt;button onClick={() =&amp;gt; rejectRequest(request, projectID)} className="bg-white text-black rounded-md my-10 px-3 mx-1 py-2 shadow-lg border-2"&amp;gt;Reject&amp;lt;/button&amp;gt;
&amp;lt;button onClick={() =&amp;gt; transferFunds(request, projectID)} className="bg-white text-black rounded-md my-10 px-3 mx-1 py-2 shadow-lg border-2"&amp;gt;Withdraw&amp;lt;/button&amp;gt;
                                    &amp;lt;/div&amp;gt;
                                &amp;lt;/div&amp;gt;
                            &amp;lt;/div&amp;gt;
                        )
                    }
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div &amp;gt;
    )
}
export async function getStaticPaths() {
    let provider = new ethers.providers.JsonRpcProvider(`https://rinkeby.infura.io/v3/${infuraKey}`)
// localhost
    // let provider = new ethers.providers.JsonRpcProvider()vvvvv
    const contract = new ethers.Contract(contractAddress, CrowdFund.abi, provider)
    const data = await contract.getAllProjects()
// populate the dynamic routes with the id
    const paths = data.map(d =&amp;gt; ({ params: { id: BigNumber.from(d[0]).toString() } }))
return {
        paths,
        fallback: true
    }
}
// local fetch - change to ropsten/mainnet on deployement time
export async function getStaticProps({ params }) {
    // isolate ID from params
    const { id } = params
// contact the blockchain
    let provider = new ethers.providers.JsonRpcProvider(`https://rinkeby.infura.io/v3/${infuraKey}`)
// localhost
    // let provider = new ethers.providers.JsonRpcProvider()
const contract = new ethers.Contract(contractAddress, CrowdFund.abi, provider)
    const data = await contract.getProjectDetails(id);
// parse received data into JSON
    let projectData = {
        creator: data.creator,
        name: data.name,
        description: data.description,
        projectDeadline: BigNumber.from(data.projectDeadline).toNumber(),
        totalPledged: ethers.utils.formatEther(data.totalPledged),
        goal: ethers.utils.formatEther(data.goal),
        totalDepositors: BigNumber.from(data.totalDepositors).toNumber(),
        totalWithdrawn: ethers.utils.formatEther(data.totalWithdrawn),
        currentState: data.currentState
    }
// return JSON data belonging to this route
    return {
        props: {
            project: projectData,
            projectID: id
        },
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly we create a 404 page for routes that do not exist. In the pages directory create a file named 404.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const notFound = () =&amp;gt; {
  return (
    &amp;lt;div className='flex h-screen'&amp;gt;
        &amp;lt;p className='m-auto'&amp;gt;404 Not Found&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
export default notFound
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this finishes up the code. Run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to create an optimized build. Push to github and deploy to vercel, and you have a dapp ready to be interacted with.&lt;/p&gt;

&lt;p&gt;I hope you got some use out of this. If you did, please help your boy out at&lt;/p&gt;

&lt;p&gt;(ETH) 0xF1128dFF5816f80241D6E7de4f3Cc5B5E4c5A819&lt;/p&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;p&gt;(BTC) 1E8GcSLrzqFBZuxCPSodBg3P8XUV6GXaRK&lt;/p&gt;

&lt;p&gt;until next time 🖖&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>blockchain</category>
      <category>nextjs</category>
    </item>
  </channel>
</rss>
