Skip to content

Commit

Permalink
feat: copy chainlink example code
Browse files Browse the repository at this point in the history
```
cp documentation/public/samples/ChainlinkFunctions/FunctionsConsumerExample.sol smart-contract/FunctionsConsumerExample.sol
cp -r smart-contract-examples/functions-examples/examples/5-use-secrets-threshold example
cp -r smart-contract-examples/functions-examples/package.json .
```
  • Loading branch information
JayWhite2357 committed Nov 13, 2024
1 parent f51e7cb commit d6479bd
Show file tree
Hide file tree
Showing 4 changed files with 425 additions and 0 deletions.
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("../../abi/functionsClient.json");
const ethers = require("ethers");
require("@chainlink/env-enc").config();

const consumerAddress = "0x8dFf78B7EE3128D00E90611FBeD20A71397064D9"; // REPLACE this with your Functions consumer address
const subscriptionId = 3; // REPLACE this with your subscription ID

const makeRequestSepolia = async () => {
// hardcoded for Ethereum Sepolia
const routerAddress = "0xb83E47C2bC239B3bf370bc41e1459A34b41238D0";
const linkTokenAddress = "0x779877A7B0D9E8603169DdbD7836e478b4624789";
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 = ["1", "USD"];
const secrets = { apiKey: process.env.COINMARKETCAP_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`
);

//////// 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);
});
47 changes: 47 additions & 0 deletions chainlink-functions/example/source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// This example shows how to make call an API using a secret
// https://coinmarketcap.com/api/documentation/v1/

// Arguments can be provided when a request is initated on-chain and used in the request source code as shown below
const coinMarketCapCoinId = args[0];
const currencyCode = args[1];

if (!secrets.apiKey) {
throw Error(
"COINMARKETCAP_API_KEY environment variable not set for CoinMarketCap API. Get a free key from https://coinmarketcap.com/api/"
);
}

// build HTTP request object

const coinMarketCapRequest = Functions.makeHttpRequest({
url: `https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest`,
// Get a free API key from https://coinmarketcap.com/api/
headers: {
"Content-Type": "application/json",
"X-CMC_PRO_API_KEY": secrets.apiKey,
},
params: {
convert: currencyCode,
id: coinMarketCapCoinId,
},
});

// Make the HTTP request
const coinMarketCapResponse = await coinMarketCapRequest;

if (coinMarketCapResponse.error) {
throw new Error("CoinMarketCap Error");
}

// fetch the price
const price =
coinMarketCapResponse.data.data[coinMarketCapCoinId]["quote"][currencyCode][
"price"
];

console.log(`Price: ${price.toFixed(2)} ${currencyCode}`);

// price * 100 to move by 2 decimals (Solidity doesn't support decimals)
// Math.round() to round to the nearest integer
// Functions.encodeUint256() helper function to encode the result from uint256 to bytes
return Functions.encodeUint256(Math.round(price * 100));
16 changes: 16 additions & 0 deletions chainlink-functions/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"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"
}
}
Loading

0 comments on commit d6479bd

Please sign in to comment.