Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/refactor source npm package #31

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions chainlink-functions/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
.env.enc
13 changes: 13 additions & 0 deletions chainlink-functions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
This example is heavily borrowed from https://docs.chain.link/chainlink-functions/tutorials/api-use-secrets.
1. Run `npm install` to install the dependencies.
2. Set up your on chain resources as shown here: https://docs.chain.link/chainlink-functions/tutorials/api-use-secrets#configure-your-onchain-resources.
3. Add the following environment variables, either in a `.env` file, or using `npx env-enc set`.
```
CONSUMER_ADDRESS=0x8dFf78B7EE3128D00E90611FBeD20A71397064D9 # REPLACE this with your Functions consumer address
SUBSCRIPTION_ID=3 # REPLACE this with your subscription ID
LINK_TOKEN_ADDRESS=0x779877A7B0D9E8603169DdbD7836e478b4624789 # REPLACE this with your wallet address
ETHEREUM_SEPOLIA_RPC_URL=
PRIVATE_KEY=
SXT_API_KEY=
```
4. Run `node example/request.js` to upload the secrets, run a simulation, and then submit a chainlink job.
250 changes: 250 additions & 0 deletions chainlink-functions/example/request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
const fs = require("fs");
const path = require("path");
const {
SubscriptionManager,
SecretsManager,
simulateScript,
ResponseListener,
ReturnType,
decodeResult,
FulfillmentCode,
} = require("@chainlink/functions-toolkit");
const functionsConsumerAbi = require("../smart-contract/FunctionsConsumerAbi.json");
const ethers = require("ethers");
require('dotenv').config(); // This will load from .env file in addition to env-enc file.
require("@chainlink/env-enc").config();

const consumerAddress = process.env.CONSUMER_ADDRESS;
const subscriptionId = process.env.SUBSCRIPTION_ID;

const makeRequestSepolia = async () => {
// hardcoded for Ethereum Sepolia
const routerAddress = "0xb83E47C2bC239B3bf370bc41e1459A34b41238D0";
const linkTokenAddress = process.env.LINK_TOKEN_ADDRESS;
const donId = "fun-ethereum-sepolia-1";
const explorerUrl = "https://sepolia.etherscan.io";
const gatewayUrls = [
"https://01.functions-gateway.testnet.chain.link/",
"https://02.functions-gateway.testnet.chain.link/",
];

// Initialize functions settings
const source = fs
.readFileSync(path.resolve(__dirname, "source.js"))
.toString();

const args = [];
const secrets = { apiKey: process.env.SXT_API_KEY };
const slotIdNumber = 0; // slot ID where to upload the secrets
const expirationTimeMinutes = 15; // expiration time in minutes of the secrets
const gasLimit = 300000;

// Initialize ethers signer and provider to interact with the contracts onchain
const privateKey = process.env.PRIVATE_KEY; // fetch PRIVATE_KEY
if (!privateKey)
throw new Error(
"private key not provided - check your environment variables"
);

const rpcUrl = process.env.ETHEREUM_SEPOLIA_RPC_URL; // fetch Sepolia RPC URL

if (!rpcUrl)
throw new Error(`rpcUrl not provided - check your environment variables`);

const provider = new ethers.providers.JsonRpcProvider(rpcUrl);

const wallet = new ethers.Wallet(privateKey);
const signer = wallet.connect(provider); // create ethers signer for signing transactions

///////// START SIMULATION ////////////

console.log("Start simulation...");

const response = await simulateScript({
source: source,
args: args,
bytesArgs: [], // bytesArgs - arguments can be encoded off-chain to bytes.
secrets: secrets,
});

console.log("Simulation result", response);
const errorString = response.errorString;
if (errorString) {
console.log(`❌ Error during simulation: `, errorString);
} else {
const returnType = ReturnType.uint256;
const responseBytesHexstring = response.responseBytesHexstring;
if (ethers.utils.arrayify(responseBytesHexstring).length > 0) {
const decodedResponse = decodeResult(
response.responseBytesHexstring,
returnType
);
console.log(`✅ Decoded response to ${returnType}: `, decodedResponse);
}
}

//////// ESTIMATE REQUEST COSTS ////////
console.log("\nEstimate request costs...");
// Initialize and return SubscriptionManager
const subscriptionManager = new SubscriptionManager({
signer: signer,
linkTokenAddress: linkTokenAddress,
functionsRouterAddress: routerAddress,
});
await subscriptionManager.initialize();

// estimate costs in Juels

const gasPriceWei = await signer.getGasPrice(); // get gasPrice in wei

const estimatedCostInJuels =
await subscriptionManager.estimateFunctionsRequestCost({
donId: donId, // ID of the DON to which the Functions request will be sent
subscriptionId: subscriptionId, // Subscription ID
callbackGasLimit: gasLimit, // Total gas used by the consumer contract's callback
gasPriceWei: BigInt(gasPriceWei), // Gas price in gWei
});

console.log(
`Fulfillment cost estimated to ${ethers.utils.formatEther(
estimatedCostInJuels
)} LINK`
);

// process.exit(0); // Uncomment to stop the script after simulation

//////// MAKE REQUEST ////////

console.log("\nMake request...");

// First encrypt secrets and upload the encrypted secrets to the DON
const secretsManager = new SecretsManager({
signer: signer,
functionsRouterAddress: routerAddress,
donId: donId,
});
await secretsManager.initialize();

// Encrypt secrets and upload to DON
const encryptedSecretsObj = await secretsManager.encryptSecrets(secrets);

console.log(
`Upload encrypted secret to gateways ${gatewayUrls}. slotId ${slotIdNumber}. Expiration in minutes: ${expirationTimeMinutes}`
);
// Upload secrets
const uploadResult = await secretsManager.uploadEncryptedSecretsToDON({
encryptedSecretsHexstring: encryptedSecretsObj.encryptedSecrets,
gatewayUrls: gatewayUrls,
slotId: slotIdNumber,
minutesUntilExpiration: expirationTimeMinutes,
});

if (!uploadResult.success)
throw new Error(`Encrypted secrets not uploaded to ${gatewayUrls}`);

console.log(
`\n✅ Secrets uploaded properly to gateways ${gatewayUrls}! Gateways response: `,
uploadResult
);

const donHostedSecretsVersion = parseInt(uploadResult.version); // fetch the reference of the encrypted secrets

const functionsConsumer = new ethers.Contract(
consumerAddress,
functionsConsumerAbi,
signer
);

// Actual transaction call
const transaction = await functionsConsumer.sendRequest(
source, // source
"0x", // user hosted secrets - encryptedSecretsUrls - empty in this example
slotIdNumber, // slot ID of the encrypted secrets
donHostedSecretsVersion, // version of the encrypted secrets
args,
[], // bytesArgs - arguments can be encoded off-chain to bytes.
subscriptionId,
gasLimit,
ethers.utils.formatBytes32String(donId) // jobId is bytes32 representation of donId
);

// Log transaction details
console.log(
`\n✅ Functions request sent! Transaction hash ${transaction.hash}. Waiting for a response...`
);

console.log(
`See your request in the explorer ${explorerUrl}/tx/${transaction.hash}`
);

const responseListener = new ResponseListener({
provider: provider,
functionsRouterAddress: routerAddress,
}); // Instantiate a ResponseListener object to wait for fulfillment.
(async () => {
try {
const response = await new Promise((resolve, reject) => {
responseListener
.listenForResponseFromTransaction(transaction.hash)
.then((response) => {
resolve(response); // Resolves once the request has been fulfilled.
})
.catch((error) => {
reject(error); // Indicate that an error occurred while waiting for fulfillment.
});
});

const fulfillmentCode = response.fulfillmentCode;

if (fulfillmentCode === FulfillmentCode.FULFILLED) {
console.log(
`\n✅ Request ${response.requestId
} successfully fulfilled. Cost is ${ethers.utils.formatEther(
response.totalCostInJuels
)} LINK.Complete reponse: `,
response
);
} else if (fulfillmentCode === FulfillmentCode.USER_CALLBACK_ERROR) {
console.log(
`\n⚠️ Request ${response.requestId
} fulfilled. However, the consumer contract callback failed. Cost is ${ethers.utils.formatEther(
response.totalCostInJuels
)} LINK.Complete reponse: `,
response
);
} else {
console.log(
`\n❌ Request ${response.requestId
} not fulfilled. Code: ${fulfillmentCode}. Cost is ${ethers.utils.formatEther(
response.totalCostInJuels
)} LINK.Complete reponse: `,
response
);
}

const errorString = response.errorString;
if (errorString) {
console.log(`\n❌ Error during the execution: `, errorString);
} else {
const responseBytesHexstring = response.responseBytesHexstring;
if (ethers.utils.arrayify(responseBytesHexstring).length > 0) {
const decodedResponse = decodeResult(
response.responseBytesHexstring,
ReturnType.uint256
);
console.log(
`\n✅ Decoded response to ${ReturnType.uint256}: `,
decodedResponse
);
}
}
} catch (error) {
console.error("Error listening for response:", error);
}
})();
};

makeRequestSepolia().catch((e) => {
console.error(e);
process.exit(1);
});
27 changes: 27 additions & 0 deletions chainlink-functions/example/source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Import the package
const SxTProofModule = await import("npm:sxt-chain-sdk");

// Extract the default export (SxTProof class)
const SxTProof = SxTProofModule.default;

// Define test parameters
const queryString = 'SELECT SUM(BLOCK_NUMBER), COUNT(*) FROM ETHEREUM.BLOCKS';
const commitmentKey =
'0xca407206ec1ab726b2636c4b145ac28749505e273536fae35330b966dac69e86a4832a125c0464e066dd20add960efb518424c4f434b5320455448455245554d4a9e6f9b8d43f6ad008f8c291929dee201';

if (!secrets.apiKey) {
throw Error("Missing secret: apiKey");
}

// Initialize the SxTProof instance
const proof = new SxTProof(queryString, commitmentKey, secrets.apiKey);

try {
// Kick off the proof and await execution
const result = await proof.executeWorkflow();
console.log("Workflow completed successfully:", result);
return Functions.encodeString("Verified");
} catch (error) {
console.log("Workflow failed: ", error);
return Functions.encodeString("Failed: ", error);
}
19 changes: 19 additions & 0 deletions chainlink-functions/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "functions-examples",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@chainlink/env-enc": "^1.0.5",
"@chainlink/functions-toolkit": "^0.2.7",
"ethers": "^5.7.2"
},
"devDependencies": {
"dotenv": "^16.4.5"
}
}
Loading
Loading