X?LlekW91Akt@-MfMnus4Se<#p
zHl~9%oyOHoJmknJ_oar1_6!iYwMa=?1TAT6A^a<>i?xj68HW_Dj0J(`oV8EknvV;MG6)YN2!rF(-2fq0WO-`b3W-N;GX
zrH0#xj-c_FArTV8-Y`dJD?KMmnB*Bp3`D|26}G~swR}oAIO*@u0ijb|tNm?vVydcB
zJoYOJnl_jR+xEq@Hsh5CQ9lFKz8mwj`xZ7SG42O~l+W5@{S5nf&DmPinJST^!&7xn
zb=@dsza*CxG+PIaQWd
z5if64$SccP_%VOh1935TdC{9LSp-X+E7*smY8T^qiFejy9l2m|FWCToa*}IXyBpp-
z%lSHT4k*sR@@0bX(te26W=OIlxqHO_uQajnMN>f2E#^m&(7oMXQB&Rf$6$qIN%K=}
z(@?J+cN;p&IcnCBlnX4?_=FeYRgCqlypr_nZ2LKbo2#Spd|#LNlrBftU1e-8fz2ZX
zhxp&u{8R=~(%lv6vS=!jLU+D5K_=No&g6oIb%TCs?}@<0;m~Qu~XIrJ0IbaILozv
z@|T5-w-!fI|CiwiOsE1^a;Hw-XmL(s`n-NWjRl)b
zUsZmEcyi>9jeSL062bR;=UB`M`t|6Nr0z0E!#m&Zs{NRxOr^U?Yf{r1jsvYUBA;FX
zU6YUmpFC@s`zvxvn)=+slL+|S>^mL8Bf7;(__p+7FAE`wo~WjDmM9&2E9JUkl0%C$
z^slkNT-rr{(_9MG1bRnHO_H0l&r{K`KX~5(&tDQf^mTG%knTGgLkv-SI|QDilmEd)%trF^w-hl#-Q7ql{Gq*R(MTogji-$
z&on1pOYqmwihdKzzXQH8IIG^@R$0<$G@EU1ON^`#h_A~LvWQzx$`$)eLQSG`G<VaeYCD&0z@&<{
z94FD=uLTz03fogu)DiLXdTI-t>ru`LS^F&bVC5w0wvY@j@
zL==n|Twj5bpRUegi%F5*4`lj&JPbo2xep&zDQUd6iit4|#jvQfb!AVH@zEx0mfXat
zY}`s55$1#T%FbLH?W_9rF_gqjGd)pWWQ}=k38eX;1w}hNldTaj$Pf^|LEo)(eSnmg
zs~um!z(a}0?Y^AKHMf>c4*qOXnUG$`W{oKy`t1|mPznVl519O!UPs7Y+m^8_O6@|_
z%77w4)9HoA)GK?avL1h=>TiUXC3~6Aa+6e1cyU9BQX~ZS<_HSA!doPqcL{G-QiH1cb%g~-t-P|u
z{?v3%b~?eCv_{)96EkMIS)Uy8T&n~lXUzrqG6GmKz1_4${W2O@qJAaTP-oFJ*ca`!
za}t+PD_eONiMkjIDB`>WP%~aH-$+@X_Z3}?^&N03BdOU=D)(||Gcu~!+J72qE~8B#
zg{>>}qR@PF$Y+FKM*hzHDObJ7R#!@cMKxapukVTgQok;gW5UMSW)Y1<{!<}DoSp3y
zq1O;h>e8aNr)lKMOP_vBr`U|}L!2J1=0zZsOyI%XbCcoYmtw6nD{4%80n3T;v}Kuv
z<2L8u3f0UM5Bf$1!TEI!c#K24iTb@6as73$j}BpBXO66cY>bZhOK(!fM+tjQ%GGFL
zD@=70nxOG>hB>)sO&l&4lHY*m!Vd9l!LjBnV9n2q$Xi>CGn}GFX1wZOr;{iDB=ZEeqUU?Rdl5{!Dw3$
z
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
literal 0
HcmV?d00001
diff --git a/HeadlineHackathon/Dreamf1re_hack/constants.py b/HeadlineHackathon/Dreamf1re_hack/constants.py
new file mode 100644
index 0000000..03ab12e
--- /dev/null
+++ b/HeadlineHackathon/Dreamf1re_hack/constants.py
@@ -0,0 +1,4 @@
+ALGONODE_NODE_ADDRESS = "http://testnet-api.algonode.network"
+ALGONODE_INDX_ADDRESS = "http://testnet-idx.algonode.network"
+ALGOEXPL_NODE_ADDRESS = "https://node.testnet.algoexplorerapi.io"
+ALGOEXPL_INDX_ADDRESS = "https://algoindexer.testnet.algoexplorerapi.io"
diff --git a/HeadlineHackathon/Dreamf1re_hack/deploy.py b/HeadlineHackathon/Dreamf1re_hack/deploy.py
new file mode 100644
index 0000000..2fdf2c7
--- /dev/null
+++ b/HeadlineHackathon/Dreamf1re_hack/deploy.py
@@ -0,0 +1,531 @@
+# In production, an escrow account must be newly generated and
+# funded to delegate uploading of .txt files. A user must pay
+# the fees required to upload the file
+# In this demo, a test account is set and is pre-funded
+# to show functionality.
+
+from upload import *
+from pywebio.input import *
+from pywebio.output import *
+from pywebio.session import *
+from pywebio.platform.tornado import start_server
+from qrcode import QRCode, constants
+from cv2 import cv2
+import base64
+import os
+from util import TEST_SENDER_PRIVATE_KEY, \
+ TEST_SENDER_ADDRESS, search_note_by_txid, get_lines, init_get_client
+from checking import check_if_connection_exists
+from PIL import Image
+
+
+class Veritas:
+ def __init__(self):
+ self.sender_address = None
+ self.original_file = None
+ self.alpha_fn = None
+ self.alpha = None
+ self.filename = None
+ self.transaction_ids = None
+ self.receiver_address = self.sender_address
+ self.sender_private_key = TEST_SENDER_PRIVATE_KEY
+
+ @use_scope("upload")
+ def to_blockchain(self):
+ self.sender_address = TEST_SENDER_ADDRESS
+ self.receiver_address = self.sender_address
+ # Remove scopes
+ remove("download")
+ remove("selector")
+ remove("manage")
+ put_button(
+ label="Reload",
+ onclick=self.reload_page
+ )
+ # Show file upload
+ file = file_upload(
+ label="Find your text file",
+ required=True,
+ accept=[".txt"]
+ )
+ # Write
+ self.filename = file['filename']
+ open(self.filename, 'wb').write(file['content'])
+ obtained_from_local = open(self.filename, 'rb').read()
+ while True:
+ if (self.filename and obtained_from_local) is not None:
+ break
+ # Await payment from user before uploading
+ # Compute cost - A payment of choice can also
+ # be included on top of the base cost
+ cost = self.compute_cost(self.filename)
+ put_text(f"Estimated Cost: {round(cost, 5)} ALGO")
+ # Start upload
+ file_id = self.custom_upload(
+ filename=self.filename,
+ sender_address=self.sender_address,
+ receiver_address=self.receiver_address,
+ sender_private_key=self.sender_private_key
+ )
+ put_html(
+ f"""
+
+ Successfully made {self.filename} permanent.
+ Scan this QR Code to get the file.
+ Transaction: {file_id}
+
+ """
+ )
+ # Add data to QR Code and make
+ basewidth = 100
+ choice_logo = Image.open("choice_logo.jpg")
+
+ wpercent = (basewidth / float(choice_logo.size[0]))
+ hsize = int((float(choice_logo.size[1]) * float(wpercent)))
+ choice_logo = choice_logo.resize((basewidth, hsize))
+ qrc = QRCode(
+ error_correction=constants.ERROR_CORRECT_H
+ )
+
+ qrc.add_data(file_id)
+ qrc.make()
+ qrimg = qrc.make_image(
+ fill_color="black", back_color="white").convert('RGB')
+ pos = ((qrimg.size[0] - choice_logo.size[0]) // 2,
+ (qrimg.size[1] - choice_logo.size[1]) // 2)
+
+ qrimg.paste(choice_logo, pos)
+
+ # Save QR Code
+ qr_code_saved_fname = f"{self.filename}-QR.png"
+ qrimg.save(qr_code_saved_fname)
+
+ # Open QR Code and display
+ with open(qr_code_saved_fname, 'rb') as qrimg_:
+ __qr__ = qrimg_.read()
+ if __qr__ is not None:
+ put_image(
+ src=__qr__,
+ format="png",
+ title="QR Code",
+ width="185",
+ height="185",
+ )
+ put_file(
+ name=qr_code_saved_fname,
+ content=__qr__,
+ label=f"Download QR"
+ )
+ put_link(
+ name="See on Algoexplorer",
+ url=f"https://testnet.algoexplorer.io/tx/{file_id}",
+ new_window=True
+ )
+ os.remove(self.filename)
+ os.remove(qr_code_saved_fname)
+
+ @use_scope("download")
+ def from_blockchain(self):
+ # Remove scopes
+ remove("upload")
+ remove("selector")
+ remove("manage")
+ put_button(
+ label="Reload",
+ onclick=self.reload_page,
+ position=0
+ )
+ # Get output
+ qrcode = file_upload(
+ label="Locate QR Code.",
+ accept=".png",
+ required=True
+ )
+ pb_download = "downloadprog"
+ put_processbar(
+ name=pb_download,
+ init=0,
+ label="Setting the truth free...",
+ auto_close=True
+ )
+ filename = qrcode['filename']
+ open(filename, 'wb').write(qrcode['content'])
+ obtained_from_local = open(filename, 'rb').read()
+ while True:
+ if (qrcode and obtained_from_local) is not None:
+ break
+ # Decode QR
+ qr_ = cv2.imread(filename)
+ qr__ = cv2.QRCodeDetector()
+ file_id, _, _ = qr__.detectAndDecode(qr_)
+ # Initiate download
+ # Initialize stuff to be used later
+ get_client = init_get_client()
+ remnant = []
+ first = True
+ connection = None
+ fno = None
+ file_name_decoded = None
+ # Repeat until there is no Connection left.
+ # A Connection is the Transaction ID
+ # included at the end of a note to serve as
+ # a link to the preceding note.
+ set_processbar(
+ name=pb_download,
+ value=0.25,
+ label="Setting the truth free..."
+ )
+ while True:
+ if first:
+ gotten = search_note_by_txid(
+ get_client=get_client,
+ txid=file_id
+ )
+ first = False
+ else:
+ gotten = search_note_by_txid(
+ get_client=get_client,
+ txid=connection
+ )
+ if gotten != "":
+ has_connection = check_if_connection_exists(gotten)
+ # Check if a Transaction ID is
+ # expected to be found in the note,
+ # thus hinting that there is a preceding
+ # note. if "" is found in the note,
+ # it means that the there are no more
+ # preceding notes.
+ if has_connection and not ("" in gotten):
+ connection = gotten[(len(gotten) - 1) - 51:]
+ actual = gotten[:(len(gotten) - 1) - 51]
+ remnant.append(actual)
+ else:
+ actual = gotten[:]
+ remnant.append(actual)
+ break
+ else:
+ break
+ # Arrange the reference line
+ # to link other Transaction IDs
+ set_processbar(
+ name=pb_download,
+ value=0.25,
+ label="Setting the truth free..."
+ )
+ remnant.reverse()
+ omega = ""
+ for particle in remnant:
+ omega += particle
+ if "" in particle:
+ sidx = particle.index("")
+ idxstart_of_fn = sidx + 4
+ idxend_of_fn = idxstart_of_fn
+ # Get index of end of filename
+ while particle[idxend_of_fn] != "<":
+ idxend_of_fn += 1
+ # Get filename
+ fno = particle[idxstart_of_fn:idxend_of_fn]
+ file_name_decoded = base64.b64decode(fno.encode()).decode('iso-8859-1')
+ print(f"File name: {file_name_decoded} ")
+ print(f"File ID: {file_id}")
+ print(f"File description: ")
+ # An algorithm can be inserted here to get
+ # the file description if there is one included
+ set_processbar(
+ name=pb_download,
+ value=0.50,
+ label=f"Getting {file_name_decoded}..."
+ )
+ filename_whole = f"{fno}"
+ if filename_whole in omega:
+ omega = omega.replace(filename_whole, "")
+ else:
+ print("Cannot edit omega")
+ transaction_ids = get_lines(
+ note=omega,
+ max_length=52
+ )
+ while True:
+ try:
+ # Get Transaction IDs from the
+ # Transaction IDs obtained from the File ID
+ txn_ids = get_txn_ids_from_txn_id(
+ __txids=transaction_ids,
+ client=get_client
+ )
+ set_processbar(
+ name=pb_download,
+ value=0.75,
+ label=f"Getting {file_name_decoded}..."
+ )
+ # Download the file from the blockchain
+ downloaded_file = stitch_records(
+ get_client=get_client,
+ txn_ids=txn_ids
+ )
+ set_processbar(
+ name=pb_download,
+ value=0.85,
+ label=f"Getting {file_name_decoded}..."
+ )
+ break
+ except Exception as err:
+ print(err.args)
+ set_processbar(
+ name=pb_download,
+ value=1,
+ label=f"Getting {file_name_decoded}..."
+ )
+ filedld = downloaded_file
+ filedldname = file_name_decoded
+ # End download
+ put_text(f"Successfully obtained {filedldname}.")
+ put_file(
+ name=filedldname,
+ content=base64.b64decode(filedld).decode().encode("ISO-8859-1"),
+ label=f"Download file"
+ )
+ put_text("Text:")
+ put_scrollable(
+ base64.b64decode(filedld).decode().encode("ISO-8859-1").decode(),
+ height=800
+ )
+ os.remove(filename)
+
+ @use_scope("selector")
+ def selector(self):
+ remove("init")
+ remove("upload")
+ remove("download")
+ # Add buttons
+ put_button(
+ label="Make something permanent",
+ onclick=self.to_blockchain,
+ )
+ put_button(
+ label="Retrieve document",
+ onclick=self.from_blockchain,
+ )
+
+ def root(self):
+ remove("main")
+ remove("connect")
+ remove("init")
+ put_scope("main")
+ put_scope("connect")
+ # Set title
+ set_env(title="Choice Texts")
+ # Introduction
+ choice_logo = open("choice_logo.jpg", "rb").read()
+ put_grid(
+ [
+ [
+ put_html(
+ """
+
+
+ Immutable, transparent
+ records on the blockchain
+
+
+ Choice Texts allows you
+ to make text files permanent
+ on the Algorand public blockchain.
+
+ Now there is a way to preserve
+ our stories for generations
+ and generations to come.
+
+
+ """,
+ ),
+ put_image(
+ src=choice_logo,
+ format=".jpg",
+ width="250",
+ height="250",
+ title="Choice Logo"
+ )
+ ]
+ ],
+ scope="main"
+ )
+
+ with use_scope("init") as init:
+ put_button(
+ label="Begin",
+ onclick=self.selector,
+ scope=init
+ )
+
+ def custom_upload(
+ self,
+ filename: str,
+ sender_address: str,
+ receiver_address: str,
+ sender_private_key: str
+ ):
+ # Init POST Client
+ post_client = init_post_client()
+ # Init process bar
+ process_bar_name = "uploadprog"
+ pbvi = 1 / 7
+ put_processbar(
+ name=process_bar_name,
+ init=0,
+ label="Making sure things will never change...",
+ auto_close=True
+ )
+ # Open file
+ # ( Progress - Task #1 )
+ set_processbar(name=process_bar_name, value=pbvi)
+ with open(filename, 'rb') as o:
+ self.original_file = o.read().decode('ISO-8859-1')
+ self.original_file = base64.b64encode(self.original_file.encode()).decode()
+ if self.original_file is not None:
+
+ # Get Transaction IDs submitted to the blockchain
+ # ( Progress - Task #2 )
+ pbvi += pbvi
+ set_processbar(
+ name=process_bar_name, value=pbvi,
+ label="Making sure things will never change..."
+ )
+ self.transaction_ids = process_publishing(
+ feed=self.original_file,
+ receiver_address=receiver_address,
+ sender_address=sender_address,
+ sender_private_key=self.sender_private_key
+ )
+ # Initialize GET Client
+ get_client = init_get_client()
+ # Loop until downloaded data is exactly
+ # the same with the uploaded data
+ # ( Progress - Task #3 )
+ pbvi += pbvi
+ set_processbar(
+ name=process_bar_name, value=pbvi,
+ label="Making sure things will never change..."
+ )
+ while True:
+ # Get Transaction IDs from Transaction IDs
+ txn_ids = get_txn_ids_from_txn_id(
+ __txids=self.transaction_ids,
+ client=get_client
+ )
+ # Download the uploaded file from the blockchain
+ downloaded_file = stitch_records(
+ get_client=get_client,
+ txn_ids=txn_ids
+ )
+ # Check if the uploaded file
+ # and downloaded file are the same
+ # and return Transaction IDs from
+ # upload procedure if so
+ circular = check_circular(
+ original=self.original_file,
+ stitched=downloaded_file
+ )
+ if circular:
+ print('File successfully uploaded to blockchain.')
+ break
+ # ( Progress - Task #4 )
+ # Get File ID and second cost
+ pbvi += pbvi
+ set_processbar(
+ name=process_bar_name, value=pbvi,
+ label="Making sure things will never change..."
+ )
+ print(f"Assigning File ID...Please Wait.")
+ alpha_fn = base64.b64encode(filename.encode()).decode()
+ alpha = f"{alpha_fn}"
+ for txid in self.transaction_ids:
+ alpha += txid
+ # ( Progress - Task #5 )
+ pbvi += pbvi
+ set_processbar(
+ name=process_bar_name, value=pbvi,
+ label="Making sure things will never change..."
+ )
+ feed = get_lines(
+ note=alpha,
+ max_length=947
+ )
+ txid = None
+ # ( Progress - Task #6 )
+ pbvi += pbvi
+ set_processbar(
+ name=process_bar_name, value=pbvi,
+ label="Making sure things will never change..."
+ )
+ for each in feed:
+ if len(each) != 0:
+ if len(feed) > 1:
+ if txid is None:
+ txn = create_transaction(
+ post_client,
+ receiver_address,
+ sender_address,
+ message=each
+ )
+ else:
+ txn = create_transaction(
+ post_client,
+ receiver_address,
+ sender_address,
+ message=each + txid
+ )
+ sgd = txn.sign(sender_private_key)
+ txid = post_client.send_transaction(sgd)
+ transaction.wait_for_confirmation(
+ algod_client=post_client,
+ txid=txid
+ )
+ else:
+ txn = create_transaction(
+ post_client,
+ receiver_address,
+ sender_address,
+ message=each
+ )
+ sgd = txn.sign(sender_private_key)
+ txid = post_client.send_transaction(sgd)
+ transaction.wait_for_confirmation(
+ algod_client=post_client,
+ txid=txid
+ )
+ # ( Progress - Task #7 )
+ pbvi += pbvi
+ set_processbar(
+ name=process_bar_name, value=1,
+ label="Making sure things will never change..."
+ )
+ return txid
+ else:
+ raise Exception("Error: original file is {None}")
+
+ @staticmethod
+ def reload_page():
+ run_js("window.location.reload();")
+
+ def compute_cost(self, filename):
+ with open(filename, 'rb') as o:
+ self.original_file = o.read().decode('ISO-8859-1')
+ self.original_file = base64.b64encode(self.original_file.encode()).decode()
+ lines = get_lines(self.original_file, max_length=947)
+ algocost1 = len(lines) * 0.001
+ groups = len(lines) / 16
+ algocost2 = groups * 0.001
+ total_cost = algocost2 + algocost1
+ return total_cost
+
+
+if __name__ == "__main__":
+ v = Veritas()
+ start_server(
+ applications=v.root,
+ port=0,
+ host="",
+ debug=True,
+ auto_open_webbrowser=True
+ )
diff --git a/HeadlineHackathon/Dreamf1re_hack/download.py b/HeadlineHackathon/Dreamf1re_hack/download.py
new file mode 100644
index 0000000..bed3128
--- /dev/null
+++ b/HeadlineHackathon/Dreamf1re_hack/download.py
@@ -0,0 +1,155 @@
+from util import init_get_client, search_note_by_txid, get_lines, get_txn_ids_from_txn_id
+import base64
+from checking import check_if_connection_exists
+from stitching import stitch_records
+from pywebio.output import set_processbar, put_processbar
+
+
+# Main download procedure
+def download(file_id: str):
+ """
+ Download file from blockchain using the
+ File ID generated from upload procedure
+
+ :param file_id: The link which is a File ID generated from upload.
+ :return: None, after downloading, the downloaded file will be in the same directory.
+ """
+ # Initialize stuff to be used later
+ process_bar_name = "downloadprog"
+ put_processbar(
+ name=process_bar_name,
+ init=0,
+ label="Setting the truth free...",
+ auto_close=True
+ )
+ pbd = 1/4
+ set_processbar(
+ name=process_bar_name,
+ value=pbd,
+ label="Setting the truth free..."
+ )
+ get_client = init_get_client()
+ remnant = []
+ first = True
+ connection = None
+ fno = None
+ file_name_decoded = None
+ # Repeat until there is no Connection left.
+ # A Connection is the Transaction ID
+ # included at the end of a note to serve as
+ # a link to the preceding note.
+ pbd += pbd
+ set_processbar(
+ name=process_bar_name,
+ value=pbd,
+ label="Setting the truth free..."
+ )
+ while True:
+ if first:
+ gotten = search_note_by_txid(
+ get_client=get_client,
+ txid=file_id
+ )
+ first = False
+ else:
+ gotten = search_note_by_txid(
+ get_client=get_client,
+ txid=connection
+ )
+ if gotten != "":
+ has_connection = check_if_connection_exists(gotten)
+ # Check if a Transaction ID is
+ # expected to be found in the note,
+ # thus hinting that there is a preceding
+ # note. if "" is found in the note,
+ # it means that the there are no more
+ # preceding notes.
+ if has_connection and not ("" in gotten):
+ connection = gotten[(len(gotten)-1)-51:]
+ actual = gotten[:(len(gotten)-1)-51]
+ remnant.append(actual)
+ else:
+ actual = gotten[:]
+ remnant.append(actual)
+ break
+ else:
+ break
+ # Arrange the reference line
+ # to link other Transaction IDs
+ pbd += pbd
+ set_processbar(
+ name=process_bar_name,
+ value=pbd,
+ label="Setting the truth free..."
+ )
+ remnant.reverse()
+ omega = ""
+ for particle in remnant:
+ omega += particle
+ if "" in particle:
+ sidx = particle.index("")
+ idxstart_of_fn = sidx + 4
+ idxend_of_fn = idxstart_of_fn
+ # Get index of end of filename
+ while particle[idxend_of_fn] != "<":
+ idxend_of_fn += 1
+ # Get filename
+ fno = particle[idxstart_of_fn:idxend_of_fn]
+ file_name_decoded = base64.b64decode(fno.encode()).decode('iso-8859-1')
+ print(f"File name: {file_name_decoded} ")
+ print(f"File ID: {file_id}")
+ print(f"File description: ")
+ # An algorithm can be inserted here to get
+ # the file description if there is one included
+ pbd += pbd
+ set_processbar(
+ name=process_bar_name,
+ value=pbd,
+ label="Setting the truth free..."
+ )
+ filename_whole = f"{fno}"
+ if filename_whole in omega:
+ omega = omega.replace(filename_whole, "")
+ else:
+ print("Cannot edit omega")
+ transaction_ids = get_lines(
+ note=omega,
+ max_length=52
+ )
+ pbd += pbd
+ set_processbar(
+ name=process_bar_name,
+ value=pbd,
+ label="Setting the truth free..."
+ )
+ while True:
+ try:
+ # Get Transaction IDs from the
+ # Transaction IDs obtained from the File ID
+ txn_ids = get_txn_ids_from_txn_id(
+ __txids=transaction_ids,
+ client=get_client
+ )
+ # Download the file from the blockchain
+ downloaded_file = stitch_records(
+ get_client=get_client,
+ txn_ids=txn_ids
+ )
+ # Return if finished
+ return downloaded_file, file_name_decoded
+ except Exception as err:
+ print(err.args)
+
+
+# Writes data to disk
+def write_to_file(input_data: str, file_name_out: str):
+ """
+ Write the downloaded file from blockchain to disk.
+
+ :param input_data: The downloaded data from blockchain
+ :param file_name_out: Filename of output file
+ """
+ with open(file_name_out, 'wb') as f:
+ to_be = base64.b64decode(input_data).decode()
+ f.write(to_be.encode("ISO-8859-1"))
+ print(f'\nDownloaded {file_name_out} to current directory')
diff --git a/HeadlineHackathon/Dreamf1re_hack/requirements.txt b/HeadlineHackathon/Dreamf1re_hack/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..dbe3af128d4ad20c224c7b21c63878e69a0e96d4
GIT binary patch
literal 534
zcmY+B%}&EW5QBY2;!%p407V=)^^W=qA%TL(Pe~d|Umo~8OA?{g>|yP(J+nXGrCvC1
zdf;TbqHej@y3~bAb*iaeg&NA7HRw&08I1<*bpR{%K5Hb7@m3?8gU&OSE1$bNkY_lh
zKD7$gQLp&5!M2ODxHfoNZ%5KPGM$2R50+9JwaJ9#%!~c-QkELnHzZt!wJsvHZLpY_0r+@ziRu@fp
literal 0
HcmV?d00001
diff --git a/HeadlineHackathon/Dreamf1re_hack/stitching.py b/HeadlineHackathon/Dreamf1re_hack/stitching.py
new file mode 100644
index 0000000..2080440
--- /dev/null
+++ b/HeadlineHackathon/Dreamf1re_hack/stitching.py
@@ -0,0 +1,44 @@
+from util import search_note_by_txid
+from algosdk.v2client.algod import AlgodClient
+
+
+# This function is to assemble
+# the notes from each transaction
+# in the upload procedure to
+# produce the same uploaded file
+def stitch_records(
+ get_client: AlgodClient,
+ txn_ids: list,
+) -> str:
+ """
+ Stitches notes from raw Transaction IDs
+ obtained from the upload procedure
+
+ :param get_client: AlgodClient (GET)
+ :param txn_ids: Transaction IDs from upload procedure
+ :return: stitched - the Stitched Records (string)
+ """
+ stitched = ""
+ stitched_initial_list = []
+ for specific_txid in txn_ids:
+ while True:
+ # Search note based on given Transaction ID
+ obtained_note = search_note_by_txid(
+ get_client=get_client,
+ txid=specific_txid
+ )
+ if obtained_note is not None:
+ # Append to list if note is found
+ stitched_initial_list.append(obtained_note)
+ now = txn_ids.index(specific_txid)+1
+ mot = len(txn_ids)
+ num = round((now/mot)*100, 3)
+ print(f"\r({num}%) Stitching... ", end="")
+
+ break
+ # Once all the notes are obtained,
+ # place them all in one single
+ # string and return
+ for sil in stitched_initial_list:
+ stitched += sil
+ return stitched
diff --git a/HeadlineHackathon/Dreamf1re_hack/upload.py b/HeadlineHackathon/Dreamf1re_hack/upload.py
new file mode 100644
index 0000000..85bc547
--- /dev/null
+++ b/HeadlineHackathon/Dreamf1re_hack/upload.py
@@ -0,0 +1,242 @@
+from algosdk.v2client import algod
+from algosdk.future import transaction
+from util import init_post_client, init_get_client, \
+ get_lines, create_transaction, \
+ get_txn_ids_from_txn_id
+from stitching import stitch_records
+from checking import check_circular
+import base64
+
+
+# Core upload function
+def process_publishing(
+ feed: str,
+ receiver_address: str,
+ sender_address: str,
+ sender_private_key: str
+) -> list:
+ """
+ This is the core upload procedure using the feed which is the string of
+ encoded bytes from the file to be uploaded. This returns the
+ Transaction IDs from submitted group transactions.
+
+ :param feed: The raw string that is base64 encoded with encoding ISO-8859-1
+ :param receiver_address: Algorand address of receiver
+ :param sender_address: Algorand address of sender
+ :param sender_private_key: Algorand private key of sender
+ :return: txids - the Transaction IDs of submitted atomic transactions
+ """
+ # Initiate Algorand Client
+ post_client = init_post_client()
+ # Get lines from the feed
+ lines = get_lines(note=feed, max_length=947)
+ # Create transactions and append
+ transactions = []
+ # The maximum number of transactions
+ # in a group transaction is 16.
+ if len(lines) > 16:
+ # If number of lines exceeds 16, even
+ # (empty) lines are included in transaction
+ # creation. Transactions are then appended
+ # to the transactions list
+ for line in lines:
+ created_txn = create_transaction(
+ post_client=post_client,
+ receiver_address=receiver_address,
+ sender_address=sender_address,
+ message=line
+ )
+ transactions.append(created_txn)
+ progs = round((len(transactions)/len(lines))*100, ndigits=3)
+ print(f"\rPreparing..{progs}%", end="")
+ else:
+ # If number of lines did not exceed 16,
+ # each line is included in transaction
+ # creation if the length of line is not 0
+ transactions = [
+ create_transaction(
+ post_client=post_client,
+ receiver_address=receiver_address,
+ sender_address=sender_address,
+ message=line
+ ) for line in lines if line != ""
+ ]
+ # Group created transactions by 16 to comply
+ # with the group transaction limit of 16
+ # individual transactions per group
+ init_lines = []
+ actual_lines = []
+ if len(lines) > 16:
+ for eachtxn in transactions:
+ init_lines.append(eachtxn)
+ if len(init_lines) == 16:
+ actual_lines.append(init_lines)
+ init_lines = []
+ progs = round(((transactions.index(eachtxn)+1)/len(transactions)*100), ndigits=3)
+ print(f"\rAppending..{progs}%", end="")
+ # If the last batch of transactions are out and
+ # their number did not reach 16, append to
+ # actual lines nonetheless.
+ if len(init_lines) != 0:
+ actual_lines.append(init_lines)
+ else:
+ # If the length of main line is less than 16,
+ # bypass the sorting so that the actual lines
+ # becomes the list of transactions
+ actual_lines = [transactions]
+ # Calculate Group IDs for
+ # each subgroup in transactions list
+ # and append to a signed transactions list
+ signed_transactions = []
+ signed_transactions_process = []
+ print(f'\nCalculating GIDs..')
+ for group in actual_lines:
+ # Calculate Group ID for each batch of transactions
+ cgid = transaction.calculate_group_id(txns=group)
+ for inner_item in group:
+ # Assign calculated Group ID
+ # to each transaction
+ inner_item.group = cgid
+ # Sign transaction
+ signed_txn = inner_item.sign(sender_private_key)
+ # Add to processing list
+ signed_transactions_process.append(signed_txn)
+ # Append to final signed_transactions list
+ signed_transactions.append(signed_transactions_process)
+ # Reset helper list
+ signed_transactions_process = []
+ # Send signed group transactions to the Algorand blockchain
+ txids = []
+ for signed_group in signed_transactions:
+ txid = post_client.send_transactions(signed_group)
+ txids.append(txid)
+ # Do not wait for confirmation
+ progs = round(((signed_transactions.index(signed_group)+1)/len(signed_transactions)) * 100, 3)
+ print(f"\rSubmitted transactions..{progs}% ", end="")
+ return txids
+
+
+# Main upload function
+def upload(
+ filename: str,
+ sender_address: str,
+ sender_private_key: str
+):
+ """
+ Uploads certain local file to blockchain and returns only if
+ the uploaded file is found to be the same with the downloaded file.
+
+ :param filename: The filename of the file to be uploaded
+ :param sender_address: Algorand address of sender
+ :param sender_private_key: Algorand private key of sender
+ :return: downloaded_file: The stitched records from Algorand blockchain
+ :return: transaction_ids: Transaction IDs submitted to the blockchain
+ """
+ # Base64 encode the bytes and decode to get the string
+ with open(filename, 'rb') as o:
+ original_file = o.read().decode('ISO-8859-1')
+ original_file = base64.b64encode(original_file.encode()).decode()
+ print(f'Uploading {filename} to Algorand blockchain..')
+ # Get Transaction IDs submitted to the blockchain
+ transaction_ids = process_publishing(
+ feed=original_file,
+ receiver_address=sender_address,
+ sender_address=sender_address,
+ sender_private_key=sender_private_key
+ )
+ # Initialize GET Client
+ get_client = init_get_client()
+ # Loop until downloaded data is exactly
+ # the same with the uploaded data
+ while True:
+ # Get Transaction IDs from Transaction IDs
+ txn_ids = get_txn_ids_from_txn_id(
+ __txids=transaction_ids,
+ client=get_client
+ )
+ # Download the uploaded file from the blockchain
+ downloaded_file = stitch_records(
+ get_client=get_client,
+ txn_ids=txn_ids
+ )
+ # Check if the uploaded file
+ # and downloaded file are the same
+ # and return Transaction IDs from
+ # upload procedure if so
+ circular = check_circular(
+ original=original_file,
+ stitched=downloaded_file
+ )
+ if circular:
+ print('File successfully uploaded to blockchain.')
+ return transaction_ids
+
+
+# Link getter
+def get_file_id(
+ transaction_ids: list,
+ receiver_address: str,
+ sender_address: str,
+ sender_private_key: str,
+ post_client: algod.AlgodClient,
+ filename: str,
+) -> str:
+ """
+ Returns a link which is essentially a Transaction ID
+ that can be used to download the uploaded file.
+
+ :param transaction_ids: Transaction IDs from upload
+ :param receiver_address: Algorand address of receiver
+ :param sender_address: Algorand address of sender
+ :param sender_private_key: Private key of sender
+ :param post_client: AlgodClient to node (not indexer)
+ :param filename: filename of the uploaded file
+ :return: File ID (a Transaction ID) used for stitching
+ """
+ print(f"Assigning File ID...Please Wait.")
+ alpha_fn = base64.b64encode(filename.encode()).decode()
+ alpha = f"{alpha_fn}"
+ for txid in transaction_ids:
+ alpha += txid
+ feed = get_lines(
+ note=alpha,
+ max_length=947
+ )
+ txid = None
+ for each in feed:
+ if len(each) != 0:
+ if len(feed) > 1:
+ if txid is None:
+ txn = create_transaction(
+ post_client,
+ receiver_address,
+ sender_address,
+ message=each
+ )
+ else:
+ txn = create_transaction(
+ post_client,
+ receiver_address,
+ sender_address,
+ message=each+txid
+ )
+ sgd = txn.sign(sender_private_key)
+ txid = post_client.send_transaction(sgd)
+ transaction.wait_for_confirmation(
+ algod_client=post_client,
+ txid=txid
+ )
+ else:
+ txn = create_transaction(
+ post_client,
+ receiver_address,
+ sender_address,
+ message=each
+ )
+ sgd = txn.sign(sender_private_key)
+ txid = post_client.send_transaction(sgd)
+ transaction.wait_for_confirmation(
+ algod_client=post_client,
+ txid=txid
+ )
+ return txid
diff --git a/HeadlineHackathon/Dreamf1re_hack/util.py b/HeadlineHackathon/Dreamf1re_hack/util.py
new file mode 100644
index 0000000..125461e
--- /dev/null
+++ b/HeadlineHackathon/Dreamf1re_hack/util.py
@@ -0,0 +1,317 @@
+from algosdk.v2client import algod
+from algosdk.future import transaction
+from algosdk.mnemonic import to_private_key
+from algosdk.account import address_from_private_key
+from urllib.request import Request, urlopen
+import json
+import base64
+
+from constants import *
+import os
+
+
+TEST_SENDER_MNEMONIC = os.environ["test_mnemonic"]
+TEST_SENDER_PRIVATE_KEY = to_private_key(TEST_SENDER_MNEMONIC)
+TEST_SENDER_ADDRESS = address_from_private_key(TEST_SENDER_PRIVATE_KEY)
+
+
+def init_post_client():
+ """
+ Initializes an Algorand Client for posting data
+
+ :return: algod_client - algod.AlgodClient (POST)
+ """
+ algod_address = ALGONODE_NODE_ADDRESS
+ algod_token = ''
+ headers = {'User-Agent': 'algosdk'}
+ algod_client = algod.AlgodClient(algod_token, algod_address, headers)
+ return algod_client
+
+
+def init_get_client():
+ """
+ Initializes an Algorand Client for getting data
+
+ :return: algod_client - algod.AlgodClient (GET)
+ """
+ algod_address = ALGONODE_INDX_ADDRESS
+ algod_token = ''
+ headers = {'User-Agent': 'algosdk'}
+ algod_client = algod.AlgodClient(algod_token, algod_address, headers)
+ return algod_client
+
+
+def get_account_info(
+ get_client: algod.AlgodClient,
+ account_address: str
+):
+ """
+ Gets account information from the given account address.
+
+ :param get_client: algod.AlgodClient (GET)
+ :param account_address: Algorand public address of target account
+ :return: info - Account information
+ """
+ info = None
+ while True:
+ try:
+ info = get_client.account_info(address=account_address)
+ if info is not None:
+ return info
+ except Exception as err:
+ print(err.args)
+ finally:
+ if info is None:
+ info = get_client.account_info(address=account_address)
+ if info is not None:
+ return info
+
+
+# Notes in payment transactions are
+# utilized to store data in the blockchain
+def create_transaction(
+ post_client: algod.AlgodClient,
+ receiver_address: str,
+ sender_address: str,
+ message
+):
+ """
+ Creates a payment transaction for a given message.
+
+ :param post_client: algod.AlgodClient (POST)
+ :param receiver_address: Algorand receiver address
+ :param sender_address: Algorand sender address
+ :param message: The note
+ :return: A payment transaction (PaymentTxn)
+ """
+ return transaction.PaymentTxn(
+ sender=sender_address,
+ sp=post_client.suggested_params(),
+ receiver=receiver_address,
+ amt=0,
+ note=message
+ )
+
+
+# Because data is stored in notes,
+# the following function gets the note
+# of a given transaction.
+def search_note_by_txid(
+ get_client: algod.AlgodClient,
+ txid: str
+):
+ """
+ Gets note based on the specified Transaction ID
+
+ :param get_client: algod.AlgodClient (GET)
+ :param txid: Transaction ID from which to get the note
+ :return: note - the note of a given transaction
+ """
+ try:
+ while True:
+ req = f'/v2/transactions/{txid}'
+ url = get_client.algod_address + req
+ request = Request(url=url, headers=get_client.headers)
+ resp = urlopen(request)
+ json_loaded = json.load(resp)
+ note = str(json_loaded['transaction']['note'])
+ # Notes are base64 decoded as it is
+ # base64 encoded before uploading to the
+ # blockchain. This is to add a layer of
+ # obfuscation to the contents of the actual
+ # note. This can be edited so as not to
+ # do base64 encoding before uploading thereby
+ # not needing to decode when it is fetched from
+ # the blockchain.
+ note = base64.b64decode(note).decode()
+ if len(note) != 0:
+ return note
+ except Exception as e:
+ print(e.args)
+
+
+# Since there is limited length of bytes per note in the
+# transaction which is 1024 bytes or 1 kilobyte, the main feed
+# of bytes obtained from encoding of the file-to-be-uploaded is
+# divided into separate lines
+def get_lines(
+ note: str,
+ max_length: int
+) -> list:
+ """
+ Get lines for each transaction. Each line, by design, is 947 bytes in length,
+ max length is 1024 bytes for the Algorand note field.
+
+ :param note: The main feed which is base64 encoded with ISO-8859-1 encoding
+ :param max_length: The intended line length
+ :return: list_of_notes - A list of notes
+ """
+ # Do first append
+ list_of_notes = [note[0:max_length]]
+ new_note = note[max_length:]
+ # Repeat succeeding appends
+ while True:
+ list_of_notes.append(new_note[0:max_length])
+ new_note = new_note[max_length:]
+ # Do append if final line is reached
+ if len(new_note) < max_length:
+ list_of_notes.append(new_note[0:])
+ break
+ return list_of_notes
+
+
+def get_transaction_info(
+ txids: list,
+ client: algod.AlgodClient
+):
+ """
+ Gets transaction infos from transaction IDs
+
+ :param txids: Transaction IDs
+ :param client: algod.AlgodClient (GET -> directed to indexer)
+ :return: jsons: Transaction Infos
+ """
+ jsons = []
+ for txid in txids:
+ try:
+ req = f'/v2/transactions/{txid}'
+ url = client.algod_address + req
+ request = Request(url, headers=client.headers)
+ while True:
+ resp = urlopen(request)
+ json_loaded = json.load(resp)
+ if len(str(json_loaded)) > 0 and str(json_loaded) != "()":
+ jsons.append(json_loaded)
+ print(f"\rFetched infos...", end="")
+ break
+ except Exception as e:
+ print(e.args)
+ return jsons
+
+
+def get_confirmed_rounds_from_txid(
+ txids: list,
+ client: algod.AlgodClient
+):
+ confirmed_rounds = []
+ # Get confirmed round
+ try:
+ while True:
+ tx_infos = get_transaction_info(txids=txids, client=client)
+ if len(tx_infos) != 0:
+ break
+ for txinfo in tx_infos:
+ while True:
+ conrnd = str(txinfo['transaction']['confirmed-round'])
+ if len(conrnd) > 0:
+ confirmed_rounds.append(conrnd)
+ break
+ except Exception as e:
+ print(e.args)
+ return confirmed_rounds
+
+
+def get_txn_ids_from_txn_id(
+ __txids: list,
+ client: algod.AlgodClient
+):
+ """
+ Gets Transaction IDs from a Transaction ID,
+ leverages Confirmed Rounds and Group IDs.
+
+ :param __txids: Transaction IDs
+ :param client: algod.AlgodClient (GET -> directed to indexer)
+ :return: txids: Transaction IDs
+ """
+ txids = []
+ initial = []
+ bridge_for_reverse = []
+ block_infos = []
+ while True:
+ try:
+ # Get confirmed rounds
+ while True:
+ confirmed_rounds = get_confirmed_rounds_from_txid(
+ txids=__txids,
+ client=client
+ )
+ if len(confirmed_rounds) > 0:
+ break
+ print("\rGoing through blocks... ", end="")
+ while True:
+ # Get block infos
+ for cround in confirmed_rounds:
+ req = f'/v2/transactions?round={cround}'
+ url = client.algod_address + req
+ request = Request(url, headers=client.headers)
+ while True:
+ resp = urlopen(request)
+ json_loaded = str(json.load(resp))
+ if len(json_loaded) > 0:
+ block_infos.append(json_loaded)
+ break
+ # Get Group ID list
+ gids = get_group_id(client=client, txids=__txids)
+ # Get Transaction IDs
+ for index, block_info in enumerate(block_infos, start=0):
+ while gids[index] in block_info:
+ gid_index = block_info.find(gids[index])
+ full_gid_index = gid_index + len(gids[index])
+ gid_ = block_info[gid_index:full_gid_index]
+ gid_and_ = gid_ + '","id":"'
+ txid_start_index = (gid_index + 1) + (len(gid_and_) + 1)
+ txid_end_index = txid_start_index + 52
+ txid_extract = block_info[txid_start_index:txid_end_index]
+ initial.append(txid_extract)
+ block_info = block_info[txid_end_index+1:]
+ txids.append(initial)
+ initial = []
+ break
+ break
+ except Exception as e:
+ print(e.args)
+ txids.reverse()
+ for sublist in txids:
+ bridge_for_reverse.append(sublist)
+ txids = []
+ bridge_for_reverse.reverse()
+ for superior in bridge_for_reverse:
+ for inferior in superior:
+ txids.append(inferior)
+ return txids
+
+
+# Group IDs are used to find
+# other Transaction IDs from
+# the given Transaction IDs
+# obtained from the upload
+# procedure
+def get_group_id(
+ client: algod.AlgodClient,
+ txids: list
+) -> list:
+ """
+ Gets Group IDs from Transaction IDs
+
+ :param client: an algod.AlgodClient (GET)
+ :param txids: Transaction IDs
+ :return: gids - Group IDs
+ """
+ # Get Group IDs
+ gids = []
+ print("Getting gids...")
+ try:
+ while True:
+ txn_infos = get_transaction_info(
+ txids=txids,
+ client=client
+ )
+ if len(txn_infos) != 0:
+ for txn_info in txn_infos:
+ gid = txn_info['transaction']['group']
+ if len(gid) > 0:
+ gids.append(gid)
+ break
+ except Exception as e:
+ print(e.args)
+ return gids
From 96a470aaa2810ae40ed34e82be1c4b9da9a0179d Mon Sep 17 00:00:00 2001
From: Dreamf1re <106770791+Dreamf1re@users.noreply.github.com>
Date: Sun, 12 Jun 2022 18:13:56 +0800
Subject: [PATCH 3/8] Update README.md
---
HeadlineHackathon/Dreamf1re_hack/README.md | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/HeadlineHackathon/Dreamf1re_hack/README.md b/HeadlineHackathon/Dreamf1re_hack/README.md
index 85a1284..4776ecf 100644
--- a/HeadlineHackathon/Dreamf1re_hack/README.md
+++ b/HeadlineHackathon/Dreamf1re_hack/README.md
@@ -7,6 +7,11 @@ This is a web app, designed with PyWebIO, which aims to make text files permanen
3. Edit Environment Variables -> add variable name "test_mnemonic"
with variable value which is your test mnemonic.
5. Run deploy.py through ```python deploy.py```.
-Running deploy.py will start a new tab in the user's default
-web browser. All functionalities therein are intact and the user
-can now start making text files permanent on the Algorand blockchain.
+Running deploy.py will start a new tab in the user's default
+web browser. All functionalities therein are intact and the user
+can now start making text files permanent on the Algorand blockchain.
+
+## Notes
+Only use text files that are allowed to be accessed publicly. Never use this web app with
+text files containing private information as it will be made permanent in the Algorand blockchain
+which is a ```public``` blockchain.
From 945265ace939bdc1589011c474869f92f35e43e1 Mon Sep 17 00:00:00 2001
From: Dreamf1re <106770791+Dreamf1re@users.noreply.github.com>
Date: Sun, 12 Jun 2022 18:19:17 +0800
Subject: [PATCH 4/8] Update README.md
---
HeadlineHackathon/Dreamf1re_hack/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/HeadlineHackathon/Dreamf1re_hack/README.md b/HeadlineHackathon/Dreamf1re_hack/README.md
index 4776ecf..6c8a81f 100644
--- a/HeadlineHackathon/Dreamf1re_hack/README.md
+++ b/HeadlineHackathon/Dreamf1re_hack/README.md
@@ -3,7 +3,7 @@ This is a web app, designed with PyWebIO, which aims to make text files permanen
## Usage
1. Download this entire folder.
-2. Install required packages using ```pip install requirements.txt```.
+2. Install required packages using ```pip install -r requirements.txt```.
3. Edit Environment Variables -> add variable name "test_mnemonic"
with variable value which is your test mnemonic.
5. Run deploy.py through ```python deploy.py```.
From 3ad6c51ea0382a312ae2bcead73606b2c5d95072 Mon Sep 17 00:00:00 2001
From: Dreamf1re <106770791+Dreamf1re@users.noreply.github.com>
Date: Sun, 12 Jun 2022 20:31:38 +0800
Subject: [PATCH 5/8] Update README.md
---
HeadlineHackathon/Dreamf1re_hack/README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/HeadlineHackathon/Dreamf1re_hack/README.md b/HeadlineHackathon/Dreamf1re_hack/README.md
index 6c8a81f..990f0c1 100644
--- a/HeadlineHackathon/Dreamf1re_hack/README.md
+++ b/HeadlineHackathon/Dreamf1re_hack/README.md
@@ -7,9 +7,9 @@ This is a web app, designed with PyWebIO, which aims to make text files permanen
3. Edit Environment Variables -> add variable name "test_mnemonic"
with variable value which is your test mnemonic.
5. Run deploy.py through ```python deploy.py```.
-Running deploy.py will start a new tab in the user's default
-web browser. All functionalities therein are intact and the user
-can now start making text files permanent on the Algorand blockchain.
+Running deploy.py will start a new tab in the user's default
+web browser. All functionalities therein are intact and the user
+can now start making text files permanent on the Algorand blockchain.
## Notes
Only use text files that are allowed to be accessed publicly. Never use this web app with
From 093507b3a5b95b837fdcb01f2042dcefe28ba186 Mon Sep 17 00:00:00 2001
From: Dreamf1re <106770791+Dreamf1re@users.noreply.github.com>
Date: Mon, 13 Jun 2022 11:47:22 +0800
Subject: [PATCH 6/8] Update README.md
---
HeadlineHackathon/Dreamf1re_hack/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/HeadlineHackathon/Dreamf1re_hack/README.md b/HeadlineHackathon/Dreamf1re_hack/README.md
index 990f0c1..e9d874d 100644
--- a/HeadlineHackathon/Dreamf1re_hack/README.md
+++ b/HeadlineHackathon/Dreamf1re_hack/README.md
@@ -13,5 +13,5 @@ can now start making text files permanent on the Algorand blockchain.
## Notes
Only use text files that are allowed to be accessed publicly. Never use this web app with
-text files containing private information as it will be made permanent in the Algorand blockchain
+text files containing private information as they will be made permanent in the Algorand blockchain
which is a ```public``` blockchain.
From b4156af2560b114ff4d64026e3ae7b72f3cc092d Mon Sep 17 00:00:00 2001
From: Dreamf1re <106770791+Dreamf1re@users.noreply.github.com>
Date: Tue, 14 Jun 2022 04:11:00 +0800
Subject: [PATCH 7/8] Update README.md
---
HeadlineHackathon/Dreamf1re_hack/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/HeadlineHackathon/Dreamf1re_hack/README.md b/HeadlineHackathon/Dreamf1re_hack/README.md
index e9d874d..c7f3cd0 100644
--- a/HeadlineHackathon/Dreamf1re_hack/README.md
+++ b/HeadlineHackathon/Dreamf1re_hack/README.md
@@ -4,7 +4,7 @@ This is a web app, designed with PyWebIO, which aims to make text files permanen
## Usage
1. Download this entire folder.
2. Install required packages using ```pip install -r requirements.txt```.
-3. Edit Environment Variables -> add variable name "test_mnemonic"
with variable value which is your test mnemonic.
+3. Edit Environment Variables -> add variable name "test_mnemonic" with variable value which is your test mnemonic. Make sure to fund this account with TestNet ALGOs
5. Run deploy.py through ```python deploy.py```.
Running deploy.py will start a new tab in the user's default
From 91083f82e66ea13a7b3b95201ed4fc5407bb286d Mon Sep 17 00:00:00 2001
From: Dreamf1re <106770791+Dreamf1re@users.noreply.github.com>
Date: Tue, 14 Jun 2022 04:11:47 +0800
Subject: [PATCH 8/8] Update README.md
---
HeadlineHackathon/Dreamf1re_hack/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/HeadlineHackathon/Dreamf1re_hack/README.md b/HeadlineHackathon/Dreamf1re_hack/README.md
index c7f3cd0..3ee3c8b 100644
--- a/HeadlineHackathon/Dreamf1re_hack/README.md
+++ b/HeadlineHackathon/Dreamf1re_hack/README.md
@@ -4,7 +4,7 @@ This is a web app, designed with PyWebIO, which aims to make text files permanen
## Usage
1. Download this entire folder.
2. Install required packages using ```pip install -r requirements.txt```.
-3. Edit Environment Variables -> add variable name "test_mnemonic" with variable value which is your test mnemonic. Make sure to fund this account with TestNet ALGOs
+3. Edit Environment Variables -> add variable name "test_mnemonic" with variable value which is your test mnemonic.
Make sure to fund this account with TestNet ALGOs.
5. Run deploy.py through ```python deploy.py```.
Running deploy.py will start a new tab in the user's default