# 4. Soạn hợp đồng thông minh Klaystagram

1. Background
2. Contract setup
3. Set events and data structure
4. Write functions\
   4.1. `uploadPhoto`\
   4.2. `transferOwnership`\
   4.3. `getPhoto`

## 1) Hình nền <a href="#id-1-background" id="id-1-background"></a>

We will make a simple contract called "Klaystagram".

* `PhotoData` struct is defined to store various photo data.
* User can upload photo and transfer the ownership photo via `uploadPhoto` and `transferOwnership` functions.

## 2) Thiết lập hợp đồng <a href="#id-2-contract-setup" id="id-2-contract-setup"></a>

* Specify solidity version. We recommend using 0.5.6 stable version.
* We will make use of ERC721 standard to build non-fungible tokens.
  * Import `ERC721.sol` and `ERC721Enumerable.sol`
  * Check out detailed information about ERC721 at [erc721.org](http://erc721.org)

```
pragma solidity 0.5.6;

nhập "./ERC721/ERC721.sol";
nhập "./ERC721/ERC721Enumerable.sol";

hợp đồng Klaystagram là ERC721, ERC721Enumerable {
```

## 3) Đặt sự kiện và cấu trúc dữ liệu <a href="#id-3-set-events-and-data-structure" id="id-3-set-events-and-data-structure"></a>

We need to set up an event to keep track of activities on blockchain.

As for data structure, mapping `_photoList` takes a uint256 `tokenId` to map a specific `PhotoData` struct. By defining PhotoUploaded event, transaction receipt will log this event whenever function containing this is called.

```
event PhotoUploaded (uint indexed tokenId, bytes photo, string title, string location, string description, uint256 timestamp);

ánh xạ (uint256 => PhotoData) private _photoList;

struct PhotoData {
    uint256 tokenId;                       // id token không trùng lặp, bắt đầu từ 1 và tăng thêm 1
    address[] ownerHistory;                // Lịch sử tất cả những chủ sở hữu trước đây
    bytes photo;                           // Nguồn ảnh
    string title;                          // Tiêu đề ảnh
    string location;                       // Nơi chụp ảnh
    string description;                    // Mô tả ngắn về ảnh
    uint256 timestamp;                     // Thời gian tải lên
}
```

## 4) Viết hàm <a href="#id-4-write-functions" id="id-4-write-functions"></a>

Let's write some functions that interact with the contract. In this tutorial let us only consider two functions: `uploadPhoto` and `transferOwnership`. Check out Klaystagram.sol to see the whole set of functions.

### 4-1) `uploadPhoto` <a href="#id-4-1-uploadphoto" id="id-4-1-uploadphoto"></a>

`uploadPhoto` function takes 4 arguments including photo's image source. To keep things simple, `tokenId` will start from 1 and will increase by 1.

`_mint` function is from ERC721 contract. It creates a new token and assign it to a specific address, which in this case, `msg.sender`. In this application, logged in user will create transaction with their own private key. So `msg.sender` will be the user's public address.

Finally, initialize `PhotoData` struct, locate it inside `_photoList` mapping, and push the owner address into `ownerHistory` array. And don't forget to emit the event we just created. As mentioned above, this event will be included in transaction receipt.

```
function uploadPhoto(bytes memory photo, string memory title, string memory location, string memory description) public {
    uint256 tokenId = totalSupply() + 1;

    _mint(msg.sender, tokenId);

    address[] memory ownerHistory;

    PhotoData memory newPhotoData = PhotoData({
        tokenId : tokenId,
        ownerHistory : ownerHistory,
        photo : photo,
        title: title,
        location : location,
        description : description,
        timestamp : now
    });

    _photoList[tokenId] = newPhotoData;
    _photoList[tokenId].ownerHistory.push(msg.sender);

    emit PhotoUploaded(tokenId, photo, title, location, description, now);
}
```

### 4-2) `transferOwnership` <a href="#id-4-2-transferownership" id="id-4-2-transferownership"></a>

Let's take a look at `transferOwnership` function. When transferring photo ownership, we need to do two things. First, we have to reassign the owner, and then we have to push new owner address into `ownerHistory` array.

To do this, `transferOwnership` first calls `safeTransferFrom` function from ERC721 standard, which eventually calls `transferFrom` function. As mentioned above, right after token transfer is successfully done, we have to push new owner information into `ownerHistory` array, and that is exactly why `transferFrom` is overridden as below.

```
/**
  * @ghi chú hàm safeTransferFrom kiểm tra xem người nhận có thể xử lý token ERC721 không,
  * nhờ đó, ít có khả năng bị mất token. Sau khi kiểm tra xong, hàm transferFrom với định nghĩa bên dưới sẽ được gọi
  */
function transferOwnership(uint256 tokenId, address to) public returns(uint, address, address, address) {
    safeTransferFrom(msg.sender, to, tokenId);
    uint ownerHistoryLength = _photoList[tokenId].ownerHistory.length;
    return (
        _photoList[tokenId].tokenId,
        //chủ sở hữu ban đầu        _photoList[tokenId].ownerHistory[0],
        //người sở hữu trước đây, độ dài không thể nhỏ hơn 2
        _photoList[tokenId].ownerHistory[ownerHistoryLength-2],
        //chủ sở hữu hiện tại
        _photoList[tokenId].ownerHistory[ownerHistoryLength-1]);
}

/**
  * @notice Khuyên dùng transferOwnership có sử dụng hàm safeTransferFrom
  * @dev Viết đề lên hàm transferFrom để đảm bảo rằng mỗi lần chuyển quyền sở hữu
  *  địa chỉ của chủ sở hữu mới được đẩy vào mảng ownerHistory
  */
function transferFrom(address from, address to, uint256 tokenId) public {
    super.transferFrom(from, to, tokenId);
    _photoList[tokenId].ownerHistory.push(to);
}
```

### 4-3) `getPhoto` <a href="#id-4-3-getphoto" id="id-4-3-getphoto"></a>

Finally, let's make a getter function that fetches data stored in the smart contract. By calling a single function, we want to fetch every information regarding a specific photo. So `getPhoto` function takes an index(token id) as an argument and returns every element in PhotoData struct.

```
function getPhoto(uint tokenId) public view
returns(uint256, address[] memory, bytes memory, string memory, string memory, string memory, uint256) {
    require(_photoList[tokenId].tokenId != 0, "Ảnh không tồn tại");
    return (
        _photoList[tokenId].tokenId,
        _photoList[tokenId].ownerHistory,
        _photoList[tokenId].photo,
        _photoList[tokenId].title,
        _photoList[tokenId].location,
        _photoList[tokenId].description,
        _photoList[tokenId].timestamp);
}
```

This is it, now we can deploy this contract!


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://archive-vn.docs.klaytn.foundation/content/dapp/tutorials/klaystagram/4.-write-klaystagram-smart-contract.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
