-- Leo's gemini proxy
-- Connecting to nox.im:1965...
-- Connected
-- Sending request
-- Meta line: 20 text/gemini; charset=utf-8
The design of Solana shows a deep understanding of distributed systems, operating systems and hardware. This article is the first in a series of communicating from the Web3 frontends via JSON RPC to Solana nodes, building and deploying on-chain programs and understanding the fundamental concepts.
Here, I'm going through the basics of setting up a web3 development environment with Rust, NodeJS and the Solana SDK.
Solana[1]
We start by downloading the latest Solana release from Github. If you're on a Mac like me, get solana-release-x86_64-apple-darwin.tar.bz2[1] and set it up:
tar jxf solana-release-x86_64-apple-darwin.tar.bz2 cd solana-release/ export PATH=$PWD/bin:$PATH
Generate key and save the output in a safe place
solana-keygen new -o ./validator-keypair.json Wrote new keypair to ./validator-keypair.json pubkey: 8RJ66oyximASYmucMXL5LDx6kSbqUgfesuDEm2wmXCwY Save this seed phrase and your BIP39 passphrase to recover your new keypair: # ...
it prints a pubkey which enables us to airdrop ourselves play tokens on the devnet:
solana airdrop 1 8RJ66oyximASYmucMXL5LDx6kSbqUgfesuDEm2wmXCwY --url https://api.devnet.solana.com Requesting airdrop of 1 SOL Signature: 5deUJ1tWRZb3oNoAsJ9NSfb7F2EEMPgTKYxFEL1FGSeNYktHh2PDU46J7958x1MV4aWSFWXB5VRaryNw1CCmVPd1 1 SOL
we can find the transaction signature on the Solana explorer 5deUJ1tWRZb3oNoAsJ9NSfb7F2EEMPgTKYxFEL1FGSeNYktHh2PDU46J7958x1MV4aWSFWXB5VRaryNw1CCmVPd1[1]
check the balance to confirm the airdrop was successful
solana balance 8RJ66oyximASYmucMXL5LDx6kSbqUgfesuDEm2wmXCwY --url https://api.devnet.solana.com 1 SOL
solana transfer --from <KEYPAIR> <RECIPIENT_ACCOUNT_ADDRESS> <AMOUNT> --fee-payer <KEYPAIR>
We create a second wallet and transfer funds to it
solana-keygen new --no-passphrase --no-outfile Generating a new keypair pubkey: 62PFC8bjfti96Aig3sY41o8t5n8v91c7gDS252JJMJZg Save this seed phrase to recover your new keypair: # ... solana transfer --from ./validator-keypair.json 62PFC8bjfti96Aig3sY41o8t5n8v91c7gDS252JJMJZg 0.1 --allow-unfunded-recipient --url https://api.devnet.solana.com --fee-payer ./validator-keypair.json Signature: 2qJoBsVfB2d7XaovdcuNCaxTVhRAoJvcgrA6bAHRDhKqGxS9YbPYY3GLiqyHGgYcG71ryGQPk2DwLGFaCtCgDMu5
I create notes on the technical details on how Solana smart contracts work under the hood in a separate article[1].
Here we will stick to pragmatic examples and how to deploy and execute on-chain programs.
We're installing rust via rustup[1]. This installs the Rust Programming Language from the official release channels and enables us to switch between stable, beta, and nightly compilers and keep them updated.
brew install rustup-init rustup-init
Programs are constrained to run deterministically, so random numbers are not available.
For logging, Rust's `println!` macro is computationally expensive and not supported. Instead the helper macro `msg!` is provided.
My environment at this point looks like this:
rustc --version rustc 1.57.0 (f1edd0429 2021-11-29) solana --version solana-cli 1.8.5 (src:76c5c94a; feat:52865992) node --version v15.8.0
Set CLI config url to localhost cluster
solana config set --url localhost Config File: /Users/noxim/.config/solana/cli/config.yml RPC URL: http://localhost:8899 WebSocket URL: ws://localhost:8900/ (computed) Keypair Path: /Users/noxim/.config/solana/id.json
Start a local Solana cluster:
solana-test-validator Notice! No wallet available. `solana airdrop` localnet SOL after creating one Ledger location: test-ledger Log: test-ledger/validator.log Identity: 5JjzuqdS9B36QxTxokq8QA2MNxP5GZRehEsfY9q8JJTW Genesis Hash: 2tW5hFVULXwyznwZRPMzPyUPPjWvochGb9trQ2xLFwVU Version: 1.8.5 Shred Version: 16631 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:14 | Processed Slot: 27 | Confirmed Slot: 27 | Finalized Slot: 0 | Snaps
Ctrl+C will not delete the data on this validator, should something go wrong with this validator or should you desire to reset your localnet blockchain, start it with `solana-test-validator --reset`.
Grab the hello world example from Solana labs and compile the rust program
git clone https://github.com/solana-labs/example-helloworld cd example-helloworld npm run build:program-rust
create a default signer and deploy the on-chain program, note that this will fail initially if we don't airdrop ourselves funds. I've included this example as I've seen it commonly encountered online with local clusters.
solana-keygen new -o /Users/noxim/.config/solana/id.json solana program deploy dist/program/helloworld.so Error: Account 8jz3nUqvoxBwxeNSJtcPEnu1X65gaVF9Gab5qecuGET1 has insufficient funds for spend (0.4223676 SOL) + fee (0.00032 SOL) solana aidrop 1 Requesting airdrop of 1 SOL Signature: 4X5JbjNooagQ4Q73NvPKPP1EMuURmjsGLmNh81EGA1g14e97TMsaYxKKRfpdvuM5yfAYBgKTz4HrccU3u9JJa81z solana program deploy dist/program/helloworld.so Program Id: 2YStkyjmtn6mpTfH8xE5q74wGtPKwhXm9RvuVrue36zM
We can inspect the file type and see we got a Executable and Linkable Format (ELF), a common standard for executables and shared objects, in the eBPF instruction set.
We can inspect the program with the Solana toolchain as follows:
solana program show 2YStkyjmtn6mpTfH8xE5q74wGtPKwhXm9RvuVrue36zM Program Id: 2YStkyjmtn6mpTfH8xE5q74wGtPKwhXm9RvuVrue36zM Owner: BPFLoaderUpgradeab1e11111111111111111111111 ProgramData Address: 6hskN36DBFFx4GRE4Ux1sDr5mzt2UhHe8gUfAhigZDsy Authority: 8jz3nUqvoxBwxeNSJtcPEnu1X65gaVF9Gab5qecuGET1 Last Deployed In Slot: 17583 Data Length: 121024 (0x1d8c0) bytes Balance: 0.84353112 SOL
An account in Solana has an owner. This owner refers to the program that owns and can make changes to the data in this account (more on storage later). We can inspect the account of the program with:
solana account 2YStkyjmtn6mpTfH8xE5q74wGtPKwhXm9RvuVrue36zM Public Key: 2YStkyjmtn6mpTfH8xE5q74wGtPKwhXm9RvuVrue36zM Balance: 0.00114144 SOL Owner: BPFLoaderUpgradeab1e11111111111111111111111 Executable: true Rent Epoch: 0 Length: 36 (0x24) bytes 0000: 02 00 00 00 54 c3 0c 2a a7 b2 bd 4b 84 49 d6 b0 ....T..*...K.I.. 0010: da 9b 10 98 27 ba 62 d8 09 4c 79 06 75 8c 60 d1 ....'.b..Ly.u.`. 0020: 25 e2 82 cc %...
file dist/program/helloworld.so: ELF 64-bit LSB shared object, eBPF, version 1 (SYSV), dynamically linked, stripped
run the web3 Javascript client
npm install npm run start
Let's say hello to a Solana account... Connection to cluster established: http://localhost:8899 { 'feature-set': 52865992, 'solana-core': '1.8.5' } Using account 8jz3nUqvoxBwxeNSJtcPEnu1X65gaVF9Gab5qecuGET1 containing 0.15500744 SOL to pay for fees Using program 2YStkyjmtn6mpTfH8xE5q74wGtPKwhXm9RvuVrue36zM Creating account 8qz4R9s3WSWsuFZikiGSnbG19vfkF8qPqM2RGFVZVjmW to say hello to Saying hello to 8qz4R9s3WSWsuFZikiGSnbG19vfkF8qPqM2RGFVZVjmW 8qz4R9s3WSWsuFZikiGSnbG19vfkF8qPqM2RGFVZVjmW has been greeted 1 time(s) Success
Running `solana logs` in a second window will show us the transaction on another run
Transaction executed in slot 2566: Signature: 4n6VL8i5FjUgefe3bZXNiuZJGs87D19iByxnAseimNGATRdYUeDKFbs4hPSHLwH6hHSWGZNbgPNZtSpMU1E7Hj3k Status: Ok Log Messages: Program 2YStkyjmtn6mpTfH8xE5q74wGtPKwhXm9RvuVrue36zM invoke [1] Program log: Hello World Rust program entrypoint Program log: Greeted 2 time(s)! Program 2YStkyjmtn6mpTfH8xE5q74wGtPKwhXm9RvuVrue36zM consumed 1174 of 200000 compute units Program 2YStkyjmtn6mpTfH8xE5q74wGtPKwhXm9RvuVrue36zM success
The web3 app does these five things:
async function main() { console.log("Let's say hello to a Solana account..."); // Establish connection to the cluster await establishConnection(); // Determine who pays for the fees await establishPayer(); // Check if the program has been deployed await checkProgram(); // Say hello to an account await sayHello(); // Find out how many times that account has been greeted await reportGreetings(); console.log('Success'); }
Don't forget to point your Solana CLI again to the devnet if we want to move development to staging:
solana config set --url devnet
Anchor is a development framework that will save us a ton of time and takes away some low level details with macros. See the anchor introductory guide[1] for a more in-depth tutorial. The source code can be found on project-serum/anchor[2].
Install anchor and yarn which anchor depends on;
npm install -g yarn npm i -g @project-serum/anchor-cli
if you're on MacOS or any of their unsupported operating systems, build from source
cargo install --git https://github.com/project-serum/anchor --tag v0.19.0 anchor-cli --locked
anchor init <new programm>
to run `anchor build` I had to set the following
export ANCHOR_WALLET=~/.config/solana/id.json export ANCHOR_PROVIDER_URL=http://127.0.0.1:8899
The first time we build this program with `anchor build`, anchor will generate a key pair for it. This pair will be stored in the `/target` directory (`target/deploy/<my program>-keypair.json`). The pubkey will become the unique identifier of your program, the program ID. Note that **we need to update our program ID**. It will be displayed on deployment, but we can also access it with the Solana tools:
solana address -k target/deploy/myapp-keypair.json 8Xcscw4KgUtHGVqCRNDKFRXMo2jPPs974VSj9JH9eYQv
Update `Anchor.toml`
[programs.localnet] my_solana_app = "8Xcscw4KgUtHGVqCRNDKFRXMo2jPPs974VSj9JH9eYQv"
and `lib.rs`
declare_id!("8Xcscw4KgUtHGVqCRNDKFRXMo2jPPs974VSj9JH9eYQv");
`anchor deploy` takes the BPF and deploys it with a transaction on the selected cluster. The command `anchor test` takes care of all steps including spinning up a local ledger that terminates after execution. If we do run a local test validator already and want to test closer to what we'd get on dev and main net, we run the steps `anchor test --skip-local-validator` which does:
anchor build anchor deploy anchor test
This may fail with
tsconfig.json" needs an import assertion of type "json"
You will need to install `ts-mocha`:
yarn add ts-mocha
The last step executes the "test" script from the `Anchor.toml` file. You can also invoke it individually with `anchor run test`. You're very likely to require packages such as spl-token that you can install with:
yarn add @solana/spl-token
To continue the journey and go into more details, I've since added another article on Solana On-Chain Programs[1].
You'll find plenty of directives in generated and popular anchor programs such as `#[account(mut)]` for writable accounts, `#[account(zero)]` to assert program accounts, `#[account(signer)]` to assert that the given account signed the transaction and many others as well as combinations of the above.
See Derive Macro anchor_lang::Accounts[1] to quickly know what's what.
To deploy with the `solana` command to the same program ID, specify the keypair in the deploy command to deploy to a specific program id:
solana program deploy --program-id <KEYPAIR_FILEPATH> <PROGRAM_FILEPATH> solana program deploy --program-id project-name-keypair.json project-name.so
Under certain scenarios during testing we want to deploy a second version of our on-chain program without destroying or overwriting the first version. For example when we have a web client and want to test against two different versions of the same code base. I started to rotate anchor program IDs for this as follows:
mv target/deploy/<project-name>-keypair.json target/deploy/<project-name>-keypair.json.6uaD9SSV12fLxiENKzzqUvZ7t8k2QzLxnpbLXqzKwKeH anchor build anchor deploy
assuming `6uaD9SSV12fLxiENKzzqUvZ7t8k2QzLxnpbLXqzKwKeH` was my previous program ID for quick references. We then need a new keypair and plug the program ID into a few places:
- update program id in lib.rs
- update program id in Anchor.toml
Running `anchor test --skip-local-validator` now should work again.
I couldn't deploy to my test validator one day and got this error:
Error: Account allocation failed: RPC response error -32002: Transaction simulation failed: Error processing Instruction 1: Unsupported program id [2 log messages]
Resetting the validator with `solana-test-validator -r` helped and resolved this issue.
A lot of built errors I found clean out when reinstalling/rebuilding the local toolchain and sometimes just removing the old Solana SDK via `rm -rf ~/.cache/solana`. It's the early days.
Ensure you have set the anchor wallet environment variable.
export ANCHOR_WALLET=~/.config/solana.id.json
-- Response ended
-- Page fetched on Sat Apr 27 15:12:29 2024