Reputation: 31
I am trying to use a chainlink request to make an api call, then update the volume variable with the result of the api call.
The api call should retrieve a string. After deploying and funding the smart contract it seems I am able to make the api call successfully (although I'm not sure about this). The problem is the volume variable isn't updating.
I adapted the code from a chainlink tutorial. The original tutorial code works, to my mind the changes I made should also work. Can anyone help with this?
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol";
/**
* Request testnet LINK and ETH here: https://faucets.chain.link/
* Find information on LINK Token Contracts and get the latest ETH and LINK faucets here: https://docs.chain.link/docs/link-token-contracts/
*/
/**
* THIS IS AN EXAMPLE CONTRACT WHICH USES HARDCODED VALUES FOR CLARITY.
* PLEASE DO NOT USE THIS CODE IN PRODUCTION.
*/
contract APIConsumer is ChainlinkClient {
using Chainlink for Chainlink.Request;
string public volume;
address private oracle;
bytes32 private jobId;
uint256 private fee;
/**
* Network: Kovan
* Oracle: 0xc57B33452b4F7BB189bB5AfaE9cc4aBa1f7a4FD8 (Chainlink Devrel
* Node)
* Job ID: d5270d1c311941d0b08bead21fea7747
* Fee: 0.1 LINK
*/
constructor() {
setPublicChainlinkToken();
oracle = 0xF405B99ACa8578B9eb989ee2b69D518aaDb90c1F;
jobId = "c51694e71fa94217b0f4a71b2a6b565a";
fee = 0.1 * 10 ** 18; // (Varies by network and job)
}
/**
* Create a Chainlink request to retrieve API response, find the target
* data, then multiply by 1000000000000000000 (to remove decimal places from data).
*/
function requestVolumeData() public returns (bytes32 requestId)
{
Chainlink.Request memory request = buildChainlinkRequest(jobId, address(this), this.fulfill.selector);
// Set the URL to perform the GET request on
request.add("get", "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD");
// Set the path to find the desired data in the API response, where the response format is:
// {"RAW":
// {"ETH":
// {"USD":
// {
// "VOLUME24HOUR": xxx.xxx,
// }
// }
// }
// }
request.add("path", "RAW.ETH.USD.MARKET");
// // Multiply the result by 1000000000000000000 to remove decimals
// int timesAmount = 10**18;
// request.addInt("times", timesAmount);
// Sends the request
return sendChainlinkRequestTo(oracle, request, fee);
}
/**
* Receive the response in the form of uint256
*/
function fulfill(bytes32 _requestId, bytes32 _volume) public recordChainlinkFulfillment(_requestId)
{
volume = bytes32ToString(_volume);
}
function bytes32ToString(bytes32 _bytes32) public pure returns (string memory) {
uint8 i = 0;
while(i < 32 && _bytes32[i] != 0) {
i++;
}
bytes memory bytesArray = new bytes(i);
for (i = 0; i < 32 && _bytes32[i] != 0; i++) {
bytesArray[i] = _bytes32[i];
}
return string(bytesArray);
}
// function withdrawLink() external {} - Implement a withdraw function to avoid locking your LINK in the contract
}
Upvotes: 3
Views: 807
Reputation: 71
Below is a working example that will retrieve your string object on Ethereum Sepolia testnet.
The following Chainlink consumer contract uses the function getResponseString()
to typecast the returned bytes
object into a string
object:
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol";
import "@chainlink/contracts/src/v0.8/ConfirmedOwner.sol";
/**
* LinkWell Nodes consumer contract example
*/
/**
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
*/
contract LinkWellStringBytesConsumerContractExample is ChainlinkClient, ConfirmedOwner {
using Chainlink for Chainlink.Request;
address private oracleAddress;
bytes32 private jobId;
uint256 private fee;
constructor() ConfirmedOwner(msg.sender) {
// LinkWell Nodes oracle address and jobId
setChainlinkToken(0x779877A7B0D9E8603169DdbD7836e478b4624789);
setOracleAddress(0x0FaCf846af22BCE1C7f88D1d55A038F27747eD2B);
setJobId("8ced832954544a3c98543c94a51d6a8d");
setFeeInHundredthsOfLink(0); // 0 LINK
}
// Send a request to the Chainlink oracle
function request() public {
Chainlink.Request memory req = buildOperatorRequest(jobId, this.fulfill.selector);
// DEFINE THE REQUEST PARAMETERS (example)
req.add('method', 'GET');
req.add('url', 'https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD');
req.add('headers', '["accept", "application/json"]');
req.add('body', '');
req.add('contact', 'anonymous');
// PROCESS THE RESULT (example)
req.add('path', 'RAW,ETH,USD,MARKET');
// Send the request to the Chainlink oracle
sendOperatorRequest(req, fee);
}
bytes public responseBytes;
// Receive the result from the Chainlink oracle
event RequestFulfilled(bytes32 indexed requestId);
function fulfill(bytes32 requestId, bytes memory bytesData) public recordChainlinkFulfillment(requestId) {
// Process the oracle response
// emit RequestFulfilled(requestId); // (optional) emits this event in the on-chain transaction logs, allowing Web3 applications to listen for this transaction
responseBytes = bytesData; // example value: 0x426974636f696e
}
// Retrieve the response data as a string
function getResponseString() public view onlyOwner returns (string memory) {
return string(responseBytes); // example value: Bitcoin
}
// Update oracle address
function setOracleAddress(address _oracleAddress) public onlyOwner {
oracleAddress = _oracleAddress;
setChainlinkOracle(_oracleAddress);
}
function getOracleAddress() public view onlyOwner returns (address) {
return oracleAddress;
}
// Update jobId
function setJobId(string memory _jobId) public onlyOwner {
jobId = bytes32(bytes(_jobId));
}
function getJobId() public view onlyOwner returns (string memory) {
return string(abi.encodePacked(jobId));
}
// Update fees
function setFeeInJuels(uint256 _feeInJuels) public onlyOwner {
fee = _feeInJuels;
}
function setFeeInHundredthsOfLink(uint256 _feeInHundredthsOfLink) public onlyOwner {
setFeeInJuels((_feeInHundredthsOfLink * LINK_DIVISIBILITY) / 100);
}
function getFeeInHundredthsOfLink() public view onlyOwner returns (uint256) {
return (fee * 100) / LINK_DIVISIBILITY;
}
function withdrawLink() public onlyOwner {
LinkTokenInterface link = LinkTokenInterface(chainlinkTokenAddress());
require(
link.transfer(msg.sender, link.balanceOf(address(this))),
"Unable to transfer"
);
}
}
If you're looking to replicate this result on other testnets besides Ethereum Sepolia, you can replace the oracle contract address with the one for the corresponding testnet.
Oracle contract addresses for other testnets are as follows:
0xd36c6B1777c7f3Db1B3201bDD87081A9045B7b46
0xd0EbC86a4f67654B654Feb0e615d7f5C139a6406
0xa57f0cEd49bB1ed7327f950B12a8419cFD01855f
0xd08FEb8203E76f836D74608595346ab6b0f768C9
0xB9C47B9609174716CE536324d4FbEad9292c1d3a
0x14bc7F6Da6cA3E072793c185e01a76E62341CC61
0x12A3d7759F745f4cb8EE8a647038c040cB8862A5
I hope that helps!
Upvotes: 0
Reputation: 11
I had a similar issue, and you should be able to do this without the bytes to string function. What worked for me was:
_volume
argument to string memory
so that it can be assigned to the string storage value for volume
at the top of the contract._volume
in the TOML file encode_mwr
it should also be '_volume' in the solidity fulfill function arguments._volume
data defined as a string in the encode_mwr
. This can also be extended to return multiple values of mixed types of uint, int, string, bytes in a single call.Hope this helps someone and sorry if my response is not well formatted, it is my first answer I have submitted to stackoverflow.
e.g below TOML code snippet:
encode_mwr [type="ethabiencode"
abi="(bytes32 requestId, string _volume)"
data="{\\"requestId\\": $(decode_log.requestId), \\"_volume\\": $(volume_parse)}"
]
Edited solidity file:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol";
/**
* Request testnet LINK and ETH here: https://faucets.chain.link/
* Find information on LINK Token Contracts and get the latest ETH and LINK faucets here: https://docs.chain.link/docs/link-token-contracts/
*/
/**
* THIS IS AN EXAMPLE CONTRACT WHICH USES HARDCODED VALUES FOR CLARITY.
* PLEASE DO NOT USE THIS CODE IN PRODUCTION.
*/
contract APIConsumer is ChainlinkClient {
using Chainlink for Chainlink.Request;
string public volume;
address private oracle;
bytes32 private jobId;
uint256 private fee;
/**
* Network: Kovan
* Oracle: 0xc57B33452b4F7BB189bB5AfaE9cc4aBa1f7a4FD8 (Chainlink Devrel
* Node)
* Job ID: d5270d1c311941d0b08bead21fea7747
* Fee: 0.1 LINK
*/
constructor() {
setPublicChainlinkToken();
oracle = 0xF405B99ACa8578B9eb989ee2b69D518aaDb90c1F;
jobId = "c51694e71fa94217b0f4a71b2a6b565a";
fee = 0.1 * 10 ** 18; // (Varies by network and job)
}
/**
* Create a Chainlink request to retrieve API response, find the target
* data, then multiply by 1000000000000000000 (to remove decimal places from data).
*/
function requestVolumeData() public returns (bytes32 requestId)
{
Chainlink.Request memory request = buildChainlinkRequest(jobId, address(this), this.fulfill.selector);
// Set the URL to perform the GET request on
request.add("get", "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD");
// Set the path to find the desired data in the API response, where the response format is:
// {"RAW":
// {"ETH":
// {"USD":
// {
// "VOLUME24HOUR": xxx.xxx,
// }
// }
// }
// }
request.add("path", "RAW.ETH.USD.MARKET");
// // Multiply the result by 1000000000000000000 to remove decimals
// int timesAmount = 10**18;
// request.addInt("times", timesAmount);
// Sends the request
return sendChainlinkRequestTo(oracle, request, fee);
}
/**
* Receive the response in the form of uint256
*/
function fulfill(bytes32 _requestId, string memory _volume) public recordChainlinkFulfillment(_requestId)
{
volume = _volume;
}
// function withdrawLink() external {} - Implement a withdraw function to avoid locking your LINK in the contract
}
Upvotes: 1
Reputation: 1717
Here is one working example with AviationStack. Replace the <access_key>
with your access_key, in the API URL.
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import '@chainlink/contracts/src/v0.8/ChainlinkClient.sol';
import '@chainlink/contracts/src/v0.8/ConfirmedOwner.sol';
/**
* Request testnet LINK and ETH here: https://faucets.chain.link/
* Find information on LINK Token Contracts and get the latest ETH and LINK faucets here: https://docs.chain.link/docs/link-token-contracts/
*/
/**
* THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
contract AviationGenericLargeResponse is ChainlinkClient, ConfirmedOwner {
using Chainlink for Chainlink.Request;
// variable bytes(arbitrary-length raw byte data) returned in a single oracle response
bytes public data;
string public flightStatus;
bytes32 private jobId;
uint256 private fee;
event RequestFulfilled(bytes32 indexed requestId, bytes indexed data);
/**
* @notice Initialize the link token and target oracle
* @dev The oracle address must be an Operator contract for multiword response
*
*
* Goerli Testnet details:
* Link Token: 0x326C977E6efc84E512bB9C30f76E30c160eD06FB
* Oracle: 0xCC79157eb46F5624204f47AB42b3906cAA40eaB7 (Chainlink DevRel)
* jobId: 7da2702f37fd48e5b1b9a5715e3509b6
*
*/
constructor() ConfirmedOwner(msg.sender) {
setChainlinkToken(0x326C977E6efc84E512bB9C30f76E30c160eD06FB);
setChainlinkOracle(0xCC79157eb46F5624204f47AB42b3906cAA40eaB7);
jobId = '7da2702f37fd48e5b1b9a5715e3509b6';
fee = (1 * LINK_DIVISIBILITY) / 10; // 0,1 * 10**18 (Varies by network and job)
}
/**
* @notice Request variable bytes from the oracle
*/
function requestBytes() public {
Chainlink.Request memory req = buildChainlinkRequest(jobId, address(this), this.fulfillBytes.selector);
req.add(
'get',
'http://api.aviationstack.com/v1/flights?access_key=a6656fabf7fbe4fde6e4061f5be7042a&flight_number=611&airline_name=Qantas&flight_status=landed&limit=1'
);
req.add('path', 'data,0,flight_status');
sendChainlinkRequest(req, fee);
}
/**
* @notice Fulfillment function for variable bytes
* @dev This is called by the oracle. recordChainlinkFulfillment must be used.
*/
function fulfillBytes(bytes32 requestId, bytes memory bytesData) public recordChainlinkFulfillment(requestId) {
emit RequestFulfilled(requestId, bytesData);
data = bytesData;
flightStatus = string(data);
}
/**
* Allow withdraw of Link tokens from the contract
*/
function withdrawLink() public onlyOwner {
LinkTokenInterface link = LinkTokenInterface(chainlinkTokenAddress());
require(link.transfer(msg.sender, link.balanceOf(address(this))), 'Unable to transfer');
}
}
Upvotes: 1
Reputation: 6131
To return a string, you actually have to return a bytes32
and cast it to a string on-chain.
You can use something like this, to turn a bytes32 to string:
function bytes32ToString(bytes32 _bytes32) public pure returns (string memory) {
uint8 i = 0;
while(i < 32 && _bytes32[i] != 0) {
i++;
}
bytes memory bytesArray = new bytes(i);
for (i = 0; i < 32 && _bytes32[i] != 0; i++) {
bytesArray[i] = _bytes32[i];
}
return string(bytesArray);
}
Upvotes: 1