A playground for writing, compiling, testing smart contracts on Solana chain(s): Mainnet, Testnet, Devnet.
- Anchor lang for writing smart contracts on Solana.
- Rust eDSL for writing safe, secure, and high level Solana programs
- IDL ~ ABI
The following is for macOS M1 & also for Lima VM run on top of macOS M1.
- Issue for macOS M1: not able to run
solana-test-validator
. Solved via Troubleshoot #3.- Issue for lima VM: Still unsuccessful with
solana
installation. Tried with source. getting this error:No package 'libudev' found
(TODO: fix this)
macOS M1 ✅ | lima VM ✅
Follow this
macOS M1 ✅ | lima VM
This is for compiling solana contracts/programs.
Install/Update using brew
$ brew install solana
update solana-install
❯ solana-install update
Install is up to date. 1.10.40 is the latest compatible release
❯ solana --version
solana-cli 1.13.4 (src:devbuild; feat:4011803773)
Here, the problem is that the solana-test-validator
fails on console. Hence, it is recommended to install using source via following Troubleshooting #3.
Uninstall:
$ brew uninstall solana
-
Download specific version's binaries with name:
solana-release-aarch64-apple-darwin.tar.bz2
from releases page into home directory i.e.~
-
Then, extract the folder via double-click the folder, into home directory. While writing this doc, it's
1.13.3 version
. -
Now, rename the folder from
solana-releases
tosolana-1.13.3
. No need tocargo build
as it's already built. Just link the binaries toPATH
. -
Add the following to
~/.zprofile
:export PATH="$HOME/solana-1.13.3/bin:$PATH"
-
Activate the changes:
$ source ~/.zprofile
-
Check the version:
In case of access denied to the binary CLI command execution, just go to Finder App & right click on the binary file & click on
Open With Terminal
option. Then, it will ask for the permission to execute the binary file. Click onOpen
option & then it will work.❯ solana -V solana-cli 1.13.3 (src:3271b83d; feat:4011803773)
-
Check for
solana-test-validator
if running properly:❯ solana-test-validator
If you get this error:
Error: 1:19521 Illegal hardware instruction (core dumped)
, then follow Troubleshooting #3.
In case of any update, if needed just follow the steps from #1
to #6
. Don't follow $ solana-install update
as it's meaningless.
# I am using 1.13.3 version already. So, it's not updating.
❯ solana-install update
Install is up to date. 1.10.40 is the latest compatible release
macOS M1 ✅ | lima VM ✅
This is for writing unit tests using Javascript or Typescript.
nvm
node
npm
yarn
Check the installation steps here.
macOS M1 ✅ | lima VM ✅
This is similar to Hardhat/Truffle/Foundry/Brownie (for EVM contracts). Know more
Don't follow
npm
to installanchor-cli
i.e. don't use this command:$ npm install -g @project-serum/anchor-cli
on macOS. Otherwise you will get this error:
❯ anchor
Only x86_64 / Linux distributed in NPM package right now.
Trying globally installed anchor.
Following is true for both macOS & linux VM like lima.
Hence, install using avm
. Anchor version manager (avm
) is a tool for using multiple versions of the anchor-cli
.
It's better to use avm
to manage multiple versions of Anchor
.
$ cargo install --git https://github.com/coral-xyz/anchor avm --locked --force
To use latest version, try this:
$ avm install latest
$ avm use latest
OR
avm update
In case of any issue for Ubuntu, do this first:
$ sudo apt-get update && sudo apt-get upgrade && sudo apt-get install -y pkg-config build-essential libudev-dev libssl-dev
-
Start a project
$ anchor init hello-solana
-
write the code in
src/lib.rs
& add supported filesconstant.rs
error.rs
states.rs
-
Build:
$ anchor build
-
Get the program address during build:
$ solana address -k <KEYPAIR-FILE-PATH>
e.g.$ solana address -k target/deploy/hello_solana-keypair.json
❯ solana address -k target/deploy/hello_solana-keypair.json ⏎ BTc32GfyocV5yZvSqvLvyefkLftyHfG92Sxao2KaLqiD
-
Now, add the address into
Anchor.toml
file & also insrc/lib.rs
indeclare_id!()
macro. -
Check sufficient SOL/lamports in the deployer (default keypair is
~/.config/solana/id.json
) account:$ solana balance
:- If not, then fund the account:
$ solana airdrop 1
, for localnet it's not needed though. - Check balance again:
$ solana balance
❯ solana balance ⏎ 500000000 SOL
- If not, then fund the account:
-
For localnet (ensure
$ solana config get
-> connected to localnet), deploy:$ anchor deploy
or$ solana program deploy target/deploy/hello_world-keypair.json
Run
solana-test-validator
in another terminal.Using
anchor-cli
:❯ anchor deploy ⏎ Deploying workspace: http://localhost:8899 Upgrade authority: /Users/abhi3700/.config/solana/id.json Deploying program "hello-solana"... Program path: /Users/abhi3700/F/coding/github_repos/sol-playground/sc/hello-solana/target/deploy/hello_solana.so... Program Id: BTc32GfyocV5yZvSqvLvyefkLftyHfG92Sxao2KaLqiD
OR
Using
solana-cli
:❯ solana program deploy target/deploy/hello_solana.so ⏎ Program Id: BTc32GfyocV5yZvSqvLvyefkLftyHfG92Sxao2KaLqiD
View the program details:
❯ solana program show BTc32GfyocV5yZvSqvLvyefkLftyHfG92Sxao2KaLqiD Program Id: BTc32GfyocV5yZvSqvLvyefkLftyHfG92Sxao2KaLqiD Owner: BPFLoaderUpgradeab1e11111111111111111111111 ProgramData Address: DR6WL7PQRepVzw7q772gZ9tf8CQGmhcrc8BX2vfgabyp Authority: HTeVsf7bg3EuAKKpg74p4soHVoxJAYCSmK2PBJZt8Dyu Last Deployed In Slot: 87 Data Length: 356368 (0x57010) bytes Balance: 2.48152536 SOL
-
Test:
$ anchor test
-
Upgrade (if required).
❯ solana program deploy target/deploy/hello_solana.so Program Id: BTc32GfyocV5yZvSqvLvyefkLftyHfG92Sxao2KaLqiD
View the program details:
❯ solana program show BTc32GfyocV5yZvSqvLvyefkLftyHfG92Sxao2KaLqiD Program Id: BTc32GfyocV5yZvSqvLvyefkLftyHfG92Sxao2KaLqiD Owner: BPFLoaderUpgradeab1e11111111111111111111111 ProgramData Address: DR6WL7PQRepVzw7q772gZ9tf8CQGmhcrc8BX2vfgabyp Authority: HTeVsf7bg3EuAKKpg74p4soHVoxJAYCSmK2PBJZt8Dyu Last Deployed In Slot: 8360 Data Length: 356368 (0x57010) bytes Balance: 2.48152536 SOL
- Greeting contract
- About: It's a simple program, all it does is increment a number every time it's called.
- Here, fetch the greeting account's
counter
attribute >> increment by 1 >> store it back >> log the stored value
- Serializer/Deserializer:
Bosch
(also used in Near blockchain).
-
In Solana native rust code, we have to parse
program_id
as argument:fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], _instruction_data: &[u8] ) -> ProgramResult { let accounts_iter = &mut accounts.iter(); // The account that signs the transaction and pays for it let payer_account = next_account_info(accounts_iter)?; // The account in which we store the counter let counter_account = next_account_info(accounts_iter)?; if counter_account.owner != program_id { msg!("Counter account does not have the correct program id"); return Err(ProgramError::IncorrectProgramId); } let mut counter_data = counter_account.try_borrow_mut_data()?; let mut counter = u32::from_le_bytes([counter_data[0], counter_data[1], counter_data[2], counter_data[3]]); counter += 1; counter_data[0..4].copy_from_slice(&counter.to_le_bytes()); msg!("Incremented counter to {}", counter); Ok(()) }
Code explanation:
In this code, we first get the payer account and then the counter account from the accounts slice. We then check if the counter account has the correct owner, which should be the program id of the current program. If it does not, we return an error.
Next, we borrow the counter account's data mutably and interpret the first four bytes as a
u32
integer, which is our counter. We increment the counter and then write its new value back to the counter account's data.To interact with this program, you would need to create a new account with this program's id as the owner and pass this account to the program as the second account in the accounts slice. You would also need to sign the transaction with the payer account, which is the first account in the accounts slice.
This is a very basic example and does not include error handling or checks for the payer account. In a real-world scenario, you would want to include checks to ensure that the payer account has enough SOL to pay for the transaction and that the counter account has enough space for the counter.
Here, the
program_id
is parsed to check with the data account owner (index: 1 i.e.accounts[1]
).So, we won't get the address from within the function as the code is not deployed yet when the code was being written.
Also there is no way to get the caller's address unlike
msg.sender
inSolidity
. We have to parse even that address (payer's address) as argument.As per the Solana program architecture, the program & data sits at 2 different accounts. Hence, we need to validate the
program_id
withdata_account.owner
so that the function can execute its logic & then we can then store the data (if we want to) into the data account. -
the account variable can only be edited if the account's owner public key matches with the
program_id
if (account.owner == program_id) {
//the variable can be edited.
}
To detect whether an address is a program, just check the account info (fetched from outside the SC) is not NULL
or check if the program_id
has is_executable
as true
(can be done from inside/outside the SC).
-
When we write a program, we should add function/instruction & then corresponding Account (in form of struct) & then test.
-
Add
#[program]
attribute to the mod, which contains all the functions/instruction -
The prelude contains all commonly used components of the crate. All programs should include it via
anchor_lang::prelude::*;
-
Get program id from inside the code:
msg!("The Program id: {}", crate::ID);
Every function usually comes with a struct like this:
L-10:14 & L-18: shows that.
The #[event]
attributed event can be defined like this:
#[event]
pub struct Transfer {
#[index]
pub from: Pubkey,
#[index]
pub to: Pubkey,
pub amount: u128,
}
-
#[error]
attribute is used to define custom errors like this:#[error_code] pub enum TodoError { #[msg("You are not authorized to perform this action.")] Unauthorized, #[msg("Not allowed")] NotAllowed, #[msg("Math operation overflow")] MathOverflow, #[msg("Already marked")] AlreadyMarked, }
In solidity, if we would have written like this:
struct Todo {
string content;
bool marked;
}
mapping(address => Todo[]) public todos;
Unlike solidity, we can't use mapping
in case of Solana, but rather we use the authority pattern like this:
#[account]
#[derive(Default)]
pub struct UserProfile {
pub authority: Pubkey,
pub last_todo: u8,
pub todo_count: u8,
}
#[account]
#[derive(Default)]
pub struct TodoAccount {
pub authority: Pubkey,
pub idx: u8,
pub content: String,
pub marked: bool,
}
Imagine a person maintaining TODOs on Solana, the architecture would be like this:
Here, a person would have 1 yellow colored account (todo tracker) & multiple orange colored accounts (todo item) for different TODOs.
Here, in mass, each person would have 1 yellow colored account (todo tracker) & multiple orange colored accounts (todo item) for different TODOs.
One can get the struct (#[account]
) properties size by doing manually via referring this
#[derive(Accounts)]
pub struct InitializeUser<'info> {
#[account(mut)]
authority: Signer<'info>,
#[account(
init,
seeds = [USER_TAG, authority.key().as_ref()],
bump,
payer = authority,
// 8 (First 8 bytes are default account discriminator) + max_size(UserProfile)
space = 8 + std::mem::size_of::<UserProfile>()
)]
pub user_profile: Box<Account<'info, UserProfile>>,
pub system_program: Program<'info, System>, // it's needed
}
All the logs can be viewed in .anchor/
folder created during $ anchor test
command like this:
Streaming transaction logs mentioning BTc32GfyocV5yZvSqvLvyefkLftyHfG92Sxao2KaLqiD. Confirmed commitment
Transaction executed in slot 3:
Signature: 66UGGJsbWAfuHhdWgvdHYVg4D6HsdhacEMVEzcAvy14qmb9vxwsGZoAdkrf6ogi2p8XZkAVHNf4jAA1XfuN99Q4w
Status: Ok
Log Messages:
Program BTc32GfyocV5yZvSqvLvyefkLftyHfG92Sxao2KaLqiD invoke [1]
Program log: Instruction: Initialize
Program 11111111111111111111111111111111 invoke [2]
Program 11111111111111111111111111111111 success
Program 11111111111111111111111111111111 invoke [2]
Program 11111111111111111111111111111111 success
Program log: The Program id: BTc32GfyocV5yZvSqvLvyefkLftyHfG92Sxao2KaLqiD
Program BTc32GfyocV5yZvSqvLvyefkLftyHfG92Sxao2KaLqiD consumed 21006 of 200000 compute units
Program BTc32GfyocV5yZvSqvLvyefkLftyHfG92Sxao2KaLqiD success
for the Solana program instruction defined like this:
pub fn initialize(ctx: Context<Initialize>, new_name: String) -> Result<()> {
let my_data = &mut ctx.accounts.data;
my_data.name = new_name;
msg!("The Program id: {}", crate::ID);
Ok(())
}
-
A program's instruction (defined in snake_case) like
send_tweet
is converted into camelCase likesendTweet
in the test file, like this:it("Send tweet", async () => { await program.rpc.sendTweet(); });
- Owner check
- Owner signer
- Tool:
Soteria
source
- Gelato (transaction scheduling) like tool: Clockwork
Developers can use Clockwork to schedule transactions and automate smart-contracts without relying on centralized infrastructure. This allows developers to build more complex and robust applications on Solana.
- Remix like IDE for Solana - https://beta.solpg.io/
- Cause: This is because of exceeding 5 tokens as airdrop.
- Solution: try <=5 tokens as airdrop
$ solana airdrop 5
2. Error: Account 4aUirUHybwAmuEJPorfeWeWNk4nTgujAkPo2aodNvTv6 has insufficient funds for spend (1.21953816 SOL) + fee (0.000885 SOL)
- Cause: This is because of insufficient fund in the program deployer account.
- Solution:Airdrop some tokens (a min. of
1.22042316 SOL
)$ solana airdrop 5 4aUirUHybwAmuEJPorfeWeWNk4nTgujAkPo2aodNvTv6
or$ solana airdrop 5
-
Cause: This happens on Mac M1 processor
-
Solution: Uninstall Solana, Rust & then install from scratch using the following steps shown here
- Make sure that "Open using Rosetta" is disabled in the terminal
- Open Finder & search for "Terminal"
- Right click on "Terminal" App & click "Get info"
- Ensure that the "Open using Rosetta" option is diabled.
- Uninstall Solana:
$ rm -rf /Users/abhi3700/.local/share/solana/
- Uninstall Rust:
$ rustup self uninstall
- Setup Rosetta:
$ /usr/sbin/softwareupdate --install-rosetta --agree-to-license
- Now, we will create duplicate copy of "Terminal" App (search in finder)
- Name it as "Terminal Rosetta"
- Make sure the "Open using Rosetta" option is enabled.
- Now, use "Terminal Rosetta" from hereon. [OPTIONAL] Make the background color to something else by clicking cmd+i on opened terminal to make it look different.
- Install Rust, Cargo:
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
- Install Homebrew using the x86 instruction set. Note the prefix used
arch -x86_64
:$ arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
- Also install OpenSSL in x86 instruction set, but get error like this:
// install openssl inside intel processor $ arch -x86_64 brew install [email protected] Error: Cannot install under Rosetta 2 in ARM default prefix (/opt/homebrew)! To rerun under ARM use: arch -arm64 brew install ... To install under x86_64, install Homebrew into /usr/local.
- Make sure that "Open using Rosetta" is disabled in the terminal
Then tried doing under ARM and it was success.
$ arch -arm64 brew install [email protected]
- Create a new file via
$ touch ~/.cargo/config
and copy paste this:
[target.x86_64-apple-darwin]
rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]
[target.aarch64-apple-darwin]
rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]
- [For UPDATE, start from this step] Now, clone solana from source via
$ git clone https://github.com/solana-labs/solana.git
. NOTE: Do it in the home directory & then won't be deleted by mistake.- first download the
solana-release-aarch64-apple-darwin.tar.bz2
file from here into home directory i.e./Users/abhi3700/
- Then, extract the folder via double-click into home directory. While writing, it's
1.13.3 version
. - Now, get
solana-1.14.7
folder fromsolana-1.14.7.tar.gz
. You can delete thetar.gz
file. - More to the folder:
$ cd solana-1.14.7
- first download the
- Build
$ cargo build
- Install coreutils
$ arch -arm64 brew install coreutils
- Install script to generate binaries into
./bin
folder. (takes1123 seconds
)
$ ./scripts/cargo-install-all.sh .
- Add the binaries folder into the PATH.
Skip step
#11
to#13
if already available with binary. In case of access denied to the binary CLI command execution, just go to Finder App & right click on the binary file & click onOpen With Terminal
option. Then, it will ask for the permission to execute the binary file. Click onOpen
option & then it will work.
// open .zprofile in VSC editor
$ code ~/.zprofile
// Add this line to EOL
export PATH="/Users/abhi3700/solana-1.14.7"/bin:"$PATH"
// activate command
$ source ~/.zprofile
NOTE: For multiple versions just open
~/.zprofile
file & change the version via commenting the previous version. Also, make sure that the path exist. Hence, it looks like this:
# export PATH="/Users/abhi3700/solana-1.8.0"/bin:"$PATH"
# export PATH="/Users/abhi3700/solana-1.8.5"/bin:"$PATH"
# export PATH="/Users/abhi3700/solana-1.9.4"/bin:"$PATH"
export PATH="/Users/abhi3700/solana-1.14.7"/bin:"$PATH"
- Run the commands like
solana
,solana-test-validator
. NOTE: all the blocks will be stored intest-ledger/
[Better to delete after the localnet running is done]. To shutdown this, press ctrl+c and then restart from the stopped block.
❯ solana-test-validator ⏎
Ledger location: test-ledger
Log: test-ledger/validator.log
Identity: 3RvvwAbhmFDeF8n9SgMKKTyphDev3s9Gx6mefR65o19N
Genesis Hash: DrFFgvyNjJXgfRBgPDcTgQ7WmyFE2BkX1aRK5s8twrod
Version: 1.14.7
Shred Version: 62237
Gossip Address: 127.0.0.1:1024
TPU Address: 127.0.0.1:1027
JSON RPC URL: http://127.0.0.1:8899
⠄ 00:00:10 | Processed Slot: 19 | Confirmed Slot: 19 | Finalized Slot: 0 | Snaps
- Now, during Anchor
build
might occur an issue related tobpf
folder does not exist as thesolana
has been installed from source. So, follow "Error-4" for doing the additional step of copyingsdk/bpf/
folder into~/.cargo/bin/
.
4. Error: BPF SDK path does not exist: /Users/abhi3700/.cargo/bin/sdk/bpf: No such file or directory (os error 2)
- Cause: This happens during
$ anchor build
. This error occurs as thesolana
has been installed from source. - Solution: Just copy
~/solana-1.14.7/bin/sdk
to here:~/.cargo/bin/
. Note: there might besdk
shortcut. Just replace this with thesdk
folder containingbpf/
. Then it would build successfully.
- Error:
Error: Custom: Invalid blockhash
There was a problem deploying: Output { status: ExitStatus(unix_wait_status(256)), stdout: "", stderr: "" }.
- Cause:
- It could be due to
solana-cli
conflict. - It could be because the new
program_id
generated intotarget/deploy/
is put into thedeclare_id
,Anchor.toml
.
- It could be due to
- Solution: Check the 2 things above.
- try with
solana-1.8.0
, if not working withsolana-1.9.4
,
- try with
solana-cli 1.8.0 (src:4a8ff62a; feat:1813598585)
anchor-cli 0.20.1
rustc 1.57.0 (f1edd0429 2021-11-29)
6. Error: failed to get recent blockhash: FetchError: request to http://localhost:8899/ failed, reason: connect ECONNREFUSED 127.0.0.1:8899
- Cause: This happens when the
solana-test-validator
is not running during theanchor run test
.This is because of usage of
anchor.setProvider(anchor.AnchorProvider.env());
in thetest.ts
file. - Solution: Just run the
solana-test-validator
in another terminal & then run theanchor run test
.
7. Error: failed to send transaction: Transaction simulation failed: Attempt to load a program that does not exist
- Cause: This happens when the
solana-test-validator
is running & the program is not loaded/deployed. - Solution: Just run the
anchor deploy
(changeAnchor.toml
,src/lib.rs
) & then run theanchor run test
.
8. Error: AnchorError occurred. Error Code: DeclaredProgramIdMismatch. Error Number: 4100. Error Message: The declared program id does not match the actual program id.
- Cause: This happens when the
program_id
generated intotarget/deploy/
is not put into thedeclare_id
,Anchor.toml
. - Solution: Just copy the
program_id
fromtarget/deploy/
& put it into thedeclare_id
,Anchor.toml
. & re-deploy after starting the ledger from freshsolana-test-validator --reset
.
Also, check the available program id:
anchor keys list
- Cause: This happens when the
tweet
account is not provided with account constraint in thesrc/lib.rs
file. - Solution: Just add the
tweet
account with account constraint in thesrc/lib.rs
file.
Before:
#[derive(Accounts)]
pub struct SendTweet<'info> {
pub tweet: Account<'info, Tweet>,
pub author: Signer<'info>,
pub system_program: Program<'info, System>,
}
After:
#[derive(Accounts)]
pub struct SendTweet<'info> {
#[account(init, payer = author, space=Tweet::LEN)]
pub tweet: Account<'info, Tweet>,
#[account(mut)]
pub author: Signer<'info>,
pub system_program: Program<'info, System>,
}
-
Cause: This happens when the
payer
account is not provided with account constraint in thesrc/lib.rs
file. Basically, the trial was to make the contract as payer.#[derive(Accounts)] pub struct Initialize<'info> { #[account(init, payer = crate::ID, space= 8+4+200)] data: Account<'info, MyData>, // #[account(mut)] author: Signer<'info>, system_program: Program<'info, System>, }
-
Solution: Instead add the
author
(EOA) account with account constraint in thesrc/lib.rs
file, but not a program account.#[derive(Accounts)] pub struct Initialize<'info> { #[account(init, payer = author, space= 8+4+200)] data: Account<'info, MyData>, #[account(mut)] author: Signer<'info>, system_program: Program<'info, System>, }
- Solana Wiki, comparison to Ethereum
- Solana vs Ethereum account
- Solana Cookbook
- Anchor - Solana Smart Contract Framework
- Anchor Book
- Solana internals Part 1: what are the native on-chain programs and why do they matter?
- Get started with Anchor
- Build Solana Anchor Blog with Svelte Front end Tutorial - Part 1 (Rust)
- Solana Tutorial | Solana for Developers
- Building SmartContracts With #Solana and #Rust
- The Complete Guide to Full Stack Solana Development with React, Anchor, Rust, and Phantom
- Learning How to Build on Solana
- ok so what the fuck is the deal with solana anyway
- Solana Development Tutorial: Key Concepts
- Solana Transactions in Depth
- SOL dev
- NFT Developer Studio for Solana
- Program library | by Solana-labs
- Create a Solana dApp from scratch
best
Using anchor lang/framework
- By Figment
- Solana Tutorial: Creating PDA's with Anchor
- Solana 101:
- Solana transactions per second: how to with Rust
- Solana wallet with Rust: get started now