My chainlink request isn't getting fulfilled? - chainlink

Can someone help me investigate why my Chainlink requests aren't getting fulfilled. They get fulfilled in my tests (see hardhat test etherscan events(https://kovan.etherscan.io/address/0x8Ae71A5a6c73dc87e0B9Da426c1b3B145a6F0d12#events). But they don't get fulfilled when I make them from my react app (see react app contract's etherscan events https://kovan.etherscan.io/address/0x6da2256a13fd36a884eb14185e756e89ffa695f8#events).
Same contracts (different addresses), same function call.
Updates:
Here's the code I use to call them in my tests
const tx = await baseAgreement.connect(user).withdraw(
jobId,
oracleFee
);
Here's the code I use to call them in my UI
const signer = provider.getSigner();
const tx = await baseAgreement.connect(signer).withdraw(jobId, oracleFee);
Here's my Solidity Chainlink functions
function withdraw(
bytes32 _jobId,
uint256 _oracleFee
)
external
onlyContractActive()
returns(bytes32 requestId)
{
// check Link in this contract to see if we need to request more
checkLINK(_oracleFee);
// Build request
Chainlink.Request memory req = buildChainlinkRequest(_jobId, address(this), this.fulfillWithdraw.selector);
bytes memory url_bytes = abi.encodePacked(BASE_URL, mediaLink, API_KEY);
req.add("get", string(url_bytes));
req.add("path", "items.0.statistics.viewCount");
return sendChainlinkRequestTo(chainlinkOracleAddress(), req, _oracleFee);
}
/**
* #dev Callback for chainlink, this function pays the user
*/
function fulfillWithdraw(
bytes32 _requestId,
bytes32 _response
)
external
recordChainlinkFulfillment(_requestId)
{
// Convert api string response to an int
string memory _responseString = bytes32ToString(_response);
uint256 response = uint256(parseInt(_responseString, 0));
emit IntResponse(response);
// Pay the user
payUser(response);
}
function payUser(
uint256 _number
)
internal
{
// Calculate pay
uint256 budgetRemaining = getAgreementBalance();
uint256 accumulatedPay = budget - budgetRemaining;
uint256 pay = (payPerNumber * _number) - accumulatedPay;
if (pay > budgetRemaining) {
pay = budgetRemaining;
}
// Calculate platform fee
uint256 totalPlatformFee = (pay * PLATFORM_FEE) / 100;
// Transfer funds
paySomeone(payable(address(this)), user, pay-totalPlatformFee);
paySomeone(payable(address(this)), platformAddress, totalPlatformFee);
}
Full contract code can be viewed here: https://github.com/colinsteidtmann/dapplu-contracts/blob/main/contracts/BaseAgreement.sol
Update 2:
I figured out that my UI was deploying my contracts using a factory contract and a clones pattern (based on EIP 1167 standard and OpenZepplin's clones https://docs.openzeppelin.com/contracts/4.x/api/proxy#Clones ). But, my hardhat tests were deploying my contracts without the factory. Once I made my hardhat tests deploy the contracts using the factory contract, then they stopped working. So, does chainlink not work with Proxy contracts and the EIP 1167 standard?

Remove your agreement vars in MinimalClone.sol, and either have the user input them as args in your init() method or hardcode them into the request like this:
Chainlink.Request memory req = buildChainlinkRequest(_jobId, address(this), this.fulfillWithdraw.selector);
req.add("get", "https://youtube.googleapis.com/youtube/v3/videos?part=statistics&id=aaaaaakey=aaaaa");
The reason it wasn't working is that proxy contracts do not inherit the state of the implementation contracts, just the logic through the delegatecall() method. Thus, your proxy clones were reading essentially blank values when replacing those variables in the request.
Reference: Here is a good article on how proxies and delegate call works.

Related

How to transfer tokens from a smart contract wallet to an EOA wallet

I'm using Next.js, web3-react, ethers.js, MetaMask and Ganache to run a local blockchain.
What I'm trying to do: when the user clicks a button on the front-end, that button opens MetaMask where some tokens (I "minted/created" these tokens) are sent from the smart contract/smart contract creator to the user upon user consent.
The problem is: when the user clicks the button, the MetaMask pop-up appears, but the sender/recipient addresses are the same.
What I want is for the sender address to be the same as the smart contract/smart contract creator and this transaction should be done by MetaMask.
The following code is what I've done so far:
import { ethers, Contract } from 'ethers'
import { useWeb3React } from '#web3-react/core'
import HRC20 from '../../assets/HRC20.json'
const { account: userAccountAddress, library } = useWeb3React()
const provider = new ethers.providers.Web3Provider(
library.provider // this is the same as `window.ethereum.provider` injected by MetaMask
)
const signer = provider.getSigner()
const tokenContract = new ethers.Contract(
'0x4feEc53a54e36C80A2F0a47454Ab285B99A1a240',
HRC20.abi,
provider
)
const tokenContractWithSigner = contract.connect(signer)
const tx = tokenContractWithSigner.transfer(
userAccountAddress,
10000000000
)
My guess is that I need to specify the sender address when creating the provider or signer or something.
Based on calling tokenContractWithSigner.transfer, you have 2 args
function transfer(address _to, uint _value) public returns (bool success){
// make sure callee has enough balance
require(balanceOf[msg.sender]>=_value);
// ... more logic
to=payable(_to)
to.transfer(value);
}
Or:
function transfer(address _to, uint _value) public returns (bool success){
// make sure callee has enough balance
require(balanceOf[msg.sender]>=_value);
// ... more logic
to=payable(_to)
(bool success,)=to.call{value:_value}("");
require(success,"Transfer failed!");
}
When you call this:
tokenContractWithSigner.transfer(
userAccountAddress,
10000000000
).send({from:getAccount})
According to the ERC20 standart, the transfer method of an ERC20 contract is used to transfer tokens from the sender of the transaction, to the address specified. Because it is the same address that is sent the transaction and is given to the transfer method, the contract thinks you want to transfer tokens from an address to the same address.
To mint new tokens you need to add a mint method to your ERC20 contract.
I did find a solution to my problem: first, I can only make a transaction using MetaMask when the sender is me. I cannot send tokens on behalf of another wallet, even if I own it.
My approach was to create an endpoint on the backend that would receive the user's wallet address; then I connect to the wallet that holds the funds (usually the wallet that deployed the token) using its private key and that wallet signs the transaction. On the front end, I listen for a Transfer event so I can notify the user when their transactions are mined/confirmed.
Reference: https://ethereum.org/en/developers/tutorials/send-token-etherjs/#send-token-method

how do i change the price feed token from eth /usd to busd/usd with the chainlink oracle?

I am trying to get the price feed for BUSD/USD using the chainlink pricefeed oracle but it tells me that the code was reverted. this is also happening when i switch to any other token, i am using the kovan test net
this is the error i get
call to PriceConsumerV3.getLatestPrice
call to PriceConsumerV3.getLatestPrice errored: VM execution error.
Reverted 0x
and this is my code
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.7;
import "#chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";
contract PriceConsumerV3 {
AggregatorV3Interface internal priceFeed;
/**
* Network: Kovan
* Aggregator: ETH/USD
* Address: 0x9326BFA02ADD2366b30bacB125260Af641031331
*/
constructor() public {
priceFeed = AggregatorV3Interface(0xcBb98864Ef56E9042e7d2efef76141f15731B82f);
}
/**
* Returns the latest price
*/
function getLatestPrice() public view returns (int) {
(
uint80 roundID,
int price,
uint startedAt,
uint timeStamp,
uint80 answeredInRound
) = priceFeed.latestRoundData();
return price;
}
}
You have set incorrect ChainLink address!
correct ETH/USD address: 0x9326BFA02ADD2366b30bacB125260Af641031331
Address you are using in on BSC network, not Kovan network!
If you are trying to get BUSD/USD via a price feed on Kovan testnet, it is not available on that specific network. It is available on Ethereum mainnet however.
If you are testing using the price feed, you can always use a mock contract to mock the behavior of the feeds for testing purposes only.

Chainlink API Get Request isn't returning a value

I am creating the following Smart contract. It makes a Chainlink request which isn't returning a value from the API. I am using the following [jobID][1] and this [node][2], but the node doesn't start the job and I don't know why. I checked that the node has a balance of LINK tokens but it doesn't have any and I don't know how to send a balance to the contract node.
I am using Kovan Testnet to try the smart contract. Could you tell me something which I can try?
// This example code is designed to quickly deploy an example contract using Remix.
pragma solidity ^0.6.0;
import "#chainlink/contracts/src/v0.6/ChainlinkClient.sol";
contract APIConsumer is ChainlinkClient {
uint256 public volume;
address private oracle;
bytes32 private jobId;
uint256 private fee;
/**
* Network: Kovan
* Chainlink - 0x2f90A6D021db21e1B2A077c5a37B3C7E75D15b7e
* Chainlink - 29fa9aa13bf1468788b7cc4a500a45b8
* Fee: 0.1 LINK
*/
constructor() public {
setPublicChainlinkToken();
oracle = 0x56dd6586DB0D08c6Ce7B2f2805af28616E082455;
jobId = "b6602d14e4734c49a5e1ce19d45a4632";
fee = 0.1 * 10 ** 18; // 0.1 LINK
}
/**
* Create a Chainlink request to retrieve API response, find the target
* data, then multiply by 1000000000000000000 (to remove decimal places from data).
************************************************************************************
* STOP! *
* THIS FUNCTION WILL FAIL IF THIS CONTRACT DOES NOT OWN LINK *
* ---------------------------------------------------------- *
* Learn how to obtain testnet LINK and fund this contract: *
* ------- https://docs.chain.link/docs/acquire-link -------- *
* ---- https://docs.chain.link/docs/fund-your-contract ----- *
* *
************************************************************************************/
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.VOLUME24HOUR");
//request.add("path", "data.0.Myvalue");
// Multiply the result by 1000000000000000000 to remove decimals
// 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, uint256 _volume) public recordChainlinkFulfillment(_requestId) {
volume = _volume;
}
/**
* Withdraw LINK from this contract
*
* NOTE: DO NOT USE THIS IN PRODUCTION AS IT CAN BE CALLED BY ANY ADDRESS.
* THIS IS PURELY FOR EXAMPLE PURPOSES ONLY.
*/
function withdrawLink() external {
LinkTokenInterface linkToken = LinkTokenInterface(chainlinkTokenAddress());
require(linkToken.transfer(msg.sender, linkToken.balanceOf(address(this))), "Unable to transfer");
}
}
``
[1]: https://market.link/jobs/0609deab-6d61-4937-85e4-a8e810b8b272/runs
[2]: https://market.link/nodes/323602b9-3831-4f8d-a66b-3fb7531649eb/metrics?start=1631783169&end=1632387969
Looking at the Etherscan activity, it looks like the node you are using may be inactive. Try this node and jobId:
Oracle = 0xc57B33452b4F7BB189bB5AfaE9cc4aBa1f7a4FD8;
JobId = "d5270d1c311941d0b08bead21fea7747";
These were taken from the Chainlink Official Docs.
To check to see if a node may be inactive or not, check out the oracle address in a block explorer. You can see here that the original node you tried to use hasn't posted a transaction in quite a long time.
If a node is inactive you will need to find a new one or host one yourself. To find more nodes and jobs, you can check market.link or use the one found in the docs as mentioned earlier.

Dialogflow CX | How to close/reset a conversation

How can I close or reset a conversation programmatically from Java app?. According to Dialogflow CX documentation "A session remains active and its data is stored for 30 minutes after the last request is sent for the session."
I want to keep the session active for less time. For example, if I want the session to be active for 5 minutes, when user sends a message 5 minutes or more after last message, conversation must start again and previous flows must be closed and context parameters must be deleted.
With Dialogflow ES it is posible using ContextsClient, however new version does not offer ContextsClient class.
Dialogflow CX uses State Handlers to control conversation paths, unlike Dialogflow ES which uses Contexts.
For Dialogflow CX, you can end the current session by using the END_SESSION symbolic transition target. Once the END_SESSION transition target is invoked, it clears the current session and the next user input will restart the session at the start page of the Default Start Flow.
To achieve your desired use case, you’ll have to create your own implementation for it. Note that the solution below will only work if you integrate your Dialogflow CX agent to a custom front-end.
First, you should add an Event Handler to all of your Pages - so that the Event Handler will be accessible in any part of the conversation flow. In this Event Handler, define a custom event - for example: clearsession. Then, set its Transition to End Session Page. Once the clearsession event is invoked, it will end the current session.
Then, using your own business logic, you can create a custom function that could act as a timer for each user query. Once the timer reaches 5 minutes, your custom application should send a detectIntent request to your CX agent programmatically. This detectIntent request must contain the current session ID and the custom event (from the previously created Event Handler).
Here’s a sample detectIntent request that invokes a custom event using the Java Client Library:
// [START dialogflow_cx_detect_intent_event]
import com.google.api.gax.rpc.ApiException;
import com.google.cloud.dialogflow.cx.v3.*;
import com.google.common.collect.Maps;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public class DetectIntent {
// DialogFlow API Detect Intent sample with event input.
public static Map<String, QueryResult> detectIntentEvent(
String projectId,
String locationId,
String agentId,
String sessionId,
String languageCode,
String event)
throws IOException, ApiException {
SessionsSettings.Builder sessionsSettingsBuilder = SessionsSettings.newBuilder();
if (locationId.equals("global")) {
sessionsSettingsBuilder.setEndpoint("dialogflow.googleapis.com:443");
} else {
sessionsSettingsBuilder.setEndpoint(locationId + "-dialogflow.googleapis.com:443");
}
SessionsSettings sessionsSettings = sessionsSettingsBuilder.build();
Map<String, QueryResult> queryResults = Maps.newHashMap();
// Instantiates a client
try (SessionsClient sessionsClient = SessionsClient.create(sessionsSettings)) {
// Set the session name using the projectID (my-project-id), locationID (global), agentID
// (UUID), and sessionId (UUID).
SessionName session = SessionName.of(projectId, locationId, agentId, sessionId);
System.out.println("Session Path: " + session.toString());
EventInput.Builder eventInput = EventInput.newBuilder().setEvent(event);
// Build the query with the EventInput and language code (en-US).
QueryInput queryInput =
QueryInput.newBuilder().setEvent(eventInput).setLanguageCode(languageCode).build();
// Build the DetectIntentRequest with the SessionName and QueryInput.
DetectIntentRequest request =
DetectIntentRequest.newBuilder()
.setSession(session.toString())
.setQueryInput(queryInput)
.build();
// Performs the detect intent request.
DetectIntentResponse response = sessionsClient.detectIntent(request);
// Display the query result.
QueryResult queryResult = response.getQueryResult();
System.out.println("====================");
System.out.format(
"Detected Intent: %s (confidence: %f)\n",
queryResult.getIntent().getDisplayName(), queryResult.getIntentDetectionConfidence());
}
return queryResults;
}
public static void main(String[] args) {
String projectId = "<project-id>";
String locationId = "<location-id>";
String agentId = "<agent-id>";
String sessionId = "<current-session-id>";
String languageCode = "<language-code>";
String event = "clearsession";
try{
detectIntentEvent(projectId,locationId,agentId,sessionId, languageCode, event);
} catch (IOException e){
System.out.println(e.getMessage());
}
}
}
// [END dialogflow_cx_detect_intent_event]

How to get the result of a payable transaction using near-api-js?

When calling a contract method with attached deposits, you are redirected to the NEAR wallet for approving the transaction. How can the contract frontend app get the result of the transaction after returning from the wallet?
I've faced the same problem. For this moment near-api set transaction info in the browser url. So you get the transaction hash from url after returning from the wallet. Then using transaction hash get info about it using near-api-js:
const { providers } = require("near-api-js");
//network config (replace testnet with mainnet or betanet)
const provider = new providers.JsonRpcProvider(
"https://archival-rpc.testnet.near.org"
);
const TX_HASH = "9av2U6cova7LZPA9NPij6CTUrpBbgPG6LKVkyhcCqtk3";
// account ID associated with the transaction
const ACCOUNT_ID = "sender.testnet";
getState(TX_HASH, ACCOUNT_ID);
async function getState(txHash, accountId) {
const result = await provider.txStatus(txHash, accountId);
console.log("Result: ", result);
}
Documentation: https://docs.near.org/docs/api/naj-cookbook#get-transaction-status
There are 2 options:
Use provider.txStatus like Tom Links said. But the cons : we only know transaction success or fail but not the response from smart contract.
Seperate deposit api and actions api -> User must deposit before call actions api, so we can read the response.

Resources