This is a zero-to-one tutorial series that will teach you how to develop Solana smart contracts from the very basics.
We will start with the most fundamental operations to learn Solana smart-contract development. A general programming background and understanding of object-oriented concepts is enough—you do not need prior experience with smart contracts on other networks, nor with Rust itself.
Follow the official Solana installation guide: https://solana.com/docs/intro/installation
The documentation provides both a one-liner to install all dependencies and step-by-step instructions. Note that Solana CLI requires an update to your shell’s environment variables. After everything is installed, the solana command should be available:
solana --help
Use the anchor command to scaffold a smart-contract project. The tool was installed in the previous step. Don’t worry yet about the generated folder structure:
anchor init hello_sol
cd hello_sol
Inside programs/hello_sol/src you will find a lib.rs file. The .rs extension means this is a Rust source file. Copy the following code into it. Important: the value inside declare_id! is generated automatically when you initialise your own project—do not copy the string below verbatim.
use anchor_lang::prelude::*;
declare_id!("3Zbdw1oWu1CiMiQr3moQeT4XzMgeqmCvjH5R5wroDWQH");
#[program]
pub mod hello_sol {
use super::*;
pub fn say_hello(ctx: Context<Hello>) -> Result<()> {
msg!("Hello, world!");
Ok(())
}
}
#[derive(Accounts)]
pub struct Hello {}
Compile the contract you just wrote with the anchor command. If compilation succeeds there should be no errors. Rust is very strict and may emit long warnings—those are fine.
anchor build
Point the local solana CLI to devnet, which is intended for developers and allows you to test without paying real SOL:
solana config set --url https://api.devnet.solana.com
Generate a Solana account that will pay deployment fees:
solana-keygen new -o ~/.config/solana/id.json
The command outputs a line starting with pubkey:—that is your local account address. Because you already set devnet as the default cluster, you can check the balance directly:
solana balance
You can also open the devnet explorer and search for your address. The resulting URL looks like:
https://explorer.solana.com/address/75sFifxBt7zw1YrDfCdPjDCGDyKEqLWrBarPCLg6PHwb?cluster=devnet
You will, of course, see a balance of 0 SOL.
Airdrop yourself 2 SOL (the maximum per request):
solana airdrop 2
With the code compiled and your account funded, deploy the program:
anchor deploy --provider.cluster devnet
On success you’ll see Deploy success. Note the Program Id: in the output—that is the on-chain address of your contract, e.g.:
https://explorer.solana.com/address/3Zbdw1oWu1CiMiQr3moQeT4XzMgeqmCvjH5R5wroDWQH?cluster=devnet
In hello_sol/app, create a file named app.js and paste the following code. In short, the script loads your local keypair and sends a transaction that invokes the say_hello method once per run.
const anchor = require('@coral-xyz/anchor');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { Keypair, Connection } = anchor.web3;
const RPC_URL = process.env.RPC_URL;
const connection = new Connection(RPC_URL, { commitment: 'confirmed' });
const secretKey = Uint8Array.from(
JSON.parse(
fs.readFileSync(
path.join(os.homedir(), '.config/solana/id.json'),
'utf8',
),
),
);
const wallet = new anchor.Wallet(Keypair.fromSecretKey(secretKey));
const provider = new anchor.AnchorProvider(connection, wallet, {
preflightCommitment: 'confirmed',
});
anchor.setProvider(provider);
const idlPath = path.resolve(__dirname, '../target/idl/hello_sol.json');
const idl = JSON.parse(fs.readFileSync(idlPath, 'utf8'));
const program = new anchor.Program(idl, provider);
(async () => {
try {
const sig = await program.methods.sayHello().rpc();
console.log('✅ tx', sig);
console.log(`🌐 https://explorer.solana.com/tx/${sig}?cluster=devnet`);
} catch (err) {
console.error('❌', err);
}
})();
Back in the project root, install the Node.js dependencies:
npm init -y
npm install @coral-xyz/anchor
Now run the script (still from the root folder). It will invoke your deployed program on devnet:
export RPC_URL=https://api.devnet.solana.com
node app/app.js
Because Node.js does not honour system proxies by default, you may need a faster RPC endpoint in regions with limited connectivity. Services like Helius offer free accounts. If you encounter an error like the following, it is almost certainly a network issue—change to a more reliable RPC URL:
❌ Error: failed to get recent blockhash: TypeError: fetch failed
at Connection.getLatestBlockhash (/Users/smallyu/work/github/hello_sol/node_modules/@solana/web3.js/lib/index.cjs.js:7236:13)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async AnchorProvider.sendAndConfirm (/Users/smallyu/work/github/hello_sol/node_modules/@coral-xyz/anchor/dist/cjs/provider.js:89:35)
at async MethodsBuilder.rpc [as _rpcFn] (/Users/smallyu/work/github/hello_sol/node_modules/@coral-xyz/anchor/dist/cjs/program/namespace/rpc.js:15:24)
at async /Users/smallyu/work/github/hello_sol/app/app.js:40:17
You might wonder why the contract address was never specified. Look at the idlPath variable: target/idl/hello_sol.json contains the program’s IDL, which in turn includes the program ID. The address is generated offline during compilation—it doesn’t need to be on-chain to exist.
If the script runs without errors, your terminal will print the transaction hash and a clickable URL, e.g.:
At the bottom of that explorer page you can see the log line Program logged: "Hello, world!", which comes straight from the msg! call in your contract.
If you encounter errors, the most common cause is version mismatch. Blockchain tooling evolves rapidly, and incompatibilities appear often. My local environment is:
rustup: rustup 1.28.2 (e4f3ad6f8 2025-04-28)
rustc: rustc 1.90.0-nightly (706f244db 2025-06-23)
solana: solana-cli 2.2.18 (src:8392f753; feat:3073396398, client:Agave)
anchor: anchor-cli 0.31.1
node: v24.2.0
@coral-xyz/anchor (npm): ^0.31.1