Skip to content

Commit bea0d44

Browse files
dwightjlaelmanaa
andauthored
Update examples to support package imports (#36)
* Update examples to support package imports * Update to use data feeds with ethers * small fixes --------- Co-authored-by: aelmanaa <[email protected]>
1 parent 8da8dae commit bea0d44

File tree

4 files changed

+299
-5
lines changed

4 files changed

+299
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
const {
4+
SubscriptionManager,
5+
simulateScript,
6+
ResponseListener,
7+
ReturnType,
8+
decodeResult,
9+
FulfillmentCode,
10+
} = require("@chainlink/functions-toolkit");
11+
const functionsConsumerAbi = require("../../abi/functionsClient.json");
12+
const ethers = require("ethers");
13+
require("@chainlink/env-enc").config();
14+
15+
const consumerAddress = "0x91257aa1c6b7f382759c357fbc53c565c80f7fee"; // REPLACE this with your Functions consumer address
16+
const subscriptionId = 38; // REPLACE this with your subscription ID
17+
18+
// hardcoded for Polygon Mumbai
19+
const makeRequestMumbai = async () => {
20+
// hardcoded for Polygon Mumbai
21+
const routerAddress = "0x6E2dc0F9DB014aE19888F539E59285D2Ea04244C";
22+
const linkTokenAddress = "0x326C977E6efc84E512bB9C30f76E30c160eD06FB";
23+
const donId = "fun-polygon-mumbai-1";
24+
const explorerUrl = "https://mumbai.polygonscan.com";
25+
26+
// Initialize functions settings
27+
const source = fs
28+
.readFileSync(path.resolve(__dirname, "source.js"))
29+
.toString();
30+
31+
const args = []; // no args in this example
32+
const gasLimit = 300000;
33+
34+
// Initialize ethers signer and provider to interact with the contracts onchain
35+
const privateKey = process.env.PRIVATE_KEY; // fetch PRIVATE_KEY
36+
if (!privateKey)
37+
throw new Error(
38+
"private key not provided - check your environment variables"
39+
);
40+
41+
const rpcUrl = process.env.POLYGON_MUMBAI_RPC_URL; // fetch mumbai RPC URL
42+
43+
if (!rpcUrl)
44+
throw new Error(`rpcUrl not provided - check your environment variables`);
45+
46+
const provider = new ethers.providers.JsonRpcProvider(rpcUrl);
47+
48+
const wallet = new ethers.Wallet(privateKey);
49+
const signer = wallet.connect(provider); // create ethers signer for signing transactions
50+
51+
///////// START SIMULATION ////////////
52+
53+
console.log("Start simulation...");
54+
55+
const response = await simulateScript({
56+
source: source,
57+
args: args,
58+
bytesArgs: [], // bytesArgs - arguments can be encoded off-chain to bytes.
59+
secrets: {}, // no secrets in this example
60+
});
61+
62+
console.log("Simulation result", response);
63+
const errorString = response.errorString;
64+
if (errorString) {
65+
console.log(`❌ Error during simulation: `, errorString);
66+
} else {
67+
const returnType = ReturnType.int256;
68+
const responseBytesHexstring = response.responseBytesHexstring;
69+
if (ethers.utils.arrayify(responseBytesHexstring).length > 0) {
70+
const decodedResponse = decodeResult(
71+
response.responseBytesHexstring,
72+
returnType
73+
);
74+
console.log(`✅ Decoded response to ${returnType}: `, decodedResponse);
75+
}
76+
}
77+
78+
//////// ESTIMATE REQUEST COSTS ////////
79+
console.log("\nEstimate request costs...");
80+
// Initialize and return SubscriptionManager
81+
const subscriptionManager = new SubscriptionManager({
82+
signer: signer,
83+
linkTokenAddress: linkTokenAddress,
84+
functionsRouterAddress: routerAddress,
85+
});
86+
await subscriptionManager.initialize();
87+
88+
// estimate costs in Juels
89+
90+
const gasPriceWei = await signer.getGasPrice(); // get gasPrice in wei
91+
92+
const estimatedCostInJuels =
93+
await subscriptionManager.estimateFunctionsRequestCost({
94+
donId: donId, // ID of the DON to which the Functions request will be sent
95+
subscriptionId: subscriptionId, // Subscription ID
96+
callbackGasLimit: gasLimit, // Total gas used by the consumer contract's callback
97+
gasPriceWei: BigInt(gasPriceWei), // Gas price in gWei
98+
});
99+
100+
console.log(
101+
`Fulfillment cost estimated to ${ethers.utils.formatEther(
102+
estimatedCostInJuels
103+
)} LINK`
104+
);
105+
106+
//////// MAKE REQUEST ////////
107+
108+
console.log("\nMake request...");
109+
110+
const functionsConsumer = new ethers.Contract(
111+
consumerAddress,
112+
functionsConsumerAbi,
113+
signer
114+
);
115+
116+
// Actual transaction call
117+
const transaction = await functionsConsumer.sendRequest(
118+
source, // source
119+
"0x", // user hosted secrets - encryptedSecretsUrls - empty in this example
120+
0, // don hosted secrets - slot ID - empty in this example
121+
0, // don hosted secrets - version - empty in this example
122+
args,
123+
[], // bytesArgs - arguments can be encoded off-chain to bytes.
124+
subscriptionId,
125+
gasLimit,
126+
ethers.utils.formatBytes32String(donId) // jobId is bytes32 representation of donId
127+
);
128+
129+
// Log transaction details
130+
console.log(
131+
`\n✅ Functions request sent! Transaction hash ${transaction.hash}. Waiting for a response...`
132+
);
133+
134+
console.log(
135+
`See your request in the explorer ${explorerUrl}/tx/${transaction.hash}`
136+
);
137+
138+
const responseListener = new ResponseListener({
139+
provider: provider,
140+
functionsRouterAddress: routerAddress,
141+
}); // Instantiate a ResponseListener object to wait for fulfillment.
142+
(async () => {
143+
try {
144+
const response = await new Promise((resolve, reject) => {
145+
responseListener
146+
.listenForResponseFromTransaction(transaction.hash)
147+
.then((response) => {
148+
resolve(response); // Resolves once the request has been fulfilled.
149+
})
150+
.catch((error) => {
151+
reject(error); // Indicate that an error occurred while waiting for fulfillment.
152+
});
153+
});
154+
155+
const fulfillmentCode = response.fulfillmentCode;
156+
157+
if (fulfillmentCode === FulfillmentCode.FULFILLED) {
158+
console.log(
159+
`\n✅ Request ${
160+
response.requestId
161+
} successfully fulfilled. Cost is ${ethers.utils.formatEther(
162+
response.totalCostInJuels
163+
)} LINK.Complete reponse: `,
164+
response
165+
);
166+
} else if (fulfillmentCode === FulfillmentCode.USER_CALLBACK_ERROR) {
167+
console.log(
168+
`\n⚠️ Request ${
169+
response.requestId
170+
} fulfilled. However, the consumer contract callback failed. Cost is ${ethers.utils.formatEther(
171+
response.totalCostInJuels
172+
)} LINK.Complete reponse: `,
173+
response
174+
);
175+
} else {
176+
console.log(
177+
`\n❌ Request ${
178+
response.requestId
179+
} not fulfilled. Code: ${fulfillmentCode}. Cost is ${ethers.utils.formatEther(
180+
response.totalCostInJuels
181+
)} LINK.Complete reponse: `,
182+
response
183+
);
184+
}
185+
186+
const errorString = response.errorString;
187+
if (errorString) {
188+
console.log(`\n❌ Error during the execution: `, errorString);
189+
} else {
190+
const responseBytesHexstring = response.responseBytesHexstring;
191+
if (ethers.utils.arrayify(responseBytesHexstring).length > 0) {
192+
const decodedResponse = decodeResult(
193+
response.responseBytesHexstring,
194+
ReturnType.int256
195+
);
196+
console.log(
197+
`\n✅ Decoded response to ${ReturnType.int256}: `,
198+
decodedResponse
199+
);
200+
}
201+
}
202+
} catch (error) {
203+
console.error("Error listening for response:", error);
204+
}
205+
})();
206+
};
207+
208+
makeRequestMumbai().catch((e) => {
209+
console.error(e);
210+
process.exit(1);
211+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Imports
2+
const ethers = await import("npm:ethers");
3+
4+
// Constants
5+
const RPC_URL = "https://ethereum.publicnode.com";
6+
const CONTRACT_ADDRESS = "0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c";
7+
8+
// Data Feeds ABI
9+
const abi = [
10+
{
11+
inputs: [],
12+
name: "decimals",
13+
outputs: [{ internalType: "uint8", name: "", type: "uint8" }],
14+
stateMutability: "view",
15+
type: "function",
16+
},
17+
{
18+
inputs: [],
19+
name: "description",
20+
outputs: [{ internalType: "string", name: "", type: "string" }],
21+
stateMutability: "view",
22+
type: "function",
23+
},
24+
{
25+
inputs: [{ internalType: "uint80", name: "_roundId", type: "uint80" }],
26+
name: "getRoundData",
27+
outputs: [
28+
{ internalType: "uint80", name: "roundId", type: "uint80" },
29+
{ internalType: "int256", name: "answer", type: "int256" },
30+
{ internalType: "uint256", name: "startedAt", type: "uint256" },
31+
{ internalType: "uint256", name: "updatedAt", type: "uint256" },
32+
{ internalType: "uint80", name: "answeredInRound", type: "uint80" },
33+
],
34+
stateMutability: "view",
35+
type: "function",
36+
},
37+
{
38+
inputs: [],
39+
name: "latestRoundData",
40+
outputs: [
41+
{ internalType: "uint80", name: "roundId", type: "uint80" },
42+
{ internalType: "int256", name: "answer", type: "int256" },
43+
{ internalType: "uint256", name: "startedAt", type: "uint256" },
44+
{ internalType: "uint256", name: "updatedAt", type: "uint256" },
45+
{ internalType: "uint80", name: "answeredInRound", type: "uint80" },
46+
],
47+
stateMutability: "view",
48+
type: "function",
49+
},
50+
{
51+
inputs: [],
52+
name: "version",
53+
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
54+
stateMutability: "view",
55+
type: "function",
56+
},
57+
];
58+
59+
// Chainlink Functions compatible Ethers JSON RPC provider class
60+
// (this is required for making Ethers RPC calls with Chainlink Functions)
61+
class FunctionsJsonRpcProvider extends ethers.JsonRpcProvider {
62+
constructor(url) {
63+
super(url);
64+
this.url = url;
65+
}
66+
async _send(payload) {
67+
let resp = await fetch(this.url, {
68+
method: "POST",
69+
headers: { "Content-Type": "application/json" },
70+
body: JSON.stringify(payload),
71+
});
72+
return resp.json();
73+
}
74+
}
75+
76+
const provider = new FunctionsJsonRpcProvider(RPC_URL);
77+
const dataFeedContract = new ethers.Contract(CONTRACT_ADDRESS, abi, provider);
78+
const dataFeedResponse = await dataFeedContract.latestRoundData();
79+
80+
console.log(`Fetched BTC / USD price: ` + dataFeedResponse.answer);
81+
82+
// return result
83+
return Functions.encodeInt256(dataFeedResponse.answer);

functions-examples/package-lock.json

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

functions-examples/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"license": "ISC",
1111
"dependencies": {
1212
"@chainlink/env-enc": "^1.0.5",
13-
"@chainlink/functions-toolkit": "^0.2.6",
13+
"@chainlink/functions-toolkit": "^0.2.7",
1414
"ethers": "^5.7.2"
1515
}
1616
}

0 commit comments

Comments
 (0)