diff --git a/README.md b/README.md index 2713809..85f19e9 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,14 @@ [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/smartcontractkit/solana-starter-kit) - # Chainlink Solana Starter Kit + The Chainlink Solana Starter Kit is an [Anchor](https://project-serum.github.io/anchor/getting-started/introduction.html) based program and client that shows developers how to use and interact with [Chainlink Price Feeds on Solana](https://docs.chain.link/solana/). The demo is configured to run on the [Devnet cluster](https://docs.solana.com/clusters#devnet), and is comprised of an on-chain program written in Rust, and an off-chain client written in JavaScript. The program takes parameters and account information from the off-chain client, retrieves the latest price data from the specified Chainlink Price Feed on Devnet, then writes the data out to the specified account, which can then be read by the off-chain client. ## Running the example on Devnet ### Requirements + - [NodeJS 12](https://nodejs.org/en/download/) or higher - [Rust](https://www.rust-lang.org/tools/install) - [Solana CLI](https://docs.solanalabs.com/cli/install) @@ -41,14 +42,13 @@ npm install cargo install --git https://github.com/coral-xyz/anchor --tag v0.30.1 anchor-cli --locked ``` - Next, generate a new wallet: ``` solana-keygen new -o id.json ``` -You should see the public key in the terminal output. Alternatively, you can find the public key with the following CLI command: +You should see the public key in the terminal output. Alternatively, you can find the public key with the following CLI command: ``` solana-keygen pubkey id.json @@ -67,10 +67,13 @@ anchor build ``` The build process generates the keypair for your program's account. Before you deploy your program, you must update this public key in this line `lib.rs` file. + ``` declare_id!("GEgDWT7Cc8H5S1o2YnTp3umiciazQj5fKbftPXkc2TsL"); -``` +``` + To do this, you need to run the command below: + ``` anchor keys sync ``` @@ -107,13 +110,38 @@ export ANCHOR_PROVIDER_URL='https://api.devnet.solana.com' export ANCHOR_WALLET='./id.json' ``` -Now you are ready to run the JavaScript client. Be sure to pass Chainlink data feed address that you want to query. This can be taken from the [Chainlink Solana Data Feeds page](https://docs.chain.link/docs/solana/data-feeds-solana/), and the value will be defaulted to the Devnet SOL/USD feed address if you don’t specify a value. In this example, we specified the ETH/USD feed: +Now you are ready to run the client. You can choose between the JavaScript version or the TypeScript version. + +Be sure to pass Chainlink data feed address that you want to query. This can be taken from the [Chainlink Solana Data Feeds page](https://docs.chain.link/docs/solana/data-feeds-solana/), and the value will be defaulted to the Devnet SOL/USD feed address if you don’t specify a value. In this example, we specified the ETH/USD feed: + +#### JavaScript Client + +To run the JavaScript client, be sure to pass the Chainlink data feed address that you want to query. You can get the feed address from the [Chainlink Solana Data Feeds page](https://docs.chain.link/docs/solana/data-feeds-solana). If you don’t specify a feed address, the default will be the Devnet SOL/USD feed. In this example, we specify the ETH/USD feed: + +``` +node client.js \ + --program $(solana address -k ./target/deploy/chainlink_solana_demo-keypair.json) \ + --feed 669U43LNHx7LsVj95uYksnhXUfWKDsdzVqev3V4Jpw3P +``` + +#### TypeScript Client + +To run the TypeScript client, you'll need to have ts-node installed. To run it, use the following command: ``` -node client.js --feed 669U43LNHx7LsVj95uYksnhXUfWKDsdzVqev3V4Jpw3P +npx ts-node client.ts \ + --program $(solana address -k ./target/deploy/chainlink_solana_demo-keypair.json) \ + --feed 669U43LNHx7LsVj95uYksnhXUfWKDsdzVqev3V4Jpw3P ``` -The client will generate a new account and pass it to the deployed program, which will then populate the account with the current price from the specified price feed. The client will then read the price from the account, and output the value to the console. +#### What Happens Next? +The client will generate a new account and pass it to the deployed program. + +The program will then populate the account with the current price from the specified price feed. + +The client will read the price from the account and output the value to the console. + +Both the JavaScript and TypeScript clients will function the same way, so you can choose the version that fits your development environment. ``` Running client... @@ -160,7 +188,6 @@ export ANCHOR_WALLET='./id.json' Next, you can set the value of the `CHAINLINK_FEED_ADDRESS` static variable to the value of the [Price Feed account address](https://docs.chain.link/docs/solana/data-feeds-solana/) that you wish to query. This example queries the ETH/USD feed on Devnet: - ``` const CHAINLINK_FEED_ADDRESS="669U43LNHx7LsVj95uYksnhXUfWKDsdzVqev3V4Jpw3P" ``` @@ -174,6 +201,7 @@ npm run read-data ``` JavaScript: + ``` node read-data.js ``` @@ -192,7 +220,6 @@ pappas99@Pappas solana-starter-kit % npm run read-data 301331000000 ``` - ### Testing You can execute the [integration test](./tests/chainlink-solana-demo-int-test.ts) with the following command diff --git a/client.ts b/client.ts new file mode 100644 index 0000000..6581f7d --- /dev/null +++ b/client.ts @@ -0,0 +1,57 @@ +import * as anchor from "@coral-xyz/anchor"; +import minimist from "minimist"; + +// Set up provider and program +anchor.setProvider(anchor.AnchorProvider.env()); +const provider = anchor.getProvider(); +const program = anchor.workspace.ChainlinkSolanaDemo; + +const CHAINLINK_PROGRAM_ID = "HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny"; +const DIVISOR = 100000000; +const DEFAULT_FEED = "99B2bTijsU6f1GCT73HmdR7HCFFjGMBcPZY6jZ96ynrR"; // SOL/USD +const args = minimist(process.argv.slice(2)); +const CHAINLINK_FEED: string = args["feed"] || DEFAULT_FEED; + +async function main(): Promise { + console.log(`Interacting with program: ${program.programId}`); + + const priceFeedAccount = anchor.web3.Keypair.generate(); + console.log(`Generated account: ${priceFeedAccount.publicKey}`); + + try { + // Execute the RPC + const txSignature = await program.methods + .execute() + .accounts({ + decimal: priceFeedAccount.publicKey, + chainlinkFeed: CHAINLINK_FEED, + chainlinkProgram: CHAINLINK_PROGRAM_ID, + }) + .signers([priceFeedAccount]) + .rpc(); + + console.log(`Transaction Signature: ${txSignature}`); + + // Fetch and log transaction details + const txDetails = await provider.connection.getTransaction(txSignature, { + commitment: "confirmed", + }); + + const txLogs = txDetails?.meta?.logMessages ?? []; + console.log("Transaction Logs: ", txLogs); + + // Fetch and log price data + const latestPrice = await program.account.decimal.fetch( + priceFeedAccount.publicKey + ); + + console.log(`Price: ${(latestPrice.value / DIVISOR).toFixed(8)}`); + } catch (error) { + console.error("Error running the program:", error); + } +} + +console.log("Running client..."); +main() + .then(() => console.log("Success")) + .catch(console.error); diff --git a/package-lock.json b/package-lock.json index a905e83..6cb2930 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,9 +7,10 @@ "dependencies": { "@chainlink/solana-sdk": "^0.2.2", "@coral-xyz/anchor": "^0.30.1", + "@types/node": "^22.14.1", "bn.js": "^5.2.0", "borsh": "^0.7.0", - "typescript": "^5.5.4" + "typescript": "^5.8.3" }, "devDependencies": { "@solana/web3.js": "^1.95.2", @@ -474,9 +475,13 @@ "dev": true }, "node_modules/@types/node": { - "version": "17.0.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.25.tgz", - "integrity": "sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==" + "version": "22.14.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", + "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } }, "node_modules/@types/uuid": { "version": "8.3.4", @@ -2073,9 +2078,10 @@ } }, "node_modules/typescript": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", - "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -2084,6 +2090,12 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, "node_modules/utf-8-validate": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", diff --git a/package.json b/package.json index 8380ab5..a3571d0 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,10 @@ "dependencies": { "@chainlink/solana-sdk": "^0.2.2", "@coral-xyz/anchor": "^0.30.1", + "@types/node": "^22.14.1", "bn.js": "^5.2.0", "borsh": "^0.7.0", - "typescript": "^5.5.4" + "typescript": "^5.8.3" }, "devDependencies": { "@solana/web3.js": "^1.95.2", diff --git a/yarn.lock b/yarn.lock index a9e76e5..4c12c6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -210,10 +210,12 @@ resolved "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz" integrity sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg== -"@types/node@*": - version "17.0.25" - resolved "https://registry.npmjs.org/@types/node/-/node-17.0.25.tgz" - integrity sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w== +"@types/node@*", "@types/node@^22.14.1": + version "22.14.1" + resolved "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz" + integrity sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw== + dependencies: + undici-types "~6.21.0" "@types/node@^12.12.54": version "12.20.55" @@ -1288,10 +1290,15 @@ type-detect@^4.0.0, type-detect@^4.0.5: resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -typescript@^5.5.4: - version "5.5.4" - resolved "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz" - integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== +typescript@^5.8.3: + version "5.8.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz" + integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== utf-8-validate@^5.0.2, utf-8-validate@>=5.0.2: version "5.0.10"