Skip to content

Commit

Permalink
begin setup for weather data request through chainlink function
Browse files Browse the repository at this point in the history
  • Loading branch information
MattPereira committed Dec 6, 2023
1 parent 44d3127 commit 1574a22
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 90 deletions.
38 changes: 18 additions & 20 deletions packages/hardhat/contracts/FunctionsConsumer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ contract FunctionsConsumer is FunctionsClient, ConfirmedOwner {
// Event to log responses
event Response(
bytes32 indexed requestId,
uint256 builderCount,
string weatherResult,
bytes response,
bytes err
);
Expand All @@ -41,20 +41,10 @@ contract FunctionsConsumer is FunctionsClient, ConfirmedOwner {
bytes32 donID;

// State variables to hold function response data
uint256 public builderCount;
string public weatherResult;

// JavaScript source code
// Fetch character name from the Star Wars API.
// Documentation: https://swapi.dev/documentation#people
string source =
"const apiResponse = await Functions.makeHttpRequest({"
"url: `https://buidlguidl-v3.ew.r.appspot.com/api/stats`});"
"if (apiResponse.error) {"
"throw Error('Request failed');"
"}"
"const { data } = apiResponse;"
"const builderCount = data.builderCount;"
"return Functions.encodeUint256(builderCount);";
// JavaScript source code to fetch weather data from the OpenWeather API
string public weatherSource;

/**
* @notice Initializes the contract with the Chainlink router address and sets the contract owner
Expand All @@ -63,21 +53,29 @@ contract FunctionsConsumer is FunctionsClient, ConfirmedOwner {
address _router,
uint64 _subscriptionId,
uint32 _gasLimit,
bytes32 _donID
bytes32 _donID,
string memory _weatherSource
) FunctionsClient(_router) ConfirmedOwner(msg.sender) {
router = _router;
subscriptionId = _subscriptionId;
gasLimit = _gasLimit;
donID = _donID;
weatherSource = _weatherSource;
}

/**
* @notice Sends an HTTP request for Buidl Guidl stats
* @notice Sends an HTTP request to fetch weather data from the OpenWeather API
* @param args String arguments passed into the source code and accessible via the global variable `args`
* arg 1: the zip code
* arg 2: the ISO 3166 country code
* @return requestId The ID of the request
*/
function sendRequest() external onlyOwner returns (bytes32 requestId) {
function sendRequest(
string[] calldata args
) external onlyOwner returns (bytes32 requestId) {
FunctionsRequest.Request memory req;
req.initializeRequestForInlineJavaScript(source); // Initialize the request with JS code
req.initializeRequestForInlineJavaScript(weatherSource); // Initialize the request with JS code
if (args.length > 0) req.setArgs(args); // Set the arguments for the request

// Send the request and store the request ID
s_lastRequestId = _sendRequest(
Expand Down Expand Up @@ -106,10 +104,10 @@ contract FunctionsConsumer is FunctionsClient, ConfirmedOwner {
}
// Update the contract's state variables with the response and any errors
s_lastResponse = response;
builderCount = abi.decode(response, (uint256));
weatherResult = string(response);
s_lastError = err;

// Emit an event to log the response
emit Response(requestId, builderCount, s_lastResponse, s_lastError);
emit Response(requestId, weatherResult, s_lastResponse, s_lastError);
}
}
10 changes: 9 additions & 1 deletion packages/hardhat/deploy/05_FunctionsConsumer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
import { networkConfig } from "../helper-hardhat-config";
import fs from "fs";
import path from "path";

/** Deploy FunctionsConsumer contract
* @param hre HardhatRuntimeEnvironment object.
Expand All @@ -14,9 +16,15 @@ const functionsConsumer: DeployFunction = async function (hre: HardhatRuntimeEnv

log("------------------------------------");
const chainId = await hre.ethers.provider.getNetwork().then(network => network.chainId);

const { routerAddress, subscriptionId, gasLimit, donId } = networkConfig[chainId].FunctionsConsumer;
const weatherSourceScriptPath = path.join(__dirname, "../functions-source-scripts/fetch-weather-data.js");
const weatherSourceScript = fs.readFileSync(weatherSourceScriptPath, "utf8");

console.log("weatherSourceScriptPath", weatherSourceScriptPath);
console.log("weatherSourceScript", weatherSourceScript);

const args = [routerAddress, subscriptionId, gasLimit, donId];
const args = [routerAddress, subscriptionId, gasLimit, donId, weatherSourceScript];

const FunctionsConsumer = await deploy("FunctionsConsumer", {
from: deployer,
Expand Down
72 changes: 72 additions & 0 deletions packages/hardhat/functions-source-scripts/fetch-weather-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// This function fetches the latest temperature for a particular area from openweathermap API
// Args include the zipcode of your location, ISO 3166 country code
// units- unit in which we want the temperature (standard, metric, imperial)

if (!secrets.apiKey) {
throw Error("Weather API Key is not available!");
}

const zipCode = `${args[0]},${args[1]}`;

const geoCodingURL = "http://api.openweathermap.org/geo/1.0/zip?";

console.log(`Sending HTTP request to ${geoCodingURL}zip=${zipCode}`);

const geoCodingRequest = Functions.makeHttpRequest({
url: geoCodingURL,
method: "GET",
params: {
zip: zipCode,
appid: secrets.apiKey,
},
});

const geoCodingResponse = await geoCodingRequest;

if (geoCodingResponse.error) {
console.error(geoCodingResponse.error);
throw Error("Request failed, try checking the params provided");
}

console.log(geoCodingResponse);

const latitude = geoCodingResponse.data.lat;
const longitude = geoCodingResponse.data.lon;
const unit = args[2];

const url = `https://api.openweathermap.org/data/2.5/weather?`;

console.log(`Sending HTTP request to ${url}lat=${latitude}&lon=${longitude}&units=${unit}`);

const weatherRequest = Functions.makeHttpRequest({
url: url,
method: "GET",
params: {
lat: latitude,
lon: longitude,
appid: secrets.apiKey,
units: unit,
},
});

// Execute the API request (Promise)
const weatherResponse = await weatherRequest;
if (weatherResponse.error) {
console.error(weatherResponse.error);
throw Error("Request failed, try checking the params provided");
}

// gets the current temperature
const temperature = weatherResponse.data.main.temp;

// Gives the whole response from the request
console.log("Weather response", weatherResponse);

// result is in JSON object, containing only temperature
const result = {
temp: temperature,
};

// Use JSON.stringify() to convert from JSON object to JSON string
// Finally, use the helper Functions.encodeString() to encode from string to bytes
return Functions.encodeString(JSON.stringify(result));
68 changes: 68 additions & 0 deletions packages/hardhat/tasks/upload-secrets-to-DON.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// TODO: convert this to typescript

// const { SecretsManager } = require("@chainlink/functions-toolkit")
// const { networks } = require("../../networks")
// const process = require("process")
// const path = require("path")

// task("functions-upload-secrets-don", "Encrypts secrets and uploads them to the DON")
// .addParam(
// "slotid",
// "Storage slot number 0 or higher - if the slotid is already in use, the existing secrets for that slotid will be overwritten"
// )
// .addOptionalParam(
// "ttl",
// "Time to live - minutes until the secrets hosted on the DON expire (defaults to 10, and must be at least 5)",
// 10,
// types.int
// )
// .addOptionalParam(
// "configpath",
// "Path to Functions request config file",
// `${__dirname}/../../Functions-request-config.js`,
// types.string
// )
// .setAction(async (taskArgs) => {
// const signer = await ethers.getSigner()
// const functionsRouterAddress = networks[network.name]["functionsRouter"]
// const donId = networks[network.name]["donId"]

// const gatewayUrls = networks[network.name]["gatewayUrls"]

// const slotId = parseInt(taskArgs.slotid)
// const minutesUntilExpiration = taskArgs.ttl

// const secretsManager = new SecretsManager({
// signer,
// functionsRouterAddress,
// donId,
// })
// await secretsManager.initialize()

// // Get the secrets object from Functions-request-config.js or other specific request config.
// const requestConfig = require(path.isAbsolute(taskArgs.configpath)
// ? taskArgs.configpath
// : path.join(process.cwd(), taskArgs.configpath))

// if (!requestConfig.secrets || requestConfig.secrets.length === 0) {
// console.log("No secrets found in the request config.")
// return
// }

// console.log("Encrypting secrets and uploading to DON...")
// const encryptedSecretsObj = await secretsManager.encryptSecrets(requestConfig.secrets)

// const {
// version, // Secrets version number (corresponds to timestamp when encrypted secrets were uploaded to DON)
// success, // Boolean value indicating if encrypted secrets were successfully uploaded to all nodes connected to the gateway
// } = await secretsManager.uploadEncryptedSecretsToDON({
// encryptedSecretsHexstring: encryptedSecretsObj.encryptedSecrets,
// gatewayUrls,
// slotId,
// minutesUntilExpiration,
// })

// console.log(
// `\nYou can now use slotId ${slotId} and version ${version} to reference the encrypted secrets hosted on the DON.`
// )
// })
89 changes: 38 additions & 51 deletions packages/nextjs/components/functions/Showcase.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
import { ExternalLinkButton } from "~~/components/common";
import { Address } from "~~/components/scaffold-eth";
import { useScaffoldContract, useScaffoldContractRead, useScaffoldContractWrite } from "~~/hooks/scaffold-eth";
import {
useScaffoldContract, // useScaffoldContractRead, useScaffoldContractWrite
} from "~~/hooks/scaffold-eth";

export const Showcase = () => {
const { data: functionsConsumerContract } = useScaffoldContract({ contractName: "FunctionsConsumer" });

const { writeAsync: updateBuilderCount } = useScaffoldContractWrite({
contractName: "FunctionsConsumer",
functionName: "sendRequest",
onBlockConfirmation: txnReceipt => {
console.log("Transaction blockHash", txnReceipt.blockHash);
},
});
// const { writeAsync: fetchWeatherData } = useScaffoldContractWrite({
// contractName: "FunctionsConsumer",
// functionName: "sendRequest",
// args: [["94521", "US"]],
// });

const { data: builderCount } = useScaffoldContractRead({
contractName: "FunctionsConsumer",
functionName: "builderCount",
});
// const { data: weatherResult } = useScaffoldContractRead({
// contractName: "FunctionsConsumer",
// functionName: "weatherResult",
// });

console.log("builderCount", builderCount);
// const { data: s_lastError } = useScaffoldContractRead({
// contractName: "FunctionsConsumer",
// functionName: "s_lastError",
// });

// const { data: s_lastResponse } = useScaffoldContractRead({
// contractName: "FunctionsConsumer",
// functionName: " s_lastResponse",
// });

// console.log(weatherResult);

return (
<section>
Expand All @@ -34,14 +44,23 @@ export const Showcase = () => {
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<div>
<p className="text-xl">
Chainlink functions are used to send request from FunctionsConsumer contract to the decentralized oracle
network (DON) which executes API request to Buidl Guidl server in off chain environment and returns the
result on chain where the FunctionsConsumer contract stores the response in a state variable
Chainlink functions are used to send a request from a smart contract to a decentralized oracle network (DON)
which executes the provided source code in off chain environment and returns the result on chain through the
`fulfillRequest` function
</p>
<div>
<h3 className="font-cubano text-4xl">TODO</h3>
<ol className="list-decimal list-inside text-xl">
<li>Figure out how to upload encrypted secret to DON</li>
<li>Set up vercel serverless function with cron job to upload secret once per day</li>
<li>Set up function to return weather data</li>
</ol>
</div>
</div>

<div>
<h3 className="text-3xl text-center mb-5 font-cubano">On Chain Stats</h3>
<div className="flex gap-4 mb-3">
<h3 className="text-3xl text-center mb-5 font-cubano">On Chain Weather</h3>
{/* <div className="flex gap-4 mb-3">
<div>
<div className="stats shadow mb-3">
<div className="stat bg-base-200 w-48 text-center">
Expand All @@ -58,39 +77,7 @@ export const Showcase = () => {
</button>
</div>
</div>
<div>
<div className="stats shadow mb-3">
<div className="stat bg-base-200 w-48 text-center">
<div className="stat-value">0</div>
<div className="stat-title">Build Count</div>
</div>
</div>
<div className="flex justify-center">
<button
className="btn btn-outline hover:text-primary-content text-xl w-full"
onClick={async () => await updateBuilderCount()}
>
Update
</button>
</div>
</div>
<div>
<div className="stats shadow mb-3">
<div className="stat bg-base-200 w-48 text-center">
<div className="stat-value">0</div>
<div className="stat-title">Stream ETH</div>
</div>
</div>
<div className="flex justify-center">
<button
className="btn btn-outline hover:text-primary-content text-xl w-full"
onClick={async () => await updateBuilderCount()}
>
Update
</button>
</div>
</div>
</div>
</div> */}
</div>
</div>
</section>
Expand Down
Loading

0 comments on commit 1574a22

Please sign in to comment.