-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
``` 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
1 parent
f51e7cb
commit d6479bd
Showing
4 changed files
with
425 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
Oops, something went wrong.