Starting with Solana, Part 1

Written

My involvement in the cryptocurrency space has been minimal, aside from mining some Ethereum back in 2015 and following trends from afar. As “web3” has gained more mindshare in the developer community, it feels like a good time to dip my toe in, so here’s an exploration of writing applications for the Solana blockchain. The format will be more stream-of-consciousness than my usual articles here, just writing things down as I discover them.

This article series will start out following Nader Dabit’s introduction and we’ll see where it goes from there.

Setup 🔗

You don’t need a wallet right away, but it’s an important part of interacting with any blockchain, so it’s a good place to start. I chose https://phantom.app/ which is a popular wallet browser extension for Solana. I have some Solana on the FTX exchange already and Phantom makes it really easy to transfer from FTX into your own account, so that’s nice.

The wallet gives you a recovery passphrase so that’s nice. Apparently this is standard nowadays, but when I first started playing with crypto you just had to make sure you didn’t lose your private key files so this is a nice change.

Next I installed the Solana CLI tools and the Anchor tools as linked from the article above.

Anchor is a Rust framework that provides some abstractions over the Solana VM.

Looks like there are three Solana cluster options:

  • localhost which just runs on your computer

  • devnet which is an actual Solana network, but for testing applications and where can give your wallet as many tokens as you want (devnet tokens aren’t real or worth anything)

  • mainnet beta, which is the current “real” network where real transactions happen

    Once you have the Solana CLI tools installed, you can run solana-test-validator to start a single-node localhost cluster on your computer.

    To start out with the testing code you’ll need to create a “paper wallet” as well. This isn’t really suitable from a security standpoint for real use but is fine for just testing. You can run solana address to get instructions on how to create that wallet if you don’t have it yet.

    Nader’s article has a quick overview of other solana CLI commands that come in handy at the start. There are about 75 commands overall but it looks like most of them aren’t used too often.

The Anchor Project Template 🔗

Ok, so on to creating an Anchor project. anchor init p0001 is what I used. I initially called it just 0001 which Anchor will do, but Rust doesn’t let crate names start with numbers, so don’t do that.

So this gives you a scaffolding of a Rust Solana program, a test file in JavaScript, and some miscellaneous stuff.

The Program 🔗

Opening the Rust file programs/p0001/src/lib.rs gives us this:

use anchor_lang::prelude::*;

 declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

 #[program]
 pub mod p0001 {
     use super::*;
     pub fn initialize(ctx: Context<Initialize>) -> ProgramResult {
         Ok(())
     }
 }

 #[derive(Accounts)]
 pub struct Initialize {}

So the program itself is a module inside the file, and each function in the module becomes an entrypoint into the contract. Solana calls each of these functions an “instruction.” The #[program] line is a macro that processes the module into a Solana program. You can build the project with anchor build.

It looks like Solana programs compile to a variation of the eBPF format. This is interesting since that’s a bytecode originally designed for high-performance packet filtering inside the kernel. I’ve been hearing of it used for other stuff here and there so I guess this is one example.

The IDL File 🔗

The build command generates the compiled program, a keypair that can be used as a wallet to deploy the contract with, and an IDL file that describes the instructions exposed by the contract.

~/projects/solana/p0001 ❯ cat target/idl/p0001.json
{
  "version": "0.0.0",
  "name": "p0001",
  "instructions": [
    {
      "name": "initialize",
      "accounts": [],
      "args": []
    }
  ]
}

So as you see in the file above, this IDL file describes the initialize instruction generated by Anchor, which has no arguments or other associated data.

Let’s also take a look at the default test, at tests/p0001.js.

Testing 🔗

const anchor = require('@project-serum/anchor');

describe('p0001', () => {
  // Configure the client to use the local cluster.
  anchor.setProvider(anchor.Provider.env());

  it('Is initialized!', async () => {
    // Add your test here.
    const program = anchor.workspace.P0001;
    const tx = await program.rpc.initialize();
    console.log('Your transaction signature', tx);
  });
});

The Provider is an abstraction that rolls up the Solana network connection, the wallet, and a commitment, which basically defines how much confirmation you want on a block before your program can see it.

The test just gets the default Provider from the wallet and cluster that you set up with the anchor command. Real programs would get the user’s wallet details and use that.

So then the test makes gets a reference the to program, calls the program’s initialize instruction, and returns the signature of the transaction for that call. Apparently Solana is able to wrap up multiple instruction calls into a single transaction, even to different programs, which is interesting.

Building an Actual Project 🔗

The next step in Nader’s article is to build a very simple program with just a single counter. It has two instructions, create and increment. Each one gets an Accounts structure that goes along with it.

#[program]
pub mod p0001 {
    use super::*;
    pub fn create(ctx: Context<Create>) -> ProgramResult {
        let base_account = &mut ctx.accounts.base_account;
        base_account.count = 0;
        Ok(())
    }

    pub fn increment(ctx: Context<Increment>) -> ProgramResult {
        let base_account = &mut ctx.accounts.base_account;
        base_account.count += 1;
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Create<'info> {
    #[account(init, payer=user, space= 8 + 8)]
    pub base_account: Account<'info, BaseAccount>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Increment<'info> {
    #[account(mut)]
    pub base_account: Account<'info, BaseAccount>,
}

#[account]
pub struct BaseAccount {
    pub count: u64,
}

Accounts 🔗

Before we finish this, lets look at Accounts a bit more. The Solana Account documentation tells us that an account is not actually a wallet. Instead, it’s a way for the contract to persist data between calls. This includes information such as the count in BaseAccount, and also information about permissions on the account. It also has some lifetime information “expressed by a number of fractional native tokens called lamports.”

Accounts pay rent in the form of lamports, and if it runs out, then the account is purged from the blockchain. Accounts with two years worth of rent attached are “rent-exempt” and can stay on the chain forever. The current rent cost is designed to work out to 0.01 SOL per MB of data per day, or 3.65 SOL per MB per year. Since most accounts will hold far less than 1MB of data, the actual cost should usually be quite small.

That’s it for now. Next time, more on accounts, digging into the #[account] macro and the arguments to the create instruction, and actually implementing it.

Check out part 2 now!


Thanks for reading! If you have any questions or comments, please send me a note on Twitter. And if you enjoyed this, I also have a newsletter where I sometimes write about tech thoughts, interesting things I've read, and project updates.

You can check out a recent issue, or enter your email below to subscribe.