How to make an API call in solidity?
Asked Answered
T

3

10

I have a smart contract that I’m trying to make, it pays out the winners of my League of Legends tournament. However I’m running into an issue. I need to make an API call to get the winner of the match, I have a simple URL that I’ve make.

"example-winner.com/winner"

And it returns simple JSON with the address of the winner:

{"winner":"0xa7D0......."}

However, I’m not sure how to make the API call to the outside function. I know I need to use some sort of oracle technology.

Any thoughts? Below is my code:

pragma solidity ^0.4.24;
contract LeagueWinners{
    address public manager;
    address[] public players;
    uint256 MINIMUM = 1000000000000000;
    constructor() public{
        manager = msg.sender;
    }
    function enter() public payable{
        assert(msg.value > MINIMUM);
        players.push(msg.sender);
    }
    function getWinner() public{
        assert(msg.sender == manager);
        // TODO
        // Get the winner from the API call
        result = 0; // the result of the API call
        players[result].transfer(address(this).balance);
        // returns an adress object
        // all units of transfer are in wei
        players = new address[](0);
        // this empties the dynamic array
    }
}
Theis answered 28/5, 2020 at 19:55 Comment(0)
P
17

You can use Chainlink as your Oracle.

As many have mentioned, you will need an oracle to get your API call. Something that is important to note, your contract is actually asking an oracle to make your API call for you, and not making the API call itself. This is because the blockchain is deterministic. For more information see this thread.

To answer your question, you can use the decentralized oracle service Chainlink.

You'd add a function:

  function getWinner() 
    public
    onlyOwner
  {
    Chainlink.Request memory req = buildChainlinkRequest(JOB, address(this), this.fulfill.selector);
    req.add("get", "example-winner.com/winner");
    req.add("path", "winner");
    sendChainlinkRequestTo(ORACLE, req, ORACLE_PAYMENT);
  }

For the purpose of the following exmaple, we are going to pretend you want to return a uint256 instead of an address. You can return a bytes32 and then convert it to an address, but for simplicity let's say the API returns the index of the winner. You'll have to find a node and jobId that can make a http.get request and return a uint256 object. You can find nodes and jobs from market.link. Each testnet (Ropsten, Mainnet, Kovan, etc) has different node addresses, so make sure you pick the right ones.

For this demo, we are going to use LinkPool's ropsten node

address ORACLE=0x83F00b902cbf06E316C95F51cbEeD9D2572a349a;
bytes32 JOB= "c179a8180e034cf5a341488406c32827";

Ideally, you'd choose a number of nodes to run your job, to make it trustless and decentralized. You can read here for more information on precoordinators and aggregating data. disclosure I am the author of that blog

Your full contract would look like:

pragma solidity ^0.6.0;

import "github.com/smartcontractkit/chainlink/evm-contracts/src/v0.6/ChainlinkClient.sol";


contract GetData is ChainlinkClient {
    uint256 indexOfWinner;
    address public manager;
    address payable[] public players;
    uint256 MINIMUM = 1000000000000000;
  
  // The address of an oracle 
    address ORACLE=0x83F00b902cbf06E316C95F51cbEeD9D2572a349a;
    //bytes32 JOB= "93fedd3377a54d8dac6b4ceadd78ac34";
    bytes32 JOB= "c179a8180e034cf5a341488406c32827";
    uint256 ORACLE_PAYMENT = 1 * LINK;

  constructor() public {
    setPublicChainlinkToken();
    manager = msg.sender;
  }

function getWinnerAddress() 
    public
    onlyOwner
  {
    Chainlink.Request memory req = buildChainlinkRequest(JOB, address(this), this.fulfill.selector);
    req.add("get", "example-winner.com/winner");
    req.add("path", "winner");
    sendChainlinkRequestTo(ORACLE, req, ORACLE_PAYMENT);
  }

  // When the URL finishes, the response is routed to this function
  function fulfill(bytes32 _requestId, uint256 _index)
    public
    recordChainlinkFulfillment(_requestId)
  {
    indexOfWinner = _index;
    assert(msg.sender == manager);
    players[indexOfWinner].transfer(address(this).balance);
    players = new address payable[](0);
  }
  
  function enter() public payable{
        assert(msg.value > MINIMUM);
        players.push(msg.sender);
    } 
    
  modifier onlyOwner() {
    require(msg.sender == manager);
    _;
  }
    
    // Allows the owner to withdraw their LINK on this contract
  function withdrawLink() external onlyOwner() {
    LinkTokenInterface _link = LinkTokenInterface(chainlinkTokenAddress());
    require(_link.transfer(msg.sender, _link.balanceOf(address(this))), "Unable to transfer");
  }
  
  
}

This would do about everything you need.


If you can't adjust the API to return a uint, you can return a bytes32 and then convert it to an address or a string.

 function bytes32ToStr(bytes32 _bytes32) public pure returns (string memory) {
     bytes memory bytesArray = new bytes(32);
     for (uint256 i; i < 32; i++) {
         bytesArray[i] = _bytes32[i];
         }
     return string(bytesArray);
     }
Parakeet answered 28/5, 2020 at 22:11 Comment(0)
D
3

You cannot. The vm does not have any I/O outside of the blockchain itself. Instead you will need to tell your smart contract who the winner is and then the smart contract can just read the value of that variable.

This design pattern is also known as the "oracle". Google "Ethereum oracle" for more info.

Basically your web server can call your smart contract. Your smart contract cannot call your web server. If you need your smart contract to access a 3rd party service then your web server will need to make the request then forward the result to solidity by calling a function in your smart contract.

Draftee answered 28/5, 2020 at 20:47 Comment(0)
T
1

You didn't properly explain what you are trying to do. Are you having trouble with the solidity code? or rather with your server? Here is an edited version. See if it helps.

pragma solidity ^0.4.24;
contract LeagueWinners{
    address public manager;
    //address[] public players;
    uint256 MINIMUM = 1000000000000000;
    constructor() public{
        manager = msg.sender;
    }

    struct Player {
        address playerAddress;
        uint score;
    }

    Player[] public players;


    // i prefer passing arguments this way
    function enter(uint value) public payable{
        assert(msg.value > MINIMUM);
        players.push(Player(msg.sender, value));
    }

    //call this to get the address of winner
    function winningPlayer() public view
            returns (address winner)
    {
        uint winningScore = 0;
        for (uint p = 0; p < players.length; p++) {
            if (players[p].score > winningScore) {
                winningScore = players[p].score;
                winner = players[p].playerAddress;
            }
        }
    }

    // call this to transfer fund
    function getWinner() public{
        require(msg.sender == manager, "Only a manager is allowed to perform this operation");
        // TODO

        address winner = winningPlayer();
        // Get the winner from the API call
        //uint result = 0; // the result of the API call
        winner.transfer(address(this).balance);
        // returns an adress object
        // all units of transfer are in wei
        delete players;
        // this empties the dynamic array
    }
}

At least that is what I understand by your question.

Totalizer answered 28/5, 2020 at 20:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.