P1R0
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Weekend Project 3 > Solidity BootCamp 2023Q1 @ encode.club :::info ## Table of Contents [TOC] ::: ## Tasks * Form groups of 3 to 5 students * Complete the contracts together * Develop and run scripts for “TokenizedBallot.sol” within your group to give voting tokens, delegating voting power, casting votes, checking vote power and querying results * Write a report with each function execution and the transaction hash, if successful, or the revert reason, if failed * Share your code in a github repo in the submission form ## General Contract Information **Contract Address:** `` TODO TransactionID Deployment: https://goerli.etherscan.io/tx/0xc6ac726a083029ea8b18 Contract Code: ```solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; interface IMyToken{ function getPastVotes(address account, uint256 blockNumber) external view returns (uint256); } contract Ballot { struct Proposal { bytes32 name; uint voteCount; } IMyToken public tokenContract; uint256 public targetBlockNumber; Proposal[] public proposals; mapping (address => uint256) public votingPowerSpent; constructor( bytes32[] memory proposalNames, address _tokenContract, uint256 _targetBlockNumber ) { tokenContract = IMyToken(_tokenContract); targetBlockNumber = _targetBlockNumber; for (uint i = 0; i < proposalNames.length; i++) { proposals.push(Proposal({name: proposalNames[i], voteCount: 0})); } } function vote(uint proposal, uint256 amount) external { require(votingPower(msg.sender) >= amount); votingPowerSpent[msg.sender] += amount; proposals[proposal].voteCount += amount; } function votingPower(address account) public view returns (uint256){ return tokenContract.getPastVotes(account, targetBlockNumber) - votingPowerSpent[account]; } function winningProposal() public view returns (uint winningProposal_) { uint winningVoteCount = 0; for (uint p = 0; p < proposals.length; p++) { if (proposals[p].voteCount > winningVoteCount) { winningVoteCount = proposals[p].voteCount; winningProposal_ = p; } } } function winnerName() external view returns (bytes32 winnerName_) { winnerName_ = proposals[winningProposal()].name; } } ``` ## Adam's Contributions ### Metamask Test Wallet https://goerli.etherscan.io/address/0x01b5af9976658a33a9809d4261225FAc2f53a9DD ### Repository Repository code for this project can be found here: [Week 2 Project](https://github.com/asteinberger/encode-bootcamp-homework/tree/feature/adam-steinberger-week-2) ### State Changes 1. Deployment 2. Give Right To Vote (multiple) 3. Vote ### Edge Cases ![](https://i.imgur.com/9pUHuaq.png) ![](https://i.imgur.com/qlsfhQn.png) ## Dan transactions ### Metamask Wallet Dan's Wallet: 0xEFC0D955536ed993F93177bdaCdA5d266083F573 ### First Transaction - Attempted to vote without permission granted by the chairperson, transaction reverted. hash: 0x8626f786afa5de5d037b4a57ae1640fe324546f2c5ff396679318d7e2ff88ff9 ### Second Transaction - Chairperson grants me the right to vote. hash: 0x4c2bf8e0e5466817aced5ce943df71b451f51d30de128b1aec5147ee90e3f6d6 - I proceed to vote for proposal 2, successful transaction. hash: 0xca459bd9f0201a2f3545536275b5f663eb654933a5e38a11274585b20ce14124 #### State Changes: - Voter struct with my address created, 1 weight added, vote cast = true. ### Third transaction I attempt to give the right to vote, even though I am not the chairperson. This transaction is reverted. - hash: 0x9c4479536e69ad7a675c4f0039a8f4a2ca807ef01a4547ee8f46fe6319ed6702 ## David E. Perez Negron R. transactions ### Personal Repository https://github.com/P1R/Tokenized_Votes ## Script Code (scripts/TokenizedBallot.ts): The following script code performs the Weekend Project Tasks ```typescript import { ethers } from "hardhat"; import { MyToken__factory, Ballot__factory } from "../typechain-types"; const MINT_VALUE = 100; // I Like Pokemons which one should I pick? const PROPOSALS = ["Bulbasaur", "Charmander", "Squirtle", "pikachu"]; // Converts convert String Array to Bytes32 Array function convertStringArrayToBytes32(array: string[]) { const bytes32Array = []; for (let index = 0; index < array.length; index++) { bytes32Array.push(ethers.utils.formatBytes32String(array[index])); } return bytes32Array; } // Converts convert Bytes32 Array to String Array function convertBytes32ArrayToString(array: Bytes32[]) { const stringArray = []; for (let index = 0; index < array.length; index++) { stringArray.push(ethers.utils.parseBytes32String(array[index])); } return stringArray; } async function main() { // Deploy ERC20Votes Contract const [deployer, account1, account2, account3] = await ethers.getSigners(); const contractERC20VotesFactory = new MyToken__factory(deployer); const contractERC20Votes = await contractERC20VotesFactory.deploy(); const deployedTransactionReciptERC20 = await contractERC20Votes.deployTransaction.wait(); console.log(`The ERC20Votes contract got deployed at block number ${deployedTransactionReciptERC20.blockNumber}`); // GIVE VOTE TOKENS console.log("GIVE VOTE TOKENS:"); //Mint some tokens to Account 1 const mintTx = await contractERC20Votes.mint(account1.address, MINT_VALUE); const mintRecipt = await mintTx.wait(); console.log(`Minted tokens to ${account1.address} at ${mintRecipt.blockNumber}`); const balanceOfAccount1 = await contractERC20Votes.balanceOf(account1.address); console.log(`Now the balance of ${account1.address} is ${balanceOfAccount1}`); //Mint some tokens to Account 2 const mintTx2 = await contractERC20Votes.mint(account2.address, MINT_VALUE); const mint2Recipt = await mintTx2.wait(); console.log(`Minted tokens to ${account2.address} at ${mint2Recipt.blockNumber}`); const balanceOfAccount2 = await contractERC20Votes.balanceOf(account2.address); console.log(`Now the balance of ${account2.address} is ${balanceOfAccount2}`); //Mint some tokens to Account 3 const mintTx3 = await contractERC20Votes.mint(account3.address, MINT_VALUE); const mint3Recipt = await mintTx3.wait(); console.log(`Minted tokens to ${account3.address} at ${mint3Recipt.blockNumber}`); const balanceOfAccount3 = await contractERC20Votes.balanceOf(account3.address); console.log(`Now the balance of ${account3.address} is ${balanceOfAccount3}`); // DELEGATING VOTING POWER console.log("DELEGATING VOTING POWER"); //First account1 self delegates const delegateTx = await contractERC20Votes.connect(account1).delegate(account1.address); const delegateTxRecipt = await delegateTx.wait(); console.log( `tokens delegated from ${account1.address} to ${account1.address} at block ${delegateTxRecipt.blockNumber}` ); let votingPower = await contractERC20Votes.getVotes(account1.address); console.log( `wallet ${account1.address} has a voting power of ${votingPower}` ); //First account2 delegates const delegateTx2 = await contractERC20Votes.connect(account2).delegate(account3.address); const delegateTx2Recipt = await delegateTx2.wait(); console.log( `tokens delegated from ${account2.address} to ${account3.address} at block ${delegateTx2Recipt.blockNumber}` ); let votingPower2 = await contractERC20Votes.getVotes(account2.address); console.log( `wallet ${account2.address} has a voting power of ${votingPower2}` ); //First account3 delegates const delegateTx3 = await contractERC20Votes.connect(account3).delegate(account3.address); const delegateTx3Recipt = await delegateTx3.wait(); console.log( `tokens delegated from ${account3.address} to ${account3.address} at block ${delegateTx3Recipt.blockNumber}` ); let votingPower3 = await contractERC20Votes.getVotes(account3.address); console.log( `wallet ${account3.address} has a voting power of ${votingPower3}` ); // CASTING VOTES console.log("CASTING VOTES"); // Deploy TokenizedBallot Contract const contractBallotFactory = new Ballot__factory(deployer); const contractBallot = await contractBallotFactory.deploy( convertStringArrayToBytes32(PROPOSALS), contractERC20Votes.address, "7" ); const deployedTransactionReciptBallot = await contractBallot.deployTransaction.wait(); console.log(`The ballot contract address is ${deployedTransactionReciptBallot.contractAddress}`); console.log(`The block number is ${deployedTransactionReciptBallot.blockNumber}`); // Account1 Votes for Bulbasaur with 49 tokens and votes 51 for Charmander const vote1Tx = await contractBallot.connect(account1).vote( "0", "49"); const vote1Recipt = await vote1Tx.wait(); console.log( `${account1.address} voted at block ${vote1Recipt.blockNumber} with the txID ${vote1Recipt.transactionHash}` ); let votingPower1 = await contractBallot.votingPower(account1.address); console.log( `wallet ${account1.address} has a voting power of ${votingPower1}` ); const vote2Tx = await contractBallot.connect(account1).vote( "0", "51"); const vote2Recipt = await vote2Tx.wait(); console.log( `${account1.address} voted at block ${vote2Recipt.blockNumber} with the txID ${vote2Recipt.transactionHash}` ); votingPower1 = await contractBallot.votingPower(account1.address); console.log( `wallet ${account1.address} has a voting power of ${votingPower1}` ); // Account2 tries to give 100 to Squirtle (for testscript Gets "Transaction reverted without a reason string") // ToDO: Add an error string to the Vote Functiont at the ballot contract (require). //const vote3Tx = await contractBallot.connect(account2).vote( "2", "100"); //const vote3Recipt = await vote3Tx.wait(); //console.log(vote3Recipt); // Account3 tries to give 100 to Squirtle (for testscript Gets "Transaction reverted without a reason string") const vote3Tx = await contractBallot.connect(account3).vote( "2", "51"); const vote3Recipt = await vote3Tx.wait(); console.log( `${account3.address} voted at block ${vote3Recipt.blockNumber} with the txID ${vote3Recipt.transactionHash}` ); votingPower3 = await contractBallot.votingPower(account3.address); console.log( `wallet ${account3.address} has a voting power of ${votingPower3}` ); const vote4Tx = await contractBallot.connect(account3).vote( "3", "101"); const vote4Recipt = await vote4Tx.wait(); console.log( `${account3.address} voted at block ${vote4Recipt.blockNumber} with the txID ${vote4Recipt.transactionHash}` ); votingPower3 = await contractBallot.votingPower(account3.address); console.log( `wallet ${account3.address} has a voting power of ${votingPower3}` ); // CHECK VOTE POWER console.log("CHECK VOTE POWER"); //Check Account1 voting Power votingPower1 = await contractBallot.votingPower(account1.address); console.log( `wallet ${account1.address} has a voting power of ${votingPower1}` ); //Check Account2 voting Power votingPower2 = await contractBallot.votingPower(account2.address); console.log( `wallet ${account2.address} has a voting power of ${votingPower2}` ); ////Check Account3 voting Power votingPower3 = await contractBallot.votingPower(account3.address); console.log( `wallet ${account3.address} has a voting power of ${votingPower3}` ); // QUERIYING RESUTS const winningProposal = await contractBallot.winningProposal(); const winningProposalName = convertBytes32ArrayToString([await contractBallot.winnerName()]); console.log(` The Winner Proporsal is ${winningProposal}: ${winningProposalName} `) } main().catch((error) => { console.error(error); process.exitCode = 1; }) ``` ## Output: The following is this script output performing the Project Tasks ```shell $ yarn hardhat run scripts/TokenizedBallot.ts The ERC20Votes contract got deployed at block number 1 GIVE VOTE TOKENS: Minted tokens to 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 at 2 Now the balance of 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 is 100 Minted tokens to 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC at 3 Now the balance of 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC is 100 Minted tokens to 0x90F79bf6EB2c4f870365E785982E1f101E93b906 at 4 Now the balance of 0x90F79bf6EB2c4f870365E785982E1f101E93b906 is 100 DELEGATING VOTING POWER tokens delegated from 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 to 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 at block 5 wallet 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 has a voting power of 100 tokens delegated from 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC to 0x90F79bf6EB2c4f870365E785982E1f101E93b906 at block 6 wallet 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC has a voting power of 0 tokens delegated from 0x90F79bf6EB2c4f870365E785982E1f101E93b906 to 0x90F79bf6EB2c4f870365E785982E1f101E93b906 at block 7 wallet 0x90F79bf6EB2c4f870365E785982E1f101E93b906 has a voting power of 200 CASTING VOTES The ballot contract address is 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 The block number is 8 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 voted at block 9 with the txID 0xf4efd1513536cf8e1c5bb1b87e414aa09f3d1aac2fd92deb8b8b96083097c22a wallet 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 has a voting power of 51 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 voted at block 10 with the txID 0x68de52eab6d9a2902a37c2e5ee9e98472838f72e674ca5b570b451592aae5213 wallet 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 has a voting power of 0 0x90F79bf6EB2c4f870365E785982E1f101E93b906 voted at block 11 with the txID 0xcca725fc1eb115da30ccc69d1237e1a36197c645403991e613fd085716f0d45b wallet 0x90F79bf6EB2c4f870365E785982E1f101E93b906 has a voting power of 149 0x90F79bf6EB2c4f870365E785982E1f101E93b906 voted at block 12 with the txID 0x02a6b71d2bb55cb681a234455dae6412c547792302f32f8356018e279976b300 wallet 0x90F79bf6EB2c4f870365E785982E1f101E93b906 has a voting power of 48 CHECK VOTE POWER wallet 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 has a voting power of 0 wallet 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC has a voting power of 0 wallet 0x90F79bf6EB2c4f870365E785982E1f101E93b906 has a voting power of 48 The Winner Proporsal is 3: pikachu ``` ## Conclusions Since there where multiple errors at the Goerli network as reported issue at the Lesson - 12 [1], I was unable to perform this test on the testnet, yet at the end I foundout mumbai network let me do the deployment correctly. We might require to do self delegation with the team coordination to forefill the tasks at the testnet and for new the task was forefilled successfully at the testing enviorment. ## Cesar transactions Interacted with Contract `'0xaf5bd48C8dd8F733697e148F952c62868A701b71'` **Call vote method:** transactionId: https://goerli.etherscan.io/tx/0x2e211c70bd4b008a8cad147b1543acc93bcba8238157d20de6d47f3707f0b36f This transaction executes a call to initiate a state change and 'vote' if a user has voting delegations ## Rebecca transactions ### Successful Vote Vote confirmation: https://goerli.etherscan.io/tx/0x458c495c2caea4585b68bf49ed7be6deeac1df971d48a8da15ae529064e25487 ### Main bugs/obstacles/lessons - goerli wallet was not properly connected - did not confirm that my wallet was connected when sending the vote - did not change my wallet currency to goerli before reconnecting my wallet; as a result, i almost paid my gas fee in ETH - did not input the proper information for voting for a specific proposal - I kept trying to input a string instead of a number defined in the script. as a result, i kept getting "out of bounds" errors - had issues understanding how all the code connected - like the week before, i am still facing obstables in understanding how each part of the code works together to interact with a smart contract. unlike last week, i've learned that documentation is not enough for me -- i need to reach out to my classmates, TA and programming friends for help. i will now be communicating more frequently with all of them, and setting up study sessions. ## Brent transactions ### First Transaction https://goerli.etherscan.io/tx/0xef0443745cf85d781393ea8bc4febe85643b1831bb4d2e2d4f44ca53ec3b3961 #### State Changes 1. "Yo, whats up bro" ## Contact and Developers - [David E. Perez Negron R.](mailto:david@neetsec.com) Github: @P1R - [Rebecca Duke Wiesenberg](mailto:rdukewiesenb@gmail.com) Github: @rdukewiesenb - [Adam Steinberger](mailto:adam@asteinbe.com) | Github: @asteinberger | Discord: steinz08#3291 ## References \[1\] Encode Club Solidity Bootcamp , "Lesson 8 - Scripts for Ballot.sol", https://github.com/Encode-Club-Solidity-Bootcamp/Lesson-08, 2023. \[2\] docs.soliditylang.org , "Solidity by Example", https://docs.soliditylang.org/en/latest/solidity-by-example.html#voting, 2023.

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully