---
# System prepended metadata

title: 5/23
tags: [畢專, z-institute]

---

###### tags: `z-institute` `畢專`
# 5/23
5/9
### Ticket.sol
https://dzone.com/articles/build-a-web3-ticketing-system-and-disrupt-ticketma-1
```
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; 
import "@openzeppelin/contracts/access/Ownable.sol"; 
import "@openzeppelin/contracts/utils/Strings.sol"; 
import "@openzeppelin/contracts/utils/Counters.sol"; 

contract Ticket is Ownable, ERC1155("https://donexttime.com/{id}.json") { 
    using Counters for Counters.Counter; // 使用 Counters 函數庫來計數 NFT 的 ID    //可以先放在Remix上編輯
    Counters.Counter private _tokenIds; // 建立一個 _tokenIds 的計數器
    
    uint256 public constant MAX_SUPPLY = 1000; // 定義 MAX_SUPPLY 為 1000
    address[MAX_SUPPLY] private _owners; // 建立一個 _owners 的地址數組，長度為 MAX_SUPPLY
    uint256 public constant MAX_PER_MINT = 1; // 定義 MAX_PER_MINT 為 1
    uint256 public constant PRICE = 0.01 ether; // 定義 PRICE 為 0.01 ether
    
    mapping(address => Ticket[]) private _ticketsOwned; // 

    struct Ticket { // 建立一個名為 Ticket 的結構
        uint256 expireTime; 
        string name;
    }

    event TicketUsed(address indexed owner, string name); // 建立一個 TicketUsed 事件

    constructor(string memory uri) ERC1155(uri) {
      _setTokenURI(newTokenID, _tokenURI); //然後使用 _setTokenURI 函數將URI 設置給新的 NFT
    }

    function mintNfts(uint256 count) external payable { // 鑄造 NFT 函數
        uint256 nextId = _tokenIds.current(); // 取得下一個 token 的 ID

        require(nextId + count < MAX_SUPPLY, "Supply limit exceeded"); // 判斷是否超過了最大供應量
        require(count > 0 && count <= MAX_PER_MINT, "Can only mint one NFT per address"); // 判斷每個地址是否只能鑄造一個 NFT
        require(msg.value >= PRICE * count, "Insufficient ether sent to purchase tickets"); //判斷是否付了足夠的錢

    //這個合約中的mintNfts和_mintSingleNft函數都是用於創建新的NFT。它們的區別在於mintNfts用於一次性創建多個NFT，而_mintSingleNft用於創建一個單獨的NFT。
    //具體來說，mintNfts接受一個參數_count，表示要創建的NFT的數量，然後循環_count次調用_mintSingleNft來創建每個NFT
        for (uint256 i = 0; i < count; i++) {
            string memory metadata = uri(nextId + i);
            _mintSingleNft(msg.sender, metadata);
            _ticketsOwned[msg.sender].push(Ticket(block.timestamp + 1 days, "1 day ticket"));
        }
    }
    function _mintSingleNft(address owner, string memory tokenURI) private { //只能被合約內部的其他函數調用，不能被外部調用。
        require(totalSupply() == _tokenIds.current, "Indexing has broken down!"); //檢查目前的總供應量是否等於 _tokenIds 的當前值，如果不是，則代表索引已經出現了問題，此時函數將停止執行。
        uint newTokenID = _tokenIds.current(); //獲取當前的token ID
        _tokenIds.increment(); //增加 _tokenIds 的值，以便下次創建新的 NFT 時，其token ID 能夠自動增加。
    }
  
    function useTicket(string memory name) external {
        for (uint256 i = 0; i < _ticketsOwned[msg.sender].length; i++) { //先遍歷 _ticketsOwned 數組，找到名稱為 name 的票
            if (keccak256(bytes(_ticketsOwned[msg.sender][i].name)) == keccak256(bytes(name))) {//如果找到了與 name 相等的票券
                delete _ticketsOwned[msg.sender][i];// 如果找到了，則delete 該票券
                //並發出 TicketUsed 事件，表示票券已被使用。然後函數返回。
                emit TicketUsed(msg.sender, name);
                return;
            }
        }
        revert("No ticket found");
    }

    function withdraw() external onlyOwner {
        uint256 balance = address(this).balance;
        require(balance > 0, "No ether left to withdraw");

        (bool success, ) = payable(msg.sender).call{value: balance}("");//只有合約擁有者才能夠使用這個函數來提取合約中的以太幣餘額
        require(success, "Transfer failed");//如果轉移失敗，將會觸發 require 斷言，使函數執行停止
    }

    function _burn(address account, uint256 id, uint256 amount) internal virtual override {
        super._burn(account, id, amount);
    }

    function uri(uint256 tokenId) public pure override returns (string memory) {
        return string(abi.encodePacked("https://donexttime.com/", Strings.toString(tokenId), ".json"));
    }

    function supportsInterface(bytes4 interfaceId)public view
        override(ERC1155)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
}


```

<!--     function _mintSingleNft(address owner, string memory tokenURI) private { //只能被合約內部的其他函數調用，不能被外部調用。
        require(totalSupply() == _tokenIds.current, "Indexing has broken down!"); //檢查目前的總供應量是否等於 _tokenIds 的當前值，如果不是，則代表索引已經出現了問題，此時函數將停止執行。
        uint newTokenID = _tokenIds.current(); //獲取當前的token ID
        _setTokenURI(newTokenID, _tokenURI); //然後使用 _setTokenURI 函數將URI 設置給新的 NFT
        _tokenIds.increment(); //增加 _tokenIds 的值，以便下次創建新的 NFT 時，其token ID 能夠自動增加。
    } -->


### test.js
//測試時可以分功能測試
```
const { expect } = require("chai");

describe("Ticket", async function () {
  const [owner,] = await ethers.getSigners();
  let nft;
  let nftContractAddress;
   
  beforeEach('Ticket', async () => {
        const Ticket = await ethers.getContractFactory('Ticket')
        nft = await Ticket.deploy()
        await nft.deployed()
        nftContractAddress = await nft.address
  })
    
it("Should mint NFTs", async function () {
  const count = 1;
  const price = ethers.utils.parseEther("0.01");
  const tokenId = await nft.tokenIds();
  await expect(nft.mintNfts(count, {value: price}))
    .to.emit(nft, "TransferSingle")
    .withArgs(owner.address, ethers.constants.AddressZero, owner.address, tokenId, count);
  expect(await nft.balanceOf(owner.address, tokenId)).to.equal(count);
});

  it("Should support interface", async function () {
    expect(await nft.supportsInterface("ERC1155 "), false);
  });
});

```
使用ethers.js的getContractFactory和deploy函數創建新的Ticket合約實例，然後獲取其地址並存儲到變數nftContractAddress中。此外，它還獲取簽署者帳戶的地址並存儲到變數owner中。

測試案例檢查 mintNfts 函數是否正確地鑄造（創建）了新的 NFT，並將其發送到指定的地址。該函數期望通過傳遞一個 count 和一個以以太幣為單位的 price 參數，來鑄造指定數量的 NFT，並將它們發送到調用者的地址。

測試的第一部分使用 expect 函數檢查是否會在調用 mintNfts 函數時觸發一個名為 TransferSingle 的事件，這個事件會包含一些參數，其中包括調用者的地址、空地址、接收者的地址、NFT 的 ID 和數量等。這個事件的觸發表示鑄造 NFT 的過程已經正確地完成。如果事件觸發成功，測試就通過了。

測試的第二部分使用 expect 和 await 函數，檢查該合約的餘額是否符合預期。它通過調用 balanceOf 函數檢查調用者的地址在指定 NFT ID 下持有的 NFT 數量是否等於 count。如果餘額等於 count，則測試通過。

supportsInterface：
用於檢查合約是否實現了ERC1155介面。

Q 怎麼寫URI測試?

之後補withdraw/burn/..測試

MINT權限
數量
name

---
5/9 更改版
```
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract Ticket is Ownable, ERC1155 {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds; // 建立一個 _tokenIds 的計數器
    
    uint256 public constant MAX_SUPPLY = 1000;
    address[MAX_SUPPLY] private _owners;
    uint256 public constant MAX_PER_MINT = 2;
    uint256 public constant PRICE = 0.01 ether;
    
    mapping(address => MyTicket[]) private _ticketsOwned;

    struct MyTicket {
        uint256 expireTime;
        string name;
    }

    event TicketUsed(address indexed owner, string name);

    constructor(string memory uri) ERC1155(uri) {

        // Mint a ticket and add it to the user's list
    }

    function mintNfts(uint256 count) external payable { // mint NFTs
        uint256 nextId = _tokenIds.current(); // get the next ID

        require(nextId + count < MAX_SUPPLY, "Supply limit exceeded");
        require(count > 0 && count <= MAX_PER_MINT, "Can only mint one NFT per address");
        require(msg.value >= PRICE * count, "Insufficient ether sent to purchase tickets");

        for (uint256 i = 0; i < count; i++) {
            string memory metadata = uri(nextId + i);
            _mintSingleNft(msg.sender, metadata);
        }
    }

    //function uri(uint256 tokenId) public pure override returns (string memory) {
    //   return string(abi.encodePacked("https://donexttime.com/", Strings.toString(tokenId), ".json"));
    //}
    
    function useTicket(string memory name) external {
        for (uint256 i = 0; i < _ticketsOwned[msg.sender].length; i++) {
            if (keccak256(bytes(_ticketsOwned[msg.sender][i].name)) == keccak256(bytes(name))) {
                delete _ticketsOwned[msg.sender][i];
                emit TicketUsed(msg.sender, name);
                return;
            }
        }
        revert("No ticket found");
    }

    function withdraw() external onlyOwner {
        uint256 balance = address(this).balance;
        require(balance > 0, "No ether left to withdraw");

        (bool success, ) = payable(msg.sender).call{value: balance}("");
        require(success, "Transfer failed");
    }

    function _burn(address account, uint256 id, uint256 amount) internal virtual override {
        super._burn(account, id, amount);
    }
   
    function _mintSingleNft(address owner, string memory tokenURI) private {
        require(MAX_SUPPLY == _tokenIds.current(), "Indexing has broken down!");
        uint newTokenID = _tokenIds.current(); 
        _tokenIds.increment(); 
    }
    function supportsInterface(bytes4 interfaceId)public view
        override(ERC1155)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
}
```

---
1. 繼續完成畢業專案智能合約與開始做前端介面設計！！並寫一些基本智能合約 function 的測試 💪
2. 將畢業專案上傳至各組的 repository 內，請把目前進度推上去，之後都用這個來追蹤
    
    第二組 : https://github.com/z-institute/Solidity-EVM-Dev-Batch-2-Group-2
    
    第三組 : https://github.com/z-institute/Solidity-EVM-Dev-Batch-2-Group-3
    
3. 下次上課會檢查畢業專案的整體進度，並在課前準備好程式相關的 debug 問題，可用 Github Issues 方式整理問題與提交，到時候會較有效率
4. 

---
IPFS安裝筆記（專題）
https://circular-seed-c96.notion.site/2d25394237904ca58a5dc0efa443e69d

----
```
const { expect } = require("chai");

describe("Ticket", async function () {
  const [owner, user] = await ethers.getSigners();
  let nft;
  let nftContractAddress;
  const MAX_SUPPLY = 1000;
  const MAX_PER_MINT = 2;
  const PRICE = ethers.utils.parseEther("0.01"); 
  
  beforeEach('Ticket', async () => {
        const Ticket = await ethers.getContractFactory('Ticket')
        nft = await Ticket.deploy("https://example.com/{id}.json")
        await nft.deployed()
        nftContractAddress = await nft.address
  })
    
  it("Should mint NFTs", async function () {
    const count = 1;
    const price = ethers.utils.parseEther("0.01");
    const tokenId = await nft.tokenIds();
    await expect(nft.mintNfts(count, {value: price}))
      .to.emit(nft, "TransferSingle")
      .withArgs(owner.address, ethers.constants.AddressZero, owner.address, tokenId, count);
    expect(await nft.balanceOf(owner.address, tokenId)).to.equal(count);
  });

  it("Should use ticket and remove from _ticketsOwned", async function () {
    await nft.mintNfts(1, {value: ethers.utils.parseEther("0.01")});
    const tokenId = await nft.tokenIds();
    await nft.connect(owner).useTicket("test");
    expect((await nft._ticketsOwned(owner.address)).length).to.equal(0);
  });

  it("Should withdraw ether to owner address", async function () {
    await nft.mintNfts(1, {value: ethers.utils.parseEther("0.01")});
    const balanceBefore = await ethers.provider.getBalance(owner.address);
    await nft.connect(owner).withdraw();
    const balanceAfter = await ethers.provider.getBalance(owner.address);
    expect(balanceAfter.sub(balanceBefore)).to.equal(ethers.utils.parseEther("0.01"));
  });

  it("Should burn NFT", async function () {
    await nft.mintNfts(1, {value: ethers.utils.parseEther("0.01")});
    const tokenId = await nft.tokenIds();
    await expect(nft.burn(tokenId, 1))
      .to.emit(nft, "TransferSingle")
      .withArgs(owner.address, owner.address, ethers.constants.AddressZero, tokenId, 1);
    expect(await nft.balanceOf(owner.address, tokenId)).to.equal(0);
  });

  it("Should support interface", async function () {
    expect(await nft.supportsInterface("0xd9b67a26"), true);
  });
});

```

user can buy ticket



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

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

contract BookingSystem is Ownable {
    Ticket private ticketContract;

    struct Event {
        string eventName;
        uint256 maxSupply;
        uint256 startTime;
        uint256 price;
    }

    mapping(uint256 => Event) public events;
    mapping(address => mapping(uint256 => uint256)) public userTickets;

    uint256 public constant REFUND_PERCENTAGE = 80;
    uint256 public constant REFUND_TIME = 1200;

    event TicketPurchased(address indexed buyer, uint256 indexed eventId, uint256 ticketId);
    event TicketRefunded(address indexed buyer, uint256 indexed eventId, uint256 ticketId);

    constructor(address _ticketContract) {
        ticketContract = Ticket(_ticketContract);
    }

    function createEvent(uint256 eventId, string memory eventName, uint256 maxSupply, uint256 startTime, uint256 price) external {
        require(events[eventId].startTime == 0, "Event already exists");

        events[eventId] = Event(eventName, maxSupply, startTime, price);
    }

    function purchaseTicket(uint256 eventId) external payable {
        Event memory eventInfo = events[eventId];
        require(eventInfo.startTime > 0, "Event does not exist");
        require(block.timestamp < eventInfo.startTime, "Event has already started");
        require(msg.value == eventInfo.price, "Incorrect payment amount");

        uint256 ticketId = ticketContract.ticketIdCounter().current();
        ticketContract.ticketIdCounter().increment();

        ticketContract.tickets(ticketId) = Ticket.TicketData(1, 0);

        userTickets[msg.sender][eventId] = ticketId;

        ticketContract.mint(1);

        emit TicketPurchased(msg.sender, eventId, ticketId);
    }

    function refundTicket(uint256 eventId) external {
        Event memory eventInfo = events[eventId];
        require(eventInfo.startTime > 0, "Event does not exist");
        require(block.timestamp < eventInfo.startTime - REFUND_TIME, "Refund time has passed");

        uint256 ticketId = userTickets[msg.sender][eventId];
        require(ticketId > 0, "User does not have a ticket for this event");

        uint256 refundAmount = eventInfo.price * REFUND_PERCENTAGE / 100;
        uint256 feeAmount = eventInfo.price - refundAmount;

        delete userTickets[msg.sender][eventId];

        payable(msg.sender).transfer(refundAmount);
        payable(owner()).transfer(feeAmount);

        ticketContract.safeTransferFrom(address(this), address(0), ticketId, 1, "");

        emit TicketRefunded(msg.sender, eventId, ticketId);
    }
}

```

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

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

contract Ticket is ERC1155, Ownable {
    using Counters for Counters.Counter;
    using Strings for uint256;

    struct TicketData {
        uint256 supply;
        uint256 minted;
    }

    mapping(uint256 => TicketData) public tickets;
    Counters.Counter private ticketIdCounter;

    uint256 public constant MAX_SUPPLY = 500;
    uint256 public constant MAX_PER_MINT = 1;
    uint256 public price = 0.01 ether;

    constructor() ERC1155("") {}
   //
    function buyTicket(uint256 amount) external payable {
        require(msg.value == price * amount, "Invalid payment amount");
        require(amount <= MAX_PER_MINT, "Exceeds maximum tickets per mint");
        require(ticketIdCounter.current() + amount <= MAX_SUPPLY, "Exceeds maximum ticket supply");

        for (uint256 i = 0; i < amount; i++) {
            uint256 ticketId = ticketIdCounter.current();
            ticketIdCounter.increment();

            tickets[ticketId] = TicketData(1, 0);

            _mint(msg.sender, ticketId, 1, "");
        }
    }
    //
    function mint(uint256 amount) external onlyOwner {
        require(amount <= MAX_SUPPLY - ticketIdCounter.current(), "Exceeds maximum ticket supply");

        for (uint256 i = 0; i < amount; i++) {
            uint256 ticketId = ticketIdCounter.current();
            ticketIdCounter.increment();

            tickets[ticketId] = TicketData(1, 0);

            _mint(msg.sender, ticketId, 1, "");
        }
    }
}
```

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

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract TicketContract is ERC1155, Ownable {
    using Counters for Counters.Counter;

    struct Ticket {
        uint256 id;
        address owner;
        uint256 eventId;
        uint256 quantity;
        uint256 price;
        bool isValid;
        uint256 startTime;
        bool refunded;
        bool isUsed;
    }

    mapping(uint256 => Ticket) public tickets;
    Counters.Counter private ticketIdCounter;
    uint256 public constant MAX_SUPPLY = 500;
    uint256 public constant MAX_PER_MINT = 1;
    uint256 public price = 0.01 ether;
    uint256 public constant REFUND_PERCENT = 80;

    event TicketMinted(uint256 indexed ticketId, address indexed owner);
    event TicketBurned(uint256 indexed ticketId);

    constructor() ERC1155("") {}

    function mintTicket(uint256 eventId, uint256 quantity) external payable {
        require(quantity <= MAX_PER_MINT, "Exceeds maximum per mint");
        require(ticketIdCounter.current() + quantity <= MAX_SUPPLY, "Exceeds maximum supply");
        require(msg.value == price * quantity, "Insufficient payment");

        for (uint256 i = 0; i < quantity; i++) {
            uint256 ticketId = ticketIdCounter.current();

            tickets[ticketId] = Ticket({
                id: ticketId,
                owner: msg.sender,
                eventId: eventId,
                quantity: 1,
                price: price,
                isValid: true,
                startTime: block.timestamp,
                refunded: false,
                isUsed: false
            });

            _mint(msg.sender, ticketId, 1, "");
            ticketIdCounter.increment();

            emit TicketMinted(ticketId, msg.sender);
        }
    }
    function burnTicket(uint256 ticketId) external {
        require(tickets[ticketId].owner == msg.sender, "Not the ticket owner");
        require(tickets[ticketId].isValid, "Invalid ticket");
        require(!tickets[ticketId].isUsed, "Ticket already used");

        _burn(msg.sender, ticketId, 1);

        tickets[ticketId].isValid = false;
        tickets[ticketId].isUsed = true;

        emit TicketBurned(ticketId);
    }


    function burnTicketsAfterEvent(uint256 eventId) external onlyOwner {
        require(block.timestamp > tickets[eventId].startTime, "Event has not ended yet");

        for (uint256 i = 0; i < ticketIdCounter.current(); i++) {
            if (tickets[i].eventId == eventId && tickets[i].isValid) {
                _burn(tickets[i].owner, i, tickets[i].quantity);
                tickets[i].isValid = false;

                emit TicketBurned(i);
            }
        }
    }

    function refund(uint256 ticketId) external {
        require(tickets[ticketId].owner == msg.sender, "Not the ticket owner");
        require(tickets[ticketId].isValid, "Invalid ticket");
        require(block.timestamp < tickets[ticketId].startTime, "Refund period has ended");

        if (!tickets[ticketId].refunded) {
            uint256 refundAmount = (tickets[ticketId].price * REFUND_PERCENT) / 100;
            uint256 feeAmount = tickets[ticketId].price - refundAmount;

            tickets[ticketId].refunded = true;
            payable(tickets[ticketId].owner).transfer(refundAmount);
            payable(owner()).transfer(feeAmount);
            }
        }

    function getEvent(uint256 eventId) external view returns (
        uint256 id,
        uint256 totalTickets,
        uint256 ticketPrice,
        uint256 startTime
    ) {
        Ticket storage ticket = tickets[eventId];
        require(ticket.owner != address(0), "Event does not exist");

        return (
            ticket.eventId,
            ticketIdCounter.current(),
            ticket.price,
            ticket.startTime
        );
    }

    function getTotalTickets() external view returns (uint256) {
        return ticketIdCounter.current();
    }

    function supportsInterface(bytes4 interfaceId) public view override(ERC1155) returns (bool) {
        return super.supportsInterface(interfaceId);
    }

    function withdraw() external onlyOwner {
        uint256 balance = address(this).balance;
        payable(owner()).transfer(balance);
    }
}
```
5/23
```
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract TicketContract is ERC1155, Ownable {
    using Counters for Counters.Counter;

    struct Ticket {
        uint256 id; // 票ID
        address owner; // 擁有者地址
        uint256 eventId; // 活動ID
        uint256 quantity; // 數量（每個NFT代表一張票）
        uint256 price; // 價格
        uint256 startTime; // 活動開始時間
        bool refunded; // 是否已退款
        bool isUsed; // 是否已使用
    }
    mapping(address => mapping(address => bool)) private _operatorApprovals;//為了使 NFT 可以進行轉移而用於追蹤已授權的操作者。
    mapping(uint256 => Ticket) public tickets; // 票ID映射到票的詳細信息
    Counters.Counter private ticketIdCounter; // 票ID計數器
    uint256 public constant MAX_SUPPLY = 500; // 最大供應量
    uint256 public constant MAX_PER_MINT = 1; // 每次購買的最大數量
    uint256 public price = 0.01 ether; // 票價
    uint256 public constant REFUND_PERCENT = 80; // 退款百分比

    event TicketMinted(uint256 indexed ticketId, address indexed owner); // 票被鑄造時觸發的事件
    event TicketBurned(uint256 indexed ticketId); // 票被銷毀時觸發的事件
    event TicketTransferred(uint256 indexed ticketId, address indexed from, address indexed to); // 票被轉移時觸發的事件

    constructor() ERC1155("") {}
    //鑄造票
    function mintTicket(uint256 eventId, uint256 quantity) external payable {
        require(quantity <= MAX_PER_MINT, "Exceeds maximum per mint");
        require(ticketIdCounter.current() + quantity <= MAX_SUPPLY, "Exceeds maximum supply");
        require(msg.value == price * quantity, "Insufficient payment");

        for (uint256 i = 0; i < quantity; i++) {
            uint256 ticketId = ticketIdCounter.current();

            tickets[ticketId] = Ticket({
                id: ticketId,
                owner: msg.sender,
                eventId: eventId,
                quantity: 1,
                price: price,
                startTime: block.timestamp,
                refunded: false,
                isUsed: false
            });

            _mint(msg.sender, ticketId, 1, "");
            ticketIdCounter.increment();

            emit TicketMinted(ticketId, msg.sender);
        }
    }
    //銷毀票
    function burnTicket(uint256 ticketId) external {
        
        require(!tickets[ticketId].isUsed, "Ticket already used");

        _burn(msg.sender, ticketId, 1);
        tickets[ticketId].isUsed = true;

        emit TicketBurned(ticketId);
    }
    //轉移票
    function transferTicket(uint256 ticketId, address to) external {
        require(tickets[ticketId].owner == msg.sender, "Not the ticket owner");
        require(!tickets[ticketId].isUsed, "Ticket already used");

        tickets[ticketId].owner = to;
        _safeTransferFrom(msg.sender, to, ticketId, 1, "");

        emit TicketTransferred(ticketId, msg.sender, to);
    }
    
    //活動結束後銷毀所有票
    function burnTicketsAfterEvent(uint256 eventId) external onlyOwner {
        require(block.timestamp > tickets[eventId].startTime, "Event has not ended yet");

        for (uint256 i = 0; i < ticketIdCounter.current(); i++) {
            if (tickets[i].eventId == eventId) {
                _burn(tickets[i].owner, i, tickets[i].quantity);

                emit TicketBurned(i);
            }
        }
    }
    
    //退款
    function refund(uint256 ticketId) external {
        require(tickets[ticketId].owner == msg.sender, "Not the ticket owner");
        require(block.timestamp < tickets[ticketId].startTime, "Refund period has ended");

        if (!tickets[ticketId].refunded) {
            uint256 refundAmount = (tickets[ticketId].price * REFUND_PERCENT) / 100;
            uint256 feeAmount = tickets[ticketId].price - refundAmount;

            tickets[ticketId].refunded = true;
            payable(tickets[ticketId].owner).transfer(refundAmount);
            payable(owner()).transfer(feeAmount);

            _burn(msg.sender, ticketId, 1);

            emit TicketBurned(ticketId);
        }
    }

    //設置票務系統中的NFT的URI
    function setURI(string memory newUri) public onlyOwner {
        _setURI(newUri);
    }

    //查詢總票數，展示網頁中的現在票務銷售情況
    function getTotalTickets() external view returns (uint256) {
        return ticketIdCounter.current();
    }
    
    //檢查合約是否支持ERC1155介面
    function supportsInterface(bytes4 interfaceId) public view override(ERC1155) returns (bool) {
        return super.supportsInterface(interfaceId);
    }

    //提取合約中的資金
    function withdraw() external onlyOwner {
        uint256 balance = address(this).balance;
        payable(owner()).transfer(balance);
    }

    //獲取活動詳細信息
    function getEvent(uint256 eventId) external view returns (
        uint256 id,
        uint256 totalTickets,
        uint256 ticketPrice,
        uint256 startTime
    ) {
        Ticket storage ticket = tickets[eventId];
        require(ticket.owner != address(0), "Event does not exist");

        return (
            ticket.eventId,
            ticketIdCounter.current(),
            ticket.price,
            ticket.startTime
        );
    }
}


```

測試檔
```
const { expect } = require("chai");

describe("TicketContract", function () {
  let ticketContract;
  let owner;
  let addr1;
  let addr2;

  beforeEach(async function () {
    [owner, addr1, addr2] = await ethers.getSigners();

    const TicketContract = await ethers.getContractFactory("TicketContract");
    ticketContract = await TicketContract.deploy();
    await ticketContract.deployed();
  });

  it("should mint tickets", async function () {
    const eventId = 1;
    const quantity = 1;

    const mintTicketTx = await ticketContract.mintTicket(eventId, quantity, {
      value: ethers.utils.parseEther("0.01"),
    });

    await mintTicketTx.wait();

    const totalTickets = await ticketContract.getTotalTickets();
    expect(totalTickets).to.equal(quantity);
  });

  it("should burn tickets", async function () {
    const eventId = 1;
    const quantity = 1;

    await ticketContract.mintTicket(eventId, quantity, {
      value: ethers.utils.parseEther("0.01"),
    });

    const ticketId = 0;
    const burnTicketTx = await ticketContract.burnTicket(ticketId);
    await burnTicketTx.wait();

    const totalTickets = await ticketContract.getTotalTickets();
    expect(totalTickets).to.equal(0);
  });

  it("should transfer tickets", async function () {
    const eventId = 1;
    const quantity = 1;

    await ticketContract.mintTicket(eventId, quantity, {
      value: ethers.utils.parseEther("0.01"),
    });

    const ticketId = 0;
    const transferTicketTx = await ticketContract.transferTicket(ticketId, addr1.address);
    await transferTicketTx.wait();

    const ownerOfTicket = await ticketContract.ownerOf(ticketId);
    expect(ownerOfTicket).to.equal(addr1.address);
  });

  it("should refund tickets", async function () {
    const eventId = 1;
    const quantity = 1;

    await ticketContract.mintTicket(eventId, quantity, {
      value: ethers.utils.parseEther("0.01"),
    });

    const ticketId = 0;
    const refundTicketTx = await ticketContract.refund(ticketId);
    await refundTicketTx.wait();

    const totalTickets = await ticketContract.getTotalTickets();
    expect(totalTickets).to.equal(0);
  });
});

```

---
## 使用Ethers.js或Web3.js與智能合約進行互動

### Ethers.js
```
const { ethers } = require('ethers');

// 定義智能合約 ABI
const ticketContractAbi = [
  // 定義智能合約方法和事件
  // ...
];

// 定義智能合約地址
const ticketContractAddress = '0x123456789abcdef...';

// 設定以太坊節點提供者
const provider = new ethers.providers.JsonRpcProvider('https://ropsten.infura.io/v3/YOUR_INFURA_PROJECT_ID');

// 連接到智能合約
const ticketContract = new ethers.Contract(ticketContractAddress, ticketContractAbi, provider);

// 鑄造票
async function mintTicket(eventId, quantity) {
  const signer = provider.getSigner();
  const price = ethers.utils.parseEther('0.01').mul(quantity);

  // 獲取鑄造票的交易
  const transaction = await ticketContract.connect(signer).mintTicket(eventId, quantity, {
    value: price,
  });

  // 等待交易被確認
  await transaction.wait();

  console.log('Ticket minted successfully!');
}

// 銷毀票
async function burnTicket(ticketId) {
  const signer = provider.getSigner();

  // 獲取銷毀票的交易
  const transaction = await ticketContract.connect(signer).burnTicket(ticketId);

  // 等待交易被確認
  await transaction.wait();

  console.log('Ticket burned successfully!');
}

// 轉移票
async function transferTicket(ticketId, toAddress) {
  const signer = provider.getSigner();

  // 獲取轉移票的交易
  const transaction = await ticketContract.connect(signer).transferTicket(ticketId, toAddress);

  // 等待交易被確認
  await transaction.wait();

  console.log('Ticket transferred successfully!');
}

// 退款
async function refundTicket(ticketId) {
  const signer = provider.getSigner();

  // 獲取退款的交易
  const transaction = await ticketContract.connect(signer).refund(ticketId);

  // 等待交易被確認
  await transaction.wait();

  console.log('Ticket refunded successfully!');
}

// 使用範例
async function main() {
  // 鑄造票
  await mintTicket(1, 1);

  // 銷毀票
  await burnTicket(1);

  // 轉移票
  await transferTicket(1, '0x123456789abcdef...');

  // 退款
  await refundTicket(1);
}

main().catch((error) => {
  console.error(error);
});

```
### Web3.js

```
const Web3 = require('web3');

// 定義智能合約 ABI
const ticketContractAbi = [
  // 定義智能合約方法和事件
  // ...
];

// 定義智能合約地址
const ticketContractAddress = '0x123456789abcdef...';

// 設定以太坊節點提供者
const providerUrl = 'https://ropsten
const Web3 = require('web3');

// 定義智能合約 ABI
const ticketContractAbi = [
  // 定義智能合約方法和事件
  // ...
];

// 定義智能合約地址
const ticketContractAddress = '0x123456789abcdef...';

// 設定以太坊節點提供者
const providerUrl = 'https://ropsten

```

### 建立合約的 API
```
npm install express web3
```
#### app.js
```
const express = require('express');
const Web3 = require('web3');

const app = express();
const web3 = new Web3(new Web3.providers.HttpProvider('https://ropsten.infura.io/v3/YOUR_INFURA_PROJECT_ID'));

// 合約 ABI 和地址
const ticketContractAbi = [
  // 在這裡插入合約的 ABI
];
const ticketContractAddress = '0x...'; // 在這裡插入合約的地址

// 創建智能合約實例
const ticketContract = new web3.eth.Contract(ticketContractAbi, ticketContractAddress);

// 鑄造票的 API 端點
app.post('/api/mintTicket', async (req, res) => {
  try {
    const { eventId, quantity } = req.body;

    // 執行鑄造票的交易
    const accounts = await web3.eth.getAccounts();
    const price = web3.utils.toWei('0.01', 'ether') * quantity;
    const transaction = await ticketContract.methods.mintTicket(eventId, quantity).send({
      from: accounts[0],
      value: price,
    });

    res.status(200).json({ message: 'Ticket minted successfully' });
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: 'Failed to mint ticket' });
  }
});

// 其他 API 端點...

// 啟動服務器
app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

```
### ABI

```
[
	{
		"inputs": [],
		"stateMutability": "nonpayable",
		"type": "constructor"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "account",
				"type": "address"
			},
			{
				"indexed": true,
				"internalType": "address",
				"name": "operator",
				"type": "address"
			},
			{
				"indexed": false,
				"internalType": "bool",
				"name": "approved",
				"type": "bool"
			}
		],
		"name": "ApprovalForAll",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "previousOwner",
				"type": "address"
			},
			{
				"indexed": true,
				"internalType": "address",
				"name": "newOwner",
				"type": "address"
			}
		],
		"name": "OwnershipTransferred",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "uint256",
				"name": "ticketId",
				"type": "uint256"
			}
		],
		"name": "TicketBurned",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "uint256",
				"name": "ticketId",
				"type": "uint256"
			},
			{
				"indexed": true,
				"internalType": "address",
				"name": "owner",
				"type": "address"
			}
		],
		"name": "TicketMinted",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "uint256",
				"name": "ticketId",
				"type": "uint256"
			},
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": true,
				"internalType": "address",
				"name": "to",
				"type": "address"
			}
		],
		"name": "TicketTransferred",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "operator",
				"type": "address"
			},
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": true,
				"internalType": "address",
				"name": "to",
				"type": "address"
			},
			{
				"indexed": false,
				"internalType": "uint256[]",
				"name": "ids",
				"type": "uint256[]"
			},
			{
				"indexed": false,
				"internalType": "uint256[]",
				"name": "values",
				"type": "uint256[]"
			}
		],
		"name": "TransferBatch",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "operator",
				"type": "address"
			},
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": true,
				"internalType": "address",
				"name": "to",
				"type": "address"
			},
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "id",
				"type": "uint256"
			},
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "value",
				"type": "uint256"
			}
		],
		"name": "TransferSingle",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": false,
				"internalType": "string",
				"name": "value",
				"type": "string"
			},
			{
				"indexed": true,
				"internalType": "uint256",
				"name": "id",
				"type": "uint256"
			}
		],
		"name": "URI",
		"type": "event"
	},
	{
		"inputs": [],
		"name": "MAX_PER_MINT",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "MAX_SUPPLY",
		"outputs": [
			{https://hackmd.io/-nSNZxPmSWmIiJxsVIEwtw#
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "REFUND_PERCENT",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "account",
				"type": "address"
			},
			{
				"internalType": "uint256",
				"name": "id",
				"type": "uint256"
			}
		],
		"name": "balanceOf",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address[]",
				"name": "accounts",
				"type": "address[]"
			},
			{
				"internalType": "uint256[]",
				"name": "ids",
				"type": "uint256[]"
			}
		],
		"name": "balanceOfBatch",
		"outputs": [
			{
				"internalType": "uint256[]",
				"name": "",
				"type": "uint256[]"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "ticketId",
				"type": "uint256"
			}
		],
		"name": "burnTicket",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "eventId",
				"type": "uint256"
			}
		],
		"name": "burnTicketsAfterEvent",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "eventId",
				"type": "uint256"
			}
		],
		"name": "getEvent",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "id",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "totalTickets",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "ticketPrice",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "startTime",
				"type": "uint256"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "getTotalTickets",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "account",
				"type": "address"
			},
			{
				"internalType": "address",
				"name": "operator",
				"type": "address"
			}
		],
		"name": "isApprovedForAll",
		"outputs": [
			{
				"internalType": "bool",
				"name": "",
				"type": "bool"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "eventId",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "quantity",
				"type": "uint256"
			}
		],
		"name": "mintTicket",
		"outputs": [],
		"stateMutability": "payable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "owner",
		"outputs": [
			{
				"internalType": "address",
				"name": "",
				"type": "address"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "price",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "ticketId",
				"type": "uint256"
			}
		],
		"name": "refund",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "renounceOwnership",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"internalType": "address",
				"name": "to",
				"type": "address"
			},
			{
				"internalType": "uint256[]",
				"name": "ids",
				"type": "uint256[]"
			},
			{
				"internalType": "uint256[]",
				"name": "amounts",
				"type": "uint256[]"
			},
			{
				"internalType": "bytes",
				"name": "data",
				"type": "bytes"
			}
		],
		"name": "safeBatchTransferFrom",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"internalType": "address",
				"name": "to",
				"type": "address"
			},
			{
				"internalType": "uint256",
				"name": "id",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "amount",
				"type": "uint256"
			},
			{
				"internalType": "bytes",
				"name": "data",
				"type": "bytes"
			}
		],
		"name": "safeTransferFrom",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "operator",
				"type": "address"
			},
			{
				"internalType": "bool",
				"name": "approved",
				"type": "bool"
			}
		],
		"name": "setApprovalForAll",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "string",
				"name": "newUri",
				"type": "string"
			}
		],
		"name": "setURI",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "bytes4",
				"name": "interfaceId",
				"type": "bytes4"
			}
		],
		"name": "supportsInterface",
		"outputs": [
			{
				"internalType": "bool",
				"name": "",
				"type": "bool"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"name": "tickets",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "id",
				"type": "uint256"
			},
			{
				"internalType": "address",
				"name": "owner",
				"type": "address"
			},
			{
				"internalType": "uint256",
				"name": "eventId",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "quantity",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "price",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "startTime",
				"type": "uint256"
			},
			{
				"internalType": "bool",
				"name": "refunded",
				"type": "bool"
			},
			{
				"internalType": "bool",
				"name": "isUsed",
				"type": "bool"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "newOwner",
				"type": "address"
			}
		],
		"name": "transferOwnership",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "ticketId",
				"type": "uint256"
			},
			{
				"internalType": "address",
				"name": "to",
				"type": "address"
			}
		],
		"name": "transferTicket",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"name": "uri",
		"outputs": [
			{
				"internalType": "string",
				"name": "",
				"type": "string"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "withdraw",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	}
]

```

5/26 修改 ticket.sol
```

```
Event ID:可以區分不同的活動。同一合約中舉辦多個活動，可以使用不同的 Event ID 來區分每個活動的票。burnTicketsAfterEvent使用 Event ID 來銷毀特定活動的所有票。查詢活動詳細信息，通過 Event ID，查詢特定活動的詳細信息，例如該活動的票總數、票價和開始時間。
