iT邦幫忙

2022 iThome 鐵人賽

DAY 10
2
Web 3

從以太坊白皮書理解 web 3 概念系列 第 11

從以太坊白皮書理解 web 3 概念 - Day10

  • 分享至 

  • xImage
  •  

從以太坊白皮書理解 web 3 概念 - Day10

Learn Solidity - day 2 ZombieFeeding

昨天已經建立了產生 Zombie 物件的 Contract ZombieFactory

可以透過 createRandomZombie 來根據名字隨機產生 Zombie 物件。

今天要新增使用者與 Zomibe 物件的互動性。

新增一個 feed 的功能,新增一種產生 Zombie 物件的 function 。

可以點入以下連結一起來撰寫 Contract

Lession 2: Zombie Feeding

Mappings and Addresses

Addresses

在 Ethereum 上,主要紀錄的 transaction 都是關於 Account 的紀錄。

每個 Account 至少會有一個 address, Account 會透過 address 紀錄其所擁有的 Balance。

可以把 address 想像成對應該 account 的識別碼。

address 的形式是一個長度 42 個字元的字串如下:

0x0cE446255506E92DF41614C46F1d6df9Cc969183

在 Contract 裡,可以透過紀錄 address 當作 owner 的識別 id 。

Mappings

Mapping 在 solidity 語言是一種資料結構,類似於 Python 的 Dictionary 或是 java 的 HashMap。用來紀錄對應關係。

語法如下

// 建立紀錄 user account 與 balance 的 mapping
mapping (address => uint) public accountBalance;
// 建立紀錄 userId 與 username 的 mapping
mapping (uint => string) userIdToName;

新增紀錄 Zombie Object Owner 的資料結構

為了紀錄 zombie 的持有關係,將要建立兩個 mapping

一個用來紀錄 zombie 的持有者 address。

一個用來紀錄每個持有者具有多少 zombie。

  1. 建立一個名稱為 zombieToOwner 的 mapping 。
    使用 uint 當作 key 對應到持有者的 address。
    並且設定讀取權限為 public 。

  2. 建立一個名稱為 ownerZombieCount 的 mapping 。
    使用 address 當作 key 對應到 uint。
    用來紀錄每個 address 具有幾個 zombie 。

更新 ZombieFactory 如下

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    event NewZombie(uint zombieId, string name, uint dna);

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    // declare mappings here
    mapping (uint => address) public zombieToOwner;
    mapping (address => uint) ownerZombieCount;
    function _createZombie(string memory _name, uint _dna) private {
        uint id = zombies.push(Zombie(_name, _dna)) - 1;
        emit NewZombie(id, _name, _dna);
    } 

    function _generateRandomDna(string memory _str) private view returns (uint) {
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;
    }

    function createRandomZombie(string memory _name) public {
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randDna);
    }

}

Msg.sender

msg.sender 在 solidity 是一個特殊的 preserved word

用來存取呼叫 Contract function 的 address 。

舉例來說:

mapping (address => uint) favoriteNumber;

function setMyNumber(uint _myNumber) public {
    // 更新 favoriteNumber 這個 msg.sender key 的值為 _myNumber 
    favoriteNumber[msg.sender] = _myNumber;
}

function whatIsMyNumber() public view returns (uint) {
    // 回傳當下 msg.sender 的 number
    return favoriteNumber[msg.sender];
}

更新 _createZombie 方法

在 _createZombie 方法裡,加入儲存 zombie 所有權的邏輯

  1. 取出 Zombie 的 id ,然後更新 zombieToOwner[id] = msg.sender

  2. 更新 ownerZombieCount[msg.sender]++

在 solidity ,可以在 uint 變數使用 ++ 運算元如下:

uint number = 0;
number++; // number = 1

更新 ZombieFactory 如下

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    event NewZombie(uint zombieId, string name, uint dna);

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    mapping (uint => address) public zombieToOwner;
    mapping (address => uint) ownerZombieCount;

    function _createZombie(string memory _name, uint _dna) private {
        uint id = zombies.push(Zombie(_name, _dna)) - 1;
        // start here
        zombieToOwner[id] = msg.sender;
        ownerZombieCount[msg.sender]++;
        emit NewZombie(id, _name, _dna);
    }

    function _generateRandomDna(string memory _str) private view returns (uint) {
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;
    }

    function createRandomZombie(string memory _name) public {
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randDna);
    }

}

Require 語法

在 solidity , 如果需要限制某些條件才能執行功能,就可以使用 require 這個語法。只有在 require 設定條件是 true,才會繼續往下執行,否則會拋出錯誤並且停止執行。

語法如下:

function sayHiToSharon(string memory _name) public returns (string memory) {
  // 檢核名稱要是 Sharon 才執行
  require(keccak256(abi.encodePacked(_name)) == keccak256(abi.encodePacked("Sharon")));
  // If it's true, proceed with the function:
  return "Hi!";
}

更新 createRandomZombie

限制每個 address 只能擁有一個 zombie 物件。

  1. 新增 require (ownerZombieCount[msg.sender] == 0) 在原本的執行程式之前

更新 ZombieFactory 如下

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    event NewZombie(uint zombieId, string name, uint dna);

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    mapping (uint => address) public zombieToOwner;
    mapping (address => uint) ownerZombieCount;

    function _createZombie(string memory _name, uint _dna) private {
        uint id = zombies.push(Zombie(_name, _dna)) - 1;
        zombieToOwner[id] = msg.sender;
        ownerZombieCount[msg.sender]++;
        emit NewZombie(id, _name, _dna);
    }

    function _generateRandomDna(string memory _str) private view returns (uint) {
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;
    }

    function createRandomZombie(string memory _name) public {
        // start here
        require(ownerZombieCount[msg.sender] == 0);
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randDna);
    }
}

Inheritance

隨著功能愈來愈多,原本的 Contract 內容會不斷增加。

這樣在閱讀或是維護起來並不方便。

有時候會透過把邏輯功能做切分來更有效的組織與管理 Contract。

Contract 的 inheritance 能夠達成這個目的。

範例如下:

contract Doge {
    function catchphrase() public returns (string memory) {
        return "So Wow CryptoDoge";
    }
}

contract BabyDoge is Doge {
    function anotherCatchphrase() public returns (string memory) {
        return "Such Moon BabyDoge";
    }
}

BabyDoge 透過 is 語法繼承了 Doge

因此 BadyDoge 可以呼叫 catchphrase 與 anotherCatchphrase 這兩個 method 。

建立 ZombieFeeding Contract

為了比較好管理邏輯,

所以把 Feed zombie 邏輯放到另一個叫作 ZombieFeeding 的 Contract

並且使用 is 做繼承,讓 ZombieFeeding 可以使用 ZombieFactory 的 function 。

建立 ZombieFeeding 如下

contract ZombieFeeding is ZombieFactory {
    
}

import 語法

當把兩個 Contract 分開寫,而其中另一個 Contract 需要引用另一個 Contract 時,就需要使用 import 語法如下:

import "./animal.sol";

contract Dog is Animal {
    
}

更新 ZombieFeeding 如下

pragma solidity >=0.5.0 <0.6.0;

// put import statement here
import "./zombiefactory.sol";
contract ZombieFeeding is ZombieFactory {

}

Storage vs Memory

在 solidity, 有兩個地方可以儲存變數

  1. Storage
  2. Memory

把變數放在 Storage ,代表這個變數會永遠存在 blockchain 上。把變數放在 Memory ,代表這個變數只是暫時的。在外部函數呼叫時, 使用過的 Memory 變數將被清除。

Storage 變數之於 Memory 變數,就類似於硬碟之於記憶體。

一般來說,撰寫者不需要特別去申明這兩種變數,solidity 會自動處理這種變換。狀態變數(放在 function 之外的變數)會預設被設定成 storage 變數,而在 function 內的變數通常是 memory 變數,一旦呼叫結束,就會消失。

然而當遇到 structs 跟 array 時,就必須要做宣告如下

contract SandwichFactory {
    struct Sandwich {
        string name;
        string status;
    }
    Sandwich[] sandwiches;
    
    function  eatSandwich(uint _index) public {
        // 在此因為是直接使用到 struct 所以必須要特別申明
        Sandwich storage mySandwich = sandwiches[_index];
        // 這個更改會被紀錄在 blockchain 上
        mySandwich.status = "Eaten!";
        // 如果不想更改 只是要做 copy 就必須宣告 memory
        Sandwich memory anotherSandwich = sandwiches[_index + 1];
        // 這個只會改變 暫時變數 並不會寫到鏈上
        anotherSandwich.status = "Eaten!";
        // 然而可以透過以下語法 更新資料到鏈上
        sandwiches[_index + 1] = anotherSandwich;
        
    }
}

新增 zombie feed 功能

在 ZombieFeeding Contract 新增 feed 還有 multply 功能

當一個 zombie 被 feed 一位活人,該活人會變感染成新的 zombie 並且 DNA 會變成原本 DNA 加 zombie 的 DNA

實作細節如下

  1. 建立 feedAndMultiply function。
    feedAndMultiply 需要兩個參數: _zombieId(uint) 與 _targetDna(uint)。 feedAndMultiply的讀取權限是 public 。

  2. 限定只有 zombie 擁有者才可以呼叫 feedAndMultiply,新增 require 條件檢查 msg.sender 需要是 zombie 的 owner。

  3. 需要取得該 zombie 的 DNA。所以需要建立一個 Zombie 參數叫作 myZombie 必須要是 storage 變數。設定 myZombie = zombies[_zombieId] 。

更新 ZombieFeeding 如下

pragma solidity >=0.5.0 <0.6.0;

import "./zombiefactory.sol";

contract ZombieFeeding is ZombieFactory {

  // Start here
    function feedAndMultiply(uint _zombieId, uint _targetDna) public {
        require (msg.sender == zombieToOwner[_zombieId]);
        Zombie storage myZombie = zombies[_zombieId];
    }
}

新增 feed Zombie dna 邏輯

  1. 為了確保 _targetDna 不超過 16 字元。
    所以更新 _targetDna = _targetDna % dnaModulus;
  2. 宣告一個 uint 變數 newDna, 並且設定其值為 (myZombie.dna + _targetDna)/2;
  3. 使用 newDna 與 "NoName" 當作參數去呼教 _createZombie 。
pragma solidity >=0.5.0 <0.6.0;

import "./zombiefactory.sol";

contract ZombieFeeding is ZombieFactory {

  function feedAndMultiply(uint _zombieId, uint _targetDna) public {
    require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    // start here
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna)/2;
    _createZombie("NoName", newDna); 
  }

}

Function Visiblility

目前的 ZombieFeeding Contract

使用了 ZombieFactory 的 _createZombie 函數來建立 zombie 物件

然而, _createZombie 宣告是 private 。

也就是說,除了 ZombieFactory 沒有其他 Contract 可以呼叫 _createZombie 。

因此,需要使用其他 visibility 修飾子來修正 _createZombie 的讀取權限。

Internal 與 External

function 除了 public 與 private 讀取權限外。還有 internal 與 external 。

internal 類似於 private,只有 contract 本身以及繼承 contract 的其他 contract 可以存取,比 private 多了一個可以讓繼承者存取。

external 類似於 public,只能被其他 contract 呼叫,不同於 public 的地方是 contract 自己不能呼叫這個 function 。

範例如下:

contract Sandwich {
  uint private sandwichesEaten = 0;

  function eat() internal {
    sandwichesEaten++;
  }
}

contract BLT is Sandwich {
  uint private baconSandwichesEaten = 0;

  function eatWithBacon() public returns (string memory) {
    baconSandwichesEaten++;
    // We can call this here because it's internal
    eat();
  }
}

修正 _createZombie 為 internal

更新 ZombieFactory 如下

ragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    event NewZombie(uint zombieId, string name, uint dna);

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    mapping (uint => address) public zombieToOwner;
    mapping (address => uint) ownerZombieCount;

    // edit function definition below
    function _createZombie(string memory _name, uint _dna) internal {
        uint id = zombies.push(Zombie(_name, _dna)) - 1;
        zombieToOwner[id] = msg.sender;
        ownerZombieCount[msg.sender]++;
        emit NewZombie(id, _name, _dna);
    }

    function _generateRandomDna(string memory _str) private view returns (uint) {
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;
    }

    function createRandomZombie(string memory _name) public {
        require(ownerZombieCount[msg.sender] == 0);
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randDna);
    }

}

與其他 Contract 互動

為了要讓撰寫的 Contract 與其他不屬於我們的 Contract 互動

必須要先定好與其該 Contract 互動的 interface(介面)。

舉例: 假設有一個 LuckyNumber contract 如下

contract LuckyNumber {
    mapping(address => uint) numbers;
    
    function setNum(uint _num) public {
        numbers[msg.sender] = _num;
    }
    
    function getNum(address _myAddress) public view returns (uint) {
        return numbers[_myAddress];
    }
}

這個 contract 讓任何人可以紀錄自己的 lucky number 並且讓所有人查詢。

假設我們想要定義一個 function getNum 跟這個外部 contract 互動,

必須要撰寫以下 interface 來做互動。

contract NumberInterface {
    function getNum(address _myAddress) public view returns(uint);
}

雖然這個 interface 宣告起來很像 contract , 但是可以注意到這個 function 並沒有 function body 只有他的 function signature 。

透過這樣宣告, solidity 會自動判定這是一個 interface 。

加入與 CryptoKitty 互動的介面

首先,察看到 CryptoKitties Contract 內部有一個 getKitty 的 function

透過這個 function 來拿取 Kitty 的 genes 資料

這個 function 部份代碼如下

function getKitty(uint256 _id) external view returns (
    bool isGestating,
    bool isReady,
    uint256 cooldownIndex,
    uint256 nextActionAt,
    uint256 siringWithId,
    uint256 birthTime,
    uint256 matronId,
    uint256 sireId,
    uint256 generation,
    uint256 genes
) {
    Kitty storage kit = kitties[_id];

    // if this variable is 0 then it's not gestating
    isGestating = (kit.siringWithId != 0);
    isReady = (kit.cooldownEndBlock <= block.number);
    cooldownIndex = uint256(kit.cooldownIndex);
    nextActionAt = uint256(kit.cooldownEndBlock);
    siringWithId = uint256(kit.siringWithId);
    birthTime = uint256(kit.birthTime);
    matronId = uint256(kit.matronId);
    sireId = uint256(kit.sireId);
    generation = uint256(kit.generation);
    genes = kit.genes;
}

具體步驟:

  1. 定義一個叫作 KittyInterface 的介面。
  2. 在介面定一個接口 function getKitty ,使用上面的定義參數。

更新 ZombieFeeding 如下:

pragma solidity >=0.5.0 <0.6.0;

import "./zombiefactory.sol";

// Create KittyInterface here
contract KittyInterface {
    function getKitty(uint256 _id) external view returns (
        bool isGestating,
        bool isReady,
        uint256 cooldownIndex,
        uint256 nextActionAt,
        uint256 siringWithId,
        uint256 birthTime,
        uint256 matronId,
        uint256 sireId,
        uint256 generation,
        uint256 genes
    );
}
contract ZombieFeeding is ZombieFactory {

  function feedAndMultiply(uint _zombieId, uint _targetDna) public {
    require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna) / 2;
    _createZombie("NoName", newDna);
  }

}

使用 Interface

假設 NumberInterface 如下

contract NumberInterface {
  function getNum(address _myAddress) public view returns (uint);
}

以下是使用 interface 的範例

contract MyContract {
  address NumberInterfaceAddress = 0xab38... 
  // ^ The address of the FavoriteNumber contract on Ethereum
  NumberInterface numberContract = NumberInterface(NumberInterfaceAddress);
  // Now `numberContract` is pointing to the other contract

  function someFunction() public {
    // Now we can call `getNum` from that contract:
    uint num = numberContract.getNum(msg.sender);
    // ...and do something with `num` here
  }
}

透過把 Contract Address 帶入 Interface 就可以使用該 Contract 的 external 或是 public function 了。

新增與 CryptoKitty 互動邏輯

具體步驟:

  1. 使用變數 ckAddress 初始化 CryptoKitty 的 address
    然後使用 KittyInterface 建立出一個 kittyContract 變數

更新 ZombieFeeding 如下

pragma solidity >=0.5.0 <0.6.0;

import "./zombiefactory.sol";

contract KittyInterface {
  function getKitty(uint256 _id) external view returns (
    bool isGestating,
    bool isReady,
    uint256 cooldownIndex,
    uint256 nextActionAt,
    uint256 siringWithId,
    uint256 birthTime,
    uint256 matronId,
    uint256 sireId,
    uint256 generation,
    uint256 genes
  );
}

contract ZombieFeeding is ZombieFactory {

  address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;
  // Initialize kittyContract here using `ckAddress` from above
  KittyInterface kittyContract = KittyInterface(ckAddress);
  function feedAndMultiply(uint _zombieId, uint _targetDna) public {
    require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna) / 2;
    _createZombie("NoName", newDna);
  }

}

處理回傳多個值邏輯

因為 getKitty function 回傳多個值。

所以下面說明一下,在 solidity 接收多個值的語法。

// 回傳多值的 function
function multipleReturns() internal returns(uint a, uint b, uint c) {
  return (1, 2, 3);
}

function processMultipleReturns() external {
  uint a;
  uint b;
  uint c;
  // 用一個 () 來接收
  (a, b, c) = multipleReturns();
}

// Or if we only cared about one of the values:
function getLastReturnValue() external {
  uint c;
  // We can just leave the other fields blank:
  (,,c) = multipleReturns();
}

更新與 CryptoKitties 的邏輯

  1. 建立一個 feedOnKitty function。
    接收兩個參數: _zombieId(uint), _kittyId(uint)
    讀取權限是 public 。
  2. function 必須宣告一個 uint 變數命名為 kittyDna。
  3. function 需要使用參數 _kittyId 來呼叫 kittyContract.getKitty
    並且把回傳值 genes 儲存在 kittyDna 。
  4. 最後使用參數 _zombieId, kittyDna 呼叫 feedAndMultiply

更新 ZombieFeeding 如下

pragma solidity >=0.5.0 <0.6.0;

import "./zombiefactory.sol";

contract KittyInterface {
  function getKitty(uint256 _id) external view returns (
    bool isGestating,
    bool isReady,
    uint256 cooldownIndex,
    uint256 nextActionAt,
    uint256 siringWithId,
    uint256 birthTime,
    uint256 matronId,
    uint256 sireId,
    uint256 generation,
    uint256 genes
  );
}

contract ZombieFeeding is ZombieFactory {

  address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;
  KittyInterface kittyContract = KittyInterface(ckAddress);

  function feedAndMultiply(uint _zombieId, uint _targetDna) public {
    require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna) / 2;
    _createZombie("NoName", newDna);
  }

  // define function here
  function feedOnKitty(uint _zombieId, uint _kittyId) public {
    uint kittyDna;
    (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
    feedAndMultiply(_zombieId, kittyDna);
  }
}

特色化 Kitty Genes

特別讓 KittyZombie 具有一個特別的 DNA 組成

舉例來說在 DNA 最後兩個字元設定為 99。

這個可以透過 if statement 來做到

範例如下:

function eatBLT(string memory sandwich) public {
  // Remember with strings, we have to compare their keccak256 hashes
  // to check equality
  if (keccak256(abi.encodePacked(sandwich)) == keccak256(abi.encodePacked("BLT"))) {
    eat();
  }
}

更新 ZombieFeeding 邏輯

  1. 新增第3個字串參數 _species 給 feedAndMultiply。
    _species 是 memory 變數。
  2. 新增一個判斷式 if (keccak256(abi.encodePacked(_species)) == keccak256(abi.encodePacked("kitty"))) 到計算新 zombie dna 的邏輯
  3. 如果判斷式符合 ,則更新 newDna = newDna - newDna %100 + 99
  4. 更新 feedOnKitty 呼叫 feedAndMultiply 多帶入一個參數值 "kitty"

更新 ZombieFeeding 如下

pragma solidity >=0.5.0 <0.6.0;

import "./zombiefactory.sol";

contract KittyInterface {
  function getKitty(uint256 _id) external view returns (
    bool isGestating,
    bool isReady,
    uint256 cooldownIndex,
    uint256 nextActionAt,
    uint256 siringWithId,
    uint256 birthTime,
    uint256 matronId,
    uint256 sireId,
    uint256 generation,
    uint256 genes
  );
}

contract ZombieFeeding is ZombieFactory {

  address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;
  KittyInterface kittyContract = KittyInterface(ckAddress);

  // Modify function definition here:
  function feedAndMultiply(uint _zombieId, uint _targetDna, string memory _species) public {
    require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna) / 2;
    // Add an if statement here
    if (keccak256(abi.encodePacked(_species)) == keccak256(abi.encodePacked("kitty"))) {
      newDna = newDna - newDna % 100 + 99;      
    }
    _createZombie("NoName", newDna);
  }

  function feedOnKitty(uint _zombieId, uint _kittyId) public {
    uint kittyDna;
    (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
    // And modify function call here:
    feedAndMultiply(_zombieId, kittyDna, "kitty");
  }

}

與 Web3.js 互動

一旦 deploy ZombieFactory 與 ZombieFeeding 到已太鏈上

就可以使用下面 code 與之互動

var abi = /* abi generated by the compiler */
var ZombieFeedingContract = web3.eth.contract(abi)
var contractAddress = /* our contract address on Ethereum after deploying */
var ZombieFeeding = ZombieFeedingContract.at(contractAddress)

// Assuming we have our zombie's ID and the kitty ID we want to attack
let zombieId = 1;
let kittyId = 1;

// To get the CryptoKitty's image, we need to query their web API. This
// information isn't stored on the blockchain, just their webserver.
// If everything was stored on a blockchain, we wouldn't have to worry
// about the server going down, them changing their API, or the company 
// blocking us from loading their assets if they don't like our zombie game ;)
let apiUrl = "https://api.cryptokitties.co/kitties/" + kittyId
$.get(apiUrl, function(data) {
  let imgUrl = data.image_url
  // do something to display the image
})

// When the user clicks on a kitty:
$(".kittyImage").click(function(e) {
  // Call our contract's `feedOnKitty` method
  ZombieFeeding.feedOnKitty(zombieId, kittyId)
})

// Listen for a NewZombie event from our contract so we can display it:
ZombieFactory.NewZombie(function(error, result) {
  if (error) return
  // This function will display the zombie, like in lesson 1:
  generateZombie(result.zombieId, result.name, result.dna)
})

上一篇
從以太坊白皮書理解 web 3 概念 - Day9
下一篇
從以太坊白皮書理解 web 3 概念 - Day11
系列文
從以太坊白皮書理解 web 3 概念32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言