Cover photo

มาฝึก deploy Contract และ เก็บ 13 Base Camp testnet NFT

Base บอกว่าสิ้นปี จะเอา NFT ขึ้น mainnet ด้วย

เหมือนบทความเดิมนะ ใครเคยทำแล้ว ต่อข้อ 11 ได้เลย

  • เพิ่มข้อ 11-13 ให้ครบ

  • ใครเคยติดข้อ 10 อันนี้น่าจะทำตามง่ายขึ้นนะ 😀

  • เรียงให้ตามง่ายขึ้น

deploy contract ทำ ทำไม

โพสนี้เริ่มจาก Base มีหลักสูตรให้เรียนและเคลม NFT ทุกๆบท เมื่อผมเรียนจบก็เห็นว่ามันลอกการบ้านง่ายนะ เลยคิดว่ามาแนะนำเพื่อนๆดีกว่า

จะได้รู้ว่า contract มันทำยังไง
ง่ายกว่าที่คิด
ไม่ต้องติดตั้งแอปลงเครื่อง
ใช้เวลาไม่มาก(?)

deploy contract บน chain อื่นๆ เช่น Scroll, Linea, zkSync, polygon zkEVM และ EVM อื่นๆได้ทั้งหมด เทสเน็ตเรา deploy ได้รัวๆ ส่วน mainnet ใช้เงินจริงนะเอ้อ

การ deploy contract เป็นการสร้างกิจกรรมบน blockchain ซึ่งปกติมีแต่ dev ที่ทำ user ไม่ค่อยทำ ถ้า เชนไหน reward dev ก็น่าจะส่งผลกับ airdrop ที่มากขึ้น ดังนั้นผมคิดว่าทำไว้ไม่เสียหาย โดยเฉพาะเทสเน็ตอย่าง Scroll testnet, Linea testnet ไม่ควรพลาด

เนี่ยเป็น dev มันก็จะมีอะไรๆ แบบนี้

สำหรับ NFT ที่เราจะเก็บกันอยู่นี้ จะมีผลกับ Base mainnet ในสิ้นปี 2024 นี้ด้วย


เตรียมการกันก่อน

  1. แอด chain ต่างๆได้ที่ https: chainlist.org
    เข้าไปแล้ว connect wallet แอดเชน testnet ให้หมด โดยเฉพาะ Base sepolia

  2. เตรียม เหรียญเทส ETH ขุดเอาได้ไว https: sepolia-faucet.pk910.de

  3. บริดเหรียญเทสไปเชนที่ต้องการ https: rinkeby.orbiter.finance
    *เสริม กรณีบริด mainnet https: www.orbiter.finance

พร้อมแล้วก็ลุยต่อกันเลย


เริ่มdeploy contract และ เคลม NFT กันได้เลย

เราจะเขียน Contract และ Deploy ด้วย Remix

https: remix.ethereum.org < เพื่อความปลอดภัยเช็ค URL ให้ถูกต้องทุกครั้ง

  1. เมื่อเข้าไป ให้กด tab - file explorer บนสุด เราจะเห็นว่ามีไฟล์อยู่แล้ว ลบให้หมด เหลือแค่ folder contract

  2. จากนั้นคลิกขวา folder > new file > ตั้งชื่อไฟล์ใหม่ BasicMath.sol ก็ได้

จากนั้นดูด้านขวา ก็อบโค้ดการบ้านนี้วาง (ตอนวางจะมีเตือน เราว่าระวังอย่าใส่โค้ดหลอกมั่วๆทางเน็ต ไม่ต้องตกใจ)

การก็อบโค้ดทุกครั้งมีความเสี่ยง เช่นถ้าเราก็อบ contract rug rug มา ก็จะไม่ดี


ไม่ควรเชื่อเว็บไซต์ทั่วไป

โค้ดการบ้านข้อ 1 contract ที่ช่วยเราบวกลบเลข👇

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract BasicMath {
    uint constant MAX_UINT = type(uint).max;

    function adder(uint _a, uint _b) public pure returns (uint, bool) {
        if(_a > MAX_UINT - _b) {
            return (0, true); // Overflow would occur, return error
        }
        return (_a + _b, false); // No overflow
    }

    function subtractor(uint _a, uint _b) public pure returns (uint, bool) {
        if(_b > _a) {
            return (0, true); // Underflow would occur, return error
        }
        return (_a - _b, false); // No underflow
    }
}

จากนั้นไป tab - Solidity compiler
เลือก version ให้ตรงกับในโค้ด แล้วกดปุ่ม compile

ดูให้เวอร์ชั่นตรงกันทุกครั้ง

Compile เรียบร้อย ต่อไปเรา deploy กันเลย

  1. กด tab - deploy and run

  2. เลือก injected - wallet จะมีต่อ metamask
    ตั้ง metamask เป็น Base Sepolia network ให้ถูกต้อง
    สังเกตมีเลขระบุ network 84532 = base Sepolia network

  3. กดปุ่ม deploy และ sign ให้เรียบร้อย

  4. ด้านล่างสุด เราจะเห็น contract ที่เพิ่ง deploy มาได้
    copy address เอาไว้สำหรับเคลม NFT

เท่านี้เพื่อนๆก็ deploy contract บน Base Sepolia เรียบร้อยกันแล้วนะครับ

เคลม NFT กัน

  1. ไปที่ https: docs.base.org base-camp docs deployment-to-testnet deployment-to-testnet-exercise

รับ NFT ไปสวยๆ

เรียบร้อย จบไปแล้วสำหรับการบ้าน 1
เก่งมากครับทุกคนนนน 🧡

เพื่อนๆทำเป็นแล้ว contract การบ้าน ต่อๆไปก็น่าจะประมาณนี้แหละ ครับ

ปล. ไหนๆก็ทำแล้ว เชียร์ deploy บน testnet อื่นๆเช่น Scroll, Linea ไปด้วยเลยแม้จะไม่มี NFT เป็นรางวัล

กดเปลี่ยน network แล้ว deploy รัวๆ
Scroll Alpha testnet (check network id 534353)
Linea testnet (check network id 59140)
Taiko

การ Deploy บน mainnet ต่างๆก็น่าสนใจเช่นกัน (ต้องระวังเรื่อง gas)

Polygon zkEVM 1101
zkSync
Arb Nova 42170

และอื่นๆ บน metamask ใช้วิธีนี้ได้ทั้งหมด
โดย contract นี้มีขนาดเล็ก ใช้ gas น้อย ปลอดภัย(ทำได้แค่บวกลบเลข)


Verify contract

ต่อจาก deploy เราสามารถ verify contract ได้นะ (ไม่จำเป็น)

การ verify EVM contract ที่ deploy ไป

ไปที่ block explorer - etherscan/ basescan

คลิก verify contract

จากนั้นกรอกข้อมูลให้เรียบร้อย เลข contract address และ version solidity

ดู version ตรงนี้
license ตามที่ผมเคยสอน ส่วนใหญ่จะเป็น MIT

ใส่โค้ดที่เคยใช้เข้าไป แล้ว sign

แค่นี้การ verify ก็เรียบร้อย


การบ้าน 2-13

การบ้าน 1 ไปเรียบร้อย

ตอนนี้เพื่อนๆก็ทำเป็นแล้ว ต่อๆไป จะเป็นการก็อบโค้ดและทำซ้ำอย่างเดียวครับ :)

หากจะลุยต่อ อยากให้ลอง collect โพสด้วยปุ่มด้านล่าง 0.001 matic + gas


ทุกครั้งที่ลอกการบ้าน อย่าลืมตั้ง version complier ให้ตรงกับโค้ดทุกครั้งนะครับ

การบ้าน 2

ตั้งชื่อไฟล์ว่า ControlStructures.sol

solidity 0.8.14

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.14;

contract ControlStructures {
    // Create a custom error for AfterHours
    error AfterHours(uint256 time);

    function fizzBuzz(uint256 _number) public pure returns (string memory) {
        if (_number % 3 == 0 && _number % 5 == 0) {
            return "FizzBuzz";
        } else if (_number % 3 == 0) {
            return "Fizz";
        } else if (_number % 5 == 0) {
            return "Buzz";
        } else {
            return "Splat";
        }
    }

    function doNotDisturb(uint256 _time) public pure returns (string memory) {
        // If _time is greater than or equal to 2400, trigger a panic
        assert(_time < 2400);

        // If _time is greater than 2200 or less than 800, revert with a custom error of AfterHours
        if (_time > 2200 || _time < 800) {
            revert AfterHours(_time);
        } else if (_time >= 1200 && _time <= 1259) {
            revert("At lunch!");
        } else if (_time >= 800 && _time < 1200) {
            return "Morning!";
        } else if (_time >= 1300 && _time <= 1799) {
            return "Afternoon!";
        } else if (_time >= 1800 && _time <= 2199) {
            return "Evening!";
        } else {
            revert("Invalid time");
        }
    }
}

จากนั้น compile > deploy > เก็บ contract address ที่ได้ > claim NFT https: docs.base.org base-camp docs control-structures control-structures-exercise

Deploy บน Base เสร็จ ก็ลง testnet chain อื่นๆ ไปด้วยเช่นเคย

  • Base Sepolia

  • Scroll alpha testnet

  • Linea

  • polygon zkEVM testnet

วันไหน gas ถูกก็จัด mainnet ต่างๆ เช่น polygon zkEVM, zkSync

ลงมันทุก EVM ที่นึกได้ ถ้าไม่เปลือง gas


การบ้าน 3

เหมือนเดิม แค่ใช้โค้ดใหม่

claim https: docs.base.org base-camp docs storage storage-exercise

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract EmployeeStorage {
    uint256 public idNumber;
    uint32 private salary;
    uint16 private shares;
    string public name;

    constructor() {
        shares = 1000;
        name = "Pat";
        salary = 50000;
        idNumber = 112358132134;
    }

    function viewSalary() public view returns(uint32) {
        return salary;
    }

    function viewShares() public view returns(uint16) {
        return shares;
    }

    function grantShares(uint16 _newShares) public {
        uint16 newTotalShares = shares + _newShares;

        if (_newShares > 5000 || newTotalShares > 5000) {
            revert("Too many shares");
        }

        shares = newTotalShares;
    }

    function checkForPacking(uint _slot) public view returns (uint r) {
        assembly {
            r := sload (_slot)
        }
    }

    function debugResetShares() public {
        shares = 1000;
    }
}

การบ้าน 4

ตั้งชื่อไฟล์ว่า ArraysExercise.sol

solidity version 0.8.17

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract ArraysExercise {
    uint[] public numbers = [1,2,3,4,5,6,7,8,9,10];
    address[] public senders;
    uint[] public timestamps;

    // Return a Complete Array
    function getNumbers() public view returns (uint[] memory) {
        return numbers;
    }

    // Reset Numbers
    function resetNumbers() public {
        numbers = [1,2,3,4,5,6,7,8,9,10];
    }

    // Append to an Existing Array
    function appendToNumbers(uint[] calldata _toAppend) public {
        uint originalLength = numbers.length;
        uint toAppendLength = _toAppend.length;

        uint[] memory newNumbers = new uint[](originalLength + toAppendLength);

        for (uint i = 0; i < originalLength; i++) {
            newNumbers[i] = numbers[i];
        }

        for (uint j = 0; j < toAppendLength; j++) {
            newNumbers[originalLength + j] = _toAppend[j];
        }

        numbers = newNumbers;
    }

    // Timestamp Saving
    function saveTimestamp(uint _unixTimestamp) public {
        senders.push(msg.sender);
        timestamps.push(_unixTimestamp);
    }

    // Timestamp Filtering
    function afterY2K() public view returns (uint[] memory, address[] memory) {
        uint counter = 0;
        for (uint i = 0; i < timestamps.length; i++) {
            if (timestamps[i] > 946702800) {
                counter++;
            }
        }

        uint[] memory postY2KTimestamps = new uint[](counter);
        address[] memory postY2KSenders = new address[](counter);

        counter = 0;
        for (uint i = 0; i < timestamps.length; i++) {
            if (timestamps[i] > 946702800) {
                postY2KTimestamps[counter] = timestamps[i];
                postY2KSenders[counter] = senders[i];
                counter++;
            }
        }

        return (postY2KTimestamps, postY2KSenders);
    }

    // Resets
    function resetSenders() public {
        delete senders;
    }

    function resetTimestamps() public {
        delete timestamps;
    }
}

claim NFT https: docs.base.org base-camp docs arrays arrays-exercise


การบ้าน 5

FavoriteRecords.sol

solidity version 0.8.19

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract FavoriteRecords {
    
    // Error type for records not being approved
    error NotApproved(string record);
    
    // State variables
    mapping(string => bool) public approvedRecords;
    mapping(address => mapping(string => bool)) private userFavorites;
    string[] private approvedRecordNames;
    
    constructor() {
        // Load approved records
        string[9] memory records = [
            "Thriller",
            "Back in Black",
            "The Bodyguard",
            "The Dark Side of the Moon",
            "Their Greatest Hits (1971-1975)",
            "Hotel California",
            "Come On Over",
            "Rumours",
            "Saturday Night Fever"
        ];
        for(uint i=0; i<records.length; i++) {
            approvedRecords[records[i]] = true;
            approvedRecordNames.push(records[i]);
        }
    }
    
    // Get approved records
    function getApprovedRecords() public view returns(string[] memory) {
        return approvedRecordNames;
    }
    
    // Add record to favorites
    function addRecord(string memory record) public {
        if(approvedRecords[record]) {
            userFavorites[msg.sender][record] = true;
        } else {
            revert NotApproved(record);
        }
    }
    
    // Get user favorites
    function getUserFavorites(address user) public view returns(string[] memory) {
        uint count = 0;
        for(uint i=0; i<approvedRecordNames.length; i++) {
            if(userFavorites[user][approvedRecordNames[i]]) {
                count++;
            }
        }
        
        string[] memory favorites = new string[](count);
        uint index = 0;
        for(uint i=0; i<approvedRecordNames.length; i++) {
            if(userFavorites[user][approvedRecordNames[i]]) {
                favorites[index] = approvedRecordNames[i];
                index++;
            }
        }
        return favorites;
    }
    
    // Reset user favorites
    function resetUserFavorites() public {
        for(uint i=0; i<approvedRecordNames.length; i++) {
            userFavorites[msg.sender][approvedRecordNames[i]] = false;
        }
    }
}

claim NFT https: docs.base.org base-camp docs mappings mappings-exercise


การบ้าน 6

GarageManager.sol
solidity 0.8.19

Structs NFT Badge
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract GarageManager {
    // Car struct definition
    struct Car {
        string make;
        string model;
        string color;
        uint numberOfDoors;
    }

    // mapping to store a list of cars, indexed by address
    mapping(address => Car[]) public garage;

    // function to add a car to the user's collection in the garage
    function addCar(string memory _make, string memory _model, string memory _color, uint _numberOfDoors) public {
        Car memory newCar = Car({
            make: _make,
            model: _model,
            color: _color,
            numberOfDoors: _numberOfDoors
        });
        garage[msg.sender].push(newCar);
    }

    // function to return all cars owned by the calling user
    function getMyCars() public view returns (Car[] memory) {
        return garage[msg.sender];
    }

    // function to return all cars owned by a specific user
    function getUserCars(address user) public view returns (Car[] memory) {
        return garage[user];
    }

    // function to update a car of the calling user
    function updateCar(uint index, string memory _make, string memory _model, string memory _color, uint _numberOfDoors) public {
        if (index >= garage[msg.sender].length) {
            revert("BadCarIndex");
        }

        garage[msg.sender][index] = Car({
            make: _make,
            model: _model,
            color: _color,
            numberOfDoors: _numberOfDoors
        });
    }

    // function to delete the entry in garage for the sender
    function resetMyGarage() public {
        delete garage[msg.sender];
    }
}

claim nft https: docs.base.org base-camp docs structs structs-exercise


การบ้าน 7

อันนี้อยู่ๆก็อัพเวล เพราะเราจะ deploy หลาย contract ในการบ้านนี้
แอดนาโนจะจับมือไปทีละขั้นตอน อิอิ

1) สร้าง ไฟล์ Employee.sol
ใส่โค้ด

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

abstract contract Employee {
    uint public idNumber;
    uint public managerId;

    constructor(uint _idNumber, uint _managerId) {
        idNumber = _idNumber;
        managerId = _managerId;
    }

    function getAnnualCost() public virtual view returns(uint);
}

contract Salaried is Employee {
    uint public annualSalary;

    constructor(uint _idNumber, uint _managerId, uint _annualSalary) Employee(_idNumber, _managerId) {
        annualSalary = _annualSalary;
    }

    function getAnnualCost() public override view returns(uint) {
        return annualSalary;
    }
}

contract Hourly is Employee {
    uint public hourlyRate;

    constructor(uint _idNumber, uint _managerId, uint _hourlyRate) Employee(_idNumber, _managerId) {
        hourlyRate = _hourlyRate;
    }

    function getAnnualCost() public override view returns(uint) {
        return hourlyRate * 2080;
    }
}

contract Manager {
    uint[] public employeeIds;

    function addReport(uint idNumber) public {
        employeeIds.push(idNumber);
    }

    function resetReports() public {
        delete employeeIds;
    }
}

contract Salesperson is Hourly {
    constructor(uint _idNumber, uint _managerId, uint _hourlyRate) Hourly(_idNumber, _managerId, _hourlyRate) {}
}

contract EngineeringManager is Salaried, Manager {
    constructor(uint _idNumber, uint _managerId, uint _annualSalary) Salaried(_idNumber, _managerId, _annualSalary) {}
}

2) เริ่ม Deploy เลือก contract "Engineer manager" ใส่ 54321, 11111, 200000

3)Deploy เลือก contract "Salesperson"
ใส่ 55555,12345,20

4) สร้าง InheritanceSubmission.sol

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

contract InheritanceSubmission {
    address public salesPerson;
    address public engineeringManager;

    constructor(address _salesPerson, address _engineeringManager) {
        salesPerson = _salesPerson;
        engineeringManager = _engineeringManager;
    }
}

deploy ตามด้านล่าง
- ก็อบ contract address ของ saleperson ใส่ก่อน
- ตามด้วย comma (,)
- จากนั้นใส่ contract address ของ engineer
- กด deploy

ก็อบ contract address ของ inheritancesubmission (ลูกศรเขียว)
ไปใช้เคลม NFT reward

claim nft https: docs.base.org base-camp docs inheritance inheritance-exercise


การบ้าน 8

กลับมาง่ายเหมือนเดิมแล้ว

Silly.sol
Solidity 0.8.17

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

library SillyStringUtils {
    struct Haiku {
        string line1;
        string line2;
        string line3;
    }

    function shruggie(string memory _input) internal pure returns (string memory) {
        return string(abi.encodePacked(_input, " \xF0\x9F\xA4\xB7")); // Adding space before the emoji
    }
}

contract ImportsExercise {
    using SillyStringUtils for string;

    SillyStringUtils.Haiku public haiku;

    function saveHaiku(string memory _line1, string memory _line2, string memory _line3) public {
        haiku = SillyStringUtils.Haiku({
            line1: _line1,
            line2: _line2,
            line3: _line3
        });
    }

    function getHaiku() public view returns (SillyStringUtils.Haiku memory) {
        return haiku;
    }

    function shruggieHaiku() public view returns (SillyStringUtils.Haiku memory) {
        SillyStringUtils.Haiku memory newHaiku = haiku;
        newHaiku.line3 = newHaiku.line3.shruggie();
        return newHaiku;
    }
}

claim NFT https: docs.base.org base-camp docs imports imports-exercise


การบ้าน 9

errorFix.sol
0.8.17

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

contract ErrorTriageExercise {
    /**
     * Finds the difference between each uint with it's neighbor (a to b, b to c, etc.)
     * and returns a uint array with the absolute integer difference of each pairing.
     */
    function diffWithNeighbor(
        uint _a,
        uint _b,
        uint _c,
        uint _d
    ) public pure returns (uint[] memory) {
        uint[] memory results = new uint[](3);

        results[0] = _a > _b ? _a - _b : _b - _a;
        results[1] = _b > _c ? _b - _c : _c - _b;
        results[2] = _c > _d ? _c - _d : _d - _c;

        return results;
    }

    /**
     * Changes the _base by the value of _modifier.  Base is always > 1000.  Modifiers can be
     * between positive and negative 100;
     */
    function applyModifier(
        uint _base,
        int _modifier
    ) public pure returns (uint) {
        require(_modifier >= -100 && _modifier <= 100, "Modifier should be between -100 and 100");
        return uint(int(_base) + _modifier);
    }

    /**
     * Pop the last element from the supplied array, and return the modified array and the popped
     * value (unlike the built-in function)
     */
    uint[] arr;

    function popWithReturn() public returns (uint) {
        require(arr.length > 0, "Array is empty");
        uint index = arr.length - 1;
        uint value = arr[index];
        arr.pop();
        return value;
    }

    // The utility functions below are working as expected
    function addToArr(uint _num) public {
        arr.push(_num);
    }

    function getArr() public view returns (uint[] memory) {
        return arr;
    }

    function resetArr() public {
        delete arr;
    }
}

claim NFT https: docs.base.org base-camp docs error-triage error-triage-exercise


การบ้าน 10

คนติดข้อนี้เยอะมาก แอดเคยทำได้ ตอนนี้ลองใหม่ก็ติด คิดว่าเป็นเพราะ remix

กลับมายากนิดหน่อยกันอีกครั้ง เพราะงวดนี้เราจะสร้าง contractFactory ที่ช่วยเราสร้าง contract อีกที (งงดิ)

Contact.sol solidity 0.8.17
ก็อบโค้ดนี้เข้า remix จากนั้นอ่านวิธีต่อด้านล่าง

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

import "@openzeppelin/contracts/access/Ownable.sol";

contract AddressBook is Ownable {

    struct Contact {
        uint id;
        string firstName;
        string lastName;
        uint[] phoneNumbers;
    }

    mapping (uint => Contact) private contacts;
    uint private contactCounter;

    function addContact(
        string memory _firstName,
        string memory _lastName,
        uint[] memory _phoneNumbers
    ) public onlyOwner returns (uint) {
        contacts[contactCounter] = Contact(contactCounter, _firstName, _lastName, _phoneNumbers);
        return contactCounter++;
    }

    function deleteContact(uint _id) public onlyOwner {
        require(contacts[_id].id == _id, "ContactNotFound");
        delete contacts[_id];
    }

    function getContact(uint _id) public view returns (Contact memory) {
        require(contacts[_id].id == _id, "ContactNotFound");
        return contacts[_id];
    }

    function getAllContacts() public view returns (Contact[] memory) {
        Contact[] memory contactArray = new Contact[](contactCounter);
        for (uint i = 0; i < contactCounter; i++) {
            if (contacts[i].id == i) {
                contactArray[i] = contacts[i];
            }
        }
        return contactArray;
    }
}

contract AddressBookFactory {
    function deploy() public returns (address) {
        AddressBook addrBook = new AddressBook();
        addrBook.transferOwnership(msg.sender);
        return address(addrBook);
    }
}

เมื่อก็อบโค้ดวางเรียบร้อย ให้เลือก contract AddressBookFactory แล้วกด deploy

จากนั้นให้เข้า metamask ดู transaction ล่าสุด ก็อบ contract address มาตามปกติ

เอา contract address ที่เพิ่งได้ไปใส่ใน remix ใต้ปุ่ม deploy
ตรงปุ่ม at Address
จากนั้นกดปุ่ม deploy จาก contract

เรียบร้อย เราจะได้ contract ใหม่อีกตัว
ก็อบเลข contract อันเดิม (ในตัวอย่างก็ 0x755 ไปใช้เคลม NFT)

claim NFT https: docs.base.org base-camp docs new-keyword new-keyword-exercise


11-13 solutions from
https://github.com/harsharock/BaseCamp/tree/main thank you

การบ้าน 11

minimol.sol
0.8.0

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// Contract for an unburnable token
contract UnburnableToken {
    string private salt = "value"; // A private string variable

    // Mapping to track token balances of addresses
    mapping(address => uint256) public balances;

    uint256 public totalSupply; // Total supply of tokens
    uint256 public totalClaimed; // Total number of tokens claimed
    mapping(address => bool) private claimed; // Mapping to track whether an address has claimed tokens

    // Custom errors
    error TokensClaimed(); // Error for attempting to claim tokens again
    error AllTokensClaimed(); // Error for attempting to claim tokens when all are already claimed
    error UnsafeTransfer(address _to); // Error for unsafe token transfer

    // Constructor to set the total supply of tokens
    constructor() {
        totalSupply = 100000000; // Set the total supply of tokens
    }

    // Public function to claim tokens
    function claim() public {
        // Check if all tokens have been claimed
        if (totalClaimed >= totalSupply) revert AllTokensClaimed();
        
        // Check if the caller has already claimed tokens
        if (claimed[msg.sender]) revert TokensClaimed();

        // Update balances and claimed status
        balances[msg.sender] += 1000;
        totalClaimed += 1000;
        claimed[msg.sender] = true;
    }

    // Public function for safe token transfer
    function safeTransfer(address _to, uint256 _amount) public {
        // Check for unsafe transfer conditions, including if the target address has a non-zero ether balance
        if (_to == address(0) || _to.balance == 0) revert UnsafeTransfer(_to);

        // Ensure the sender has enough balance to transfer
        require(balances[msg.sender] >= _amount, "Insufficient balance");

        // Perform the transfer
        balances[msg.sender] -= _amount;
        balances[_to] += _amount;
    }
}

claim NFT https: docs.base.org base-camp docs minimal-tokens minimal-tokens-exercise


การบ้าน 12

erc20.sol
0.8.17

ใส่ชื่ออะไรก็ได้ตรงปุ่ม deploy

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;

// Importing OpenZeppelin contracts for ERC20 and EnumerableSet functionalities
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

// Contract for weighted voting using ERC20 token
contract WeightedVoting is ERC20 {
    string private salt = "value"; // A private string variable
    using EnumerableSet for EnumerableSet.AddressSet; // Importing EnumerableSet for address set functionality

    // Custom errors
    error TokensClaimed(); // Error for attempting to claim tokens again
    error AllTokensClaimed(); // Error for attempting to claim tokens when all are already claimed
    error NoTokensHeld(); // Error for attempting to perform an action without holding tokens
    error QuorumTooHigh(); // Error for setting a quorum higher than total supply
    error AlreadyVoted(); // Error for attempting to vote more than once
    error VotingClosed(); // Error for attempting to vote on a closed issue

    // Struct to represent an issue
    struct Issue {
        EnumerableSet.AddressSet voters; // Set of voters
        string issueDesc; // Description of the issue
        uint256 quorum; // Quorum required to close the issue
        uint256 totalVotes; // Total number of votes casted
        uint256 votesFor; // Total number of votes in favor
        uint256 votesAgainst; // Total number of votes against
        uint256 votesAbstain; // Total number of abstained votes
        bool passed; // Flag indicating if the issue passed
        bool closed; // Flag indicating if the issue is closed
    }

    // Struct to represent a serialized issue
    struct SerializedIssue {
        address[] voters; // Array of voters
        string issueDesc; // Description of the issue
        uint256 quorum; // Quorum required to close the issue
        uint256 totalVotes; // Total number of votes casted
        uint256 votesFor; // Total number of votes in favor
        uint256 votesAgainst; // Total number of votes against
        uint256 votesAbstain; // Total number of abstained votes
        bool passed; // Flag indicating if the issue passed
        bool closed; // Flag indicating if the issue is closed
    }

    // Enum to represent different vote options
    enum Vote {
        AGAINST,
        FOR,
        ABSTAIN
    }

    // Array to store all issues
    Issue[] internal issues;

    // Mapping to track if tokens are claimed by an address
    mapping(address => bool) public tokensClaimed;

    uint256 public maxSupply = 1000000; // Maximum supply of tokens
    uint256 public claimAmount = 100; // Amount of tokens to be claimed

    string saltt = "any"; // Another string variable

    // Constructor to initialize ERC20 token with a name and symbol
    constructor(string memory _name, string memory _symbol)
        ERC20(_name, _symbol)
    {
        issues.push(); // Pushing an empty issue to start from index 1
    }

    // Function to claim tokens
    function claim() public {
        // Check if all tokens have been claimed
        if (totalSupply() + claimAmount > maxSupply) {
            revert AllTokensClaimed();
        }
        // Check if the caller has already claimed tokens
        if (tokensClaimed[msg.sender]) {
            revert TokensClaimed();
        }
        // Mint tokens to the caller
        _mint(msg.sender, claimAmount);
        tokensClaimed[msg.sender] = true; // Mark tokens as claimed
    }

    // Function to create a new voting issue
    function createIssue(string calldata _issueDesc, uint256 _quorum)
        external
        returns (uint256)
    {
        // Check if the caller holds any tokens
        if (balanceOf(msg.sender) == 0) {
            revert NoTokensHeld();
        }
        // Check if the specified quorum is higher than total supply
        if (_quorum > totalSupply()) {
            revert QuorumTooHigh();
        }
        // Create a new issue and return its index
        Issue storage _issue = issues.push();
        _issue.issueDesc = _issueDesc;
        _issue.quorum = _quorum;
        return issues.length - 1;
    }

    // Function to get details of a voting issue
    function getIssue(uint256 _issueId)
        external
        view
        returns (SerializedIssue memory)
    {
        Issue storage _issue = issues[_issueId];
        return
            SerializedIssue({
                voters: _issue.voters.values(),
                issueDesc: _issue.issueDesc,
                quorum: _issue.quorum,
                totalVotes: _issue.totalVotes,
                votesFor: _issue.votesFor,
                votesAgainst: _issue.votesAgainst,
                votesAbstain: _issue.votesAbstain,
                passed: _issue.passed,
                closed: _issue.closed
            });
    }

    // Function to cast a vote on a voting issue
    function vote(uint256 _issueId, Vote _vote) public {
        Issue storage _issue = issues[_issueId];

        // Check if the issue is closed
        if (_issue.closed) {
            revert VotingClosed();
        }
        // Check if the caller has already voted
        if (_issue.voters.contains(msg.sender)) {
            revert AlreadyVoted();
        }

        uint256 nTokens = balanceOf(msg.sender);
        // Check if the caller holds any tokens
        if (nTokens == 0) {
            revert NoTokensHeld();
        }

        // Update vote counts based on the vote option
        if (_vote == Vote.AGAINST) {
            _issue.votesAgainst += nTokens;
        } else if (_vote == Vote.FOR) {
            _issue.votesFor += nTokens;
        } else {
            _issue.votesAbstain += nTokens;
        }

        // Add the caller to the list of voters and update total votes count
        _issue.voters.add(msg.sender);
        _issue.totalVotes += nTokens;

        // Close the issue if quorum is reached and determine if it passed
        if (_issue.totalVotes >= _issue.quorum) {
            _issue.closed = true;
            if (_issue.votesFor > _issue.votesAgainst) {
                _issue.passed = true;
            }
        }
    }
}

claim NFT https://docs.base.org/base-camp/docs/erc-20-token/erc-20-exercise


การบ้าน 13

erc721.sol
0.8.26 ได้

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// Importing OpenZeppelin ERC721 contract
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol";

// Interface for interacting with a submission contract
interface ISubmission {
    // Struct representing a haiku
    struct Haiku {
        address author; // Address of the haiku author
        string line1; // First line of the haiku
        string line2; // Second line of the haiku
        string line3; // Third line of the haiku
    }

    // Function to mint a new haiku
    function mintHaiku(
        string memory _line1,
        string memory _line2,
        string memory _line3
    ) external;

    // Function to get the total number of haikus
    function counter() external view returns (uint256);

    // Function to share a haiku with another address
    function shareHaiku(uint256 _id, address _to) external;

    // Function to get haikus shared with the caller
    function getMySharedHaikus() external view returns (Haiku[] memory);
}

// Contract for managing Haiku NFTs
contract HaikuNFT is ERC721, ISubmission {
    Haiku[] public haikus; // Array to store haikus
    mapping(address => mapping(uint256 => bool)) public sharedHaikus; // Mapping to track shared haikus
    uint256 public haikuCounter; // Counter for total haikus minted

    // Constructor to initialize the ERC721 contract
    constructor() ERC721("HaikuNFT", "HAIKU") {
        haikuCounter = 1; // Initialize haiku counter
    }

    string salt = "value"; // A private string variable

    // Function to get the total number of haikus
    function counter() external view override returns (uint256) {
        return haikuCounter;
    }

    // Function to mint a new haiku
    function mintHaiku(
        string memory _line1,
        string memory _line2,
        string memory _line3
    ) external override {
        // Check if the haiku is unique
        string[3] memory haikusStrings = [_line1, _line2, _line3];
        for (uint256 li = 0; li < haikusStrings.length; li++) {
            string memory newLine = haikusStrings[li];
            for (uint256 i = 0; i < haikus.length; i++) {
                Haiku memory existingHaiku = haikus[i];
                string[3] memory existingHaikuStrings = [
                    existingHaiku.line1,
                    existingHaiku.line2,
                    existingHaiku.line3
                ];
                for (uint256 eHsi = 0; eHsi < 3; eHsi++) {
                    string memory existingHaikuString = existingHaikuStrings[
                        eHsi
                    ];
                    if (
                        keccak256(abi.encodePacked(existingHaikuString)) ==
                        keccak256(abi.encodePacked(newLine))
                    ) {
                        revert HaikuNotUnique();
                    }
                }
            }
        }

        // Mint the haiku NFT
        _safeMint(msg.sender, haikuCounter);
        haikus.push(Haiku(msg.sender, _line1, _line2, _line3));
        haikuCounter++;
    }

    // Function to share a haiku with another address
    function shareHaiku(uint256 _id, address _to) external override {
        require(_id > 0 && _id <= haikuCounter, "Invalid haiku ID");

        Haiku memory haikuToShare = haikus[_id - 1];
        require(haikuToShare.author == msg.sender, "NotYourHaiku");

        sharedHaikus[_to][_id] = true;
    }

    // Function to get haikus shared with the caller
    function getMySharedHaikus()
        external
        view
        override
        returns (Haiku[] memory)
    {
        uint256 sharedHaikuCount;
        for (uint256 i = 0; i < haikus.length; i++) {
            if (sharedHaikus[msg.sender][i + 1]) {
                sharedHaikuCount++;
            }
        }

        Haiku[] memory result = new Haiku[](sharedHaikuCount);
        uint256 currentIndex;
        for (uint256 i = 0; i < haikus.length; i++) {
            if (sharedHaikus[msg.sender][i + 1]) {
                result[currentIndex] = haikus[i];
                currentIndex++;
            }
        }

        if (sharedHaikuCount == 0) {
            revert NoHaikusShared();
        }

        return result;
    }

    // Custom errors
    error HaikuNotUnique(); // Error for attempting to mint a non-unique haiku
    error NotYourHaiku(); // Error for attempting to share a haiku not owned by the caller
    error NoHaikusShared(); // Error for no haikus shared with the caller
}

claim NFT https://docs.base.org/base-camp/docs/erc-721-token/erc-721-exercise


สำหรับเพื่อนๆที่ทำถึงตรงนี้ยินดีด้วยครับ คุณได้สร้าง 13 NFT สำหรับ BaseCamp

ตรวจ nft base sepolia ได้ที่ (อันที่เคยทำบน goerli จะไม่โชว์ แต่ก็นับ) https://docs.base.org/base-camp/progress/

NFT ต่างๆที่เราได้มาจะมีการโอนไป Base mainnet ในช่วงส้ินปีนี้ด้วย

และ contract ต่างๆที่เราได้เรียนรู้
สามารถนำไปdeploy บน testnet อื่นๆได้สบาย

เมื่อทำครบหมดให้เข้า guild เพื่อรับ discord role ได้เลย

https://guild.xyz/base/base-camp

ถ้าถูกใจสามารถช่วยเลี้ยงกาแฟ nanobro ได้โดยการ collect โพสด้านล่างคร้าบ ขอบคุณมากๆครับ🙏

Loading...
highlight
Collect this post to permanently own it.
nanobro.eth logo
Subscribe to nanobro.eth and never miss a post.
#base#l2