Skip to content

Commit

Permalink
stuck on failed off chain api request executed by DON
Browse files Browse the repository at this point in the history
  • Loading branch information
MattPereira committed Dec 16, 2023
1 parent 1574a22 commit 252b146
Show file tree
Hide file tree
Showing 13 changed files with 673 additions and 94 deletions.
17 changes: 10 additions & 7 deletions packages/hardhat/contracts/FunctionsConsumer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ contract FunctionsConsumer is FunctionsClient, ConfirmedOwner {
bytes public s_lastError;

// State variables used as args in the request
address router; // https://docs.chain.link/chainlink-functions/supported-networks
uint64 subscriptionId; // functions.chain.link
uint32 gasLimit;
bytes32 donID;
address public router; // https://docs.chain.link/chainlink-functions/supported-networks
uint64 public subscriptionId; // functions.chain.link
uint32 public gasLimit;
bytes32 public donID;

// State variables to hold function response data
string public weatherResult;
Expand Down Expand Up @@ -71,10 +71,14 @@ contract FunctionsConsumer is FunctionsClient, ConfirmedOwner {
* @return requestId The ID of the request
*/
function sendRequest(
string[] calldata args
) external onlyOwner returns (bytes32 requestId) {
string[] calldata args,
bytes memory encryptedSecretsUrls
) external returns (bytes32 requestId) {
FunctionsRequest.Request memory req;
req.initializeRequestForInlineJavaScript(weatherSource); // Initialize the request with JS code
if (encryptedSecretsUrls.length > 0) {
req.addSecretsReference(encryptedSecretsUrls);
}
if (args.length > 0) req.setArgs(args); // Set the arguments for the request

// Send the request and store the request ID
Expand All @@ -84,7 +88,6 @@ contract FunctionsConsumer is FunctionsClient, ConfirmedOwner {
gasLimit,
donID
);

return s_lastRequestId;
}

Expand Down
3 changes: 0 additions & 3 deletions packages/hardhat/deploy/05_FunctionsConsumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ const functionsConsumer: DeployFunction = async function (hre: HardhatRuntimeEnv
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, weatherSourceScript];

const FunctionsConsumer = await deploy("FunctionsConsumer", {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ if (!secrets.apiKey) {

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

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

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

Expand All @@ -25,7 +25,7 @@ const geoCodingResponse = await geoCodingRequest;

if (geoCodingResponse.error) {
console.error(geoCodingResponse.error);
throw Error("Request failed, try checking the params provided");
throw Error(zipCode + " is not a valid zip code --" + `${geoCodingURL}zip=${zipCode}`);
}

console.log(geoCodingResponse);
Expand Down Expand Up @@ -67,6 +67,8 @@ const result = {
temp: temperature,
};

console.log("result", result);

// 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));
2 changes: 2 additions & 0 deletions packages/hardhat/helper-hardhat-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface NetworkConfigEntryTypes {
subscriptionId: string;
gasLimit: string;
donId: string;
toolkitDonId: string;
};
tokenAddress: {
LINK: string;
Expand Down Expand Up @@ -48,6 +49,7 @@ const networkConfig: { [key: number]: NetworkConfigEntryTypes } = {
subscriptionId: "1464",
gasLimit: "300000",
donId: "0x66756e2d657468657265756d2d7365706f6c69612d3100000000000000000000",
toolkitDonId: "fun-ethereum-sepolia-1",
},
tokenAddress: {
LINK: "0x779877A7B0D9E8603169DdbD7836e478b4624789",
Expand Down
1 change: 1 addition & 0 deletions packages/hardhat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
},
"dependencies": {
"@chainlink/contracts": "^0.7.1",
"@chainlink/functions-toolkit": "^0.2.8",
"@openzeppelin/contracts": "^4.8.1",
"dotenv": "^16.0.3",
"envfile": "^6.18.0",
Expand Down
1 change: 1 addition & 0 deletions packages/hardhat/tasks/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./send-link";
export * from "./get-token-balance";
export * from "./upload-secret-to-gist";
54 changes: 54 additions & 0 deletions packages/hardhat/tasks/upload-secret-to-gist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as dotenv from "dotenv";
dotenv.config();
// import chalk from "chalk";
import { task } from "hardhat/config";
import { SecretsManager, createGist } from "@chainlink/functions-toolkit";
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { networkConfig } from "../helper-hardhat-config";

/** Upload a single secret to a github gist
* @param key the name for the secret
* @param value the value of the secret
* @returns the `encryptedSecretsReference` used to make a chainlink functions request
*/

export async function uploadSecretToGist(hre: HardhatRuntimeEnvironment, key: string, value: string) {
if (hre.network.name !== "sepolia") {
throw new Error("This script is only configured for sepolia network");
}

const chainId = await hre.ethers.provider.getNetwork().then(network => network.chainId);
const { routerAddress, toolkitDonId } = networkConfig[chainId].FunctionsConsumer;

const [signer] = await hre.ethers.getSigners();

const secretsManager = new SecretsManager({
signer: signer,
functionsRouterAddress: routerAddress,
donId: toolkitDonId,
});
await secretsManager.initialize();

const secretsObject = { [key]: value };
// console.log("secretsObject", secretsObject);

const encryptedSecrets = await secretsManager.encryptSecrets(secretsObject);
// console.log("encryptedSecrets", encryptedSecrets);

if (process.env.GITHUB_ACCESS_TOKEN) {
const gistURL = await createGist(process.env.GITHUB_ACCESS_TOKEN, JSON.stringify(encryptedSecrets));
console.log("gistURL", gistURL);
const encryptedSecretsReference: string = await secretsManager.encryptSecretsUrls([gistURL]);
console.log("encryptedSecretsReference:", encryptedSecretsReference);
return encryptedSecretsReference;
} else {
throw new Error("GITHUB_ACCESS_TOKEN not found in .env");
}
}

task("upload-secret-to-gist", "Uploads encrypted secrets to a GitHub gist")
.addParam("key", "The name for the secret")
.addParam("value", "The value of the secret")
.setAction(async (taskArgs, hre) => {
await uploadSecretToGist(hre, taskArgs.key, taskArgs.value);
});
97 changes: 53 additions & 44 deletions packages/nextjs/components/functions/Showcase.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,35 @@
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";

// https://gist.githubusercontent.com/MattPereira/110db22c9d24cc1b30298f2818e2d6ef/raw
// below is above URL encrypted with secretsManager.encryptSecretsUrls()
const encryptedSecretsReference =
"0x822432d0a83c5c2308b6fd1a06964bbf027b1aae2beb4997b2d766011d4da126a0e6a5e32d5977862178c975eb35d5473758e4d84f19fc08a88ef8f1166bfda8704c5e35bea4f6ca31078b14eca867accb7418ba049a3c1d6c08819da13a87ead4a860cb0fad5d77658263c791d73641fb76554f370c03286ae1fe11bf6089e2483a25bb30fc7c93db01a5080206042ae5dc64a022f167a455720ac1e2992ec6c7";

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

// const { writeAsync: fetchWeatherData } = useScaffoldContractWrite({
// contractName: "FunctionsConsumer",
// functionName: "sendRequest",
// args: [["94521", "US"]],
// });
const { writeAsync: sendRequest } = useScaffoldContractWrite({
contractName: "FunctionsConsumer",
functionName: "sendRequest",
args: [["94521", "US", "metric"], encryptedSecretsReference],
});

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

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

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

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

return (
<section>
Expand All @@ -48,38 +49,46 @@ export const Showcase = () => {
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>
<button className="btn btn-primary" onClick={() => sendRequest()}>
Send Request
</button>

<div className="mt-5">
<h5 className="font-bold text-3xl">TODO</h5>
<ul>
<li>Debug to get successful response</li>
<li>Store latest city/country on chain</li>
<li>Display city/country for latest request on chain</li>
<li>Fancy user interface that shows image of sun if hot, clouds if cold, etc...</li>
</ul>
</div>
</div>

<div>
<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">
<div className="stat-value">{builderCount?.toString()}</div>
<div className="stat-title">Builder 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> */}
<h3 className="text-3xl text-center mb-5 font-bold">On Chain Weather</h3>
<div>
<label className="text-xl font-semibold">s_lastResponse ( bytes )</label>
<div className="w-full overflow-x-auto hide-scrollbar">{s_lastResponse?.toString()}</div>

<label className="text-xl font-semibold">s_lastError ( bytes )</label>
<div className="w-full overflow-x-auto hide-scrollbar">{s_lastError?.toString()}</div>
<div className="text-xl">{s_lastError && decodeHexError(s_lastError)}</div>
</div>
</div>
</div>
</section>
);
};

function decodeHexError(hexString: string) {
// Remove the '0x' prefix if it exists
if (hexString.startsWith("0x")) {
hexString = hexString.substring(2);
}

// Convert the hex string to a Buffer
const buffer = Buffer.from(hexString, "hex");

// Convert the Buffer to a string
return buffer.toString("utf8");
}
64 changes: 63 additions & 1 deletion packages/nextjs/generated/deployedContracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ const contracts = {
],
},
FunctionsConsumer: {
address: "0x7855b9755E8A1704cd59288214fd50387e05c50C",
address: "0x00B07a4b8155Ab74e45F9BabE01686e2684c3041",
abi: [
{
inputs: [
Expand Down Expand Up @@ -642,6 +642,11 @@ const contracts = {
name: "EmptyArgs",
type: "error",
},
{
inputs: [],
name: "EmptySecrets",
type: "error",
},
{
inputs: [],
name: "EmptySource",
Expand Down Expand Up @@ -770,6 +775,32 @@ const contracts = {
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [],
name: "donID",
outputs: [
{
internalType: "bytes32",
name: "",
type: "bytes32",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "gasLimit",
outputs: [
{
internalType: "uint32",
name: "",
type: "uint32",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
Expand Down Expand Up @@ -806,6 +837,19 @@ const contracts = {
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "router",
outputs: [
{
internalType: "address",
name: "",
type: "address",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "s_lastError",
Expand Down Expand Up @@ -852,6 +896,11 @@ const contracts = {
name: "args",
type: "string[]",
},
{
internalType: "bytes",
name: "encryptedSecretsUrls",
type: "bytes",
},
],
name: "sendRequest",
outputs: [
Expand All @@ -864,6 +913,19 @@ const contracts = {
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [],
name: "subscriptionId",
outputs: [
{
internalType: "uint64",
name: "",
type: "uint64",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
Expand Down
Loading

0 comments on commit 252b146

Please sign in to comment.