13 min read
Overviewโ
The Solana blockchain is a powerful tool, delivering thousands of transactions per second with almost no-cost transaction fees. If you are new to Web3 or have developed on EVM-based blockchains previously, this guide will help you understand Solana's primitives and kickstart your journey into Solana development. If you're already building on Solana, use this guide as a reference to continue to grow your understanding of Solana's fundamentals.
In this guide, we will cover the basic building blocks of Solana, specifically: Accounts, Programs, Instructions, Transactions, RPCs, and Subscriptions. Let's jump in!
TLDRโ
- Accounts are like files in a traditional operating system--most things on Solana are an Account: user wallets, programs, data logs, and even system programs
- Programs (known as smart contracts on other chains) are a particular type of "executable" account - they are stateless and only execute code based on user-input parameters. Instead of updating themselves, programs have specific authority to update the data (or state) of other accounts they "own."
- Users sign and submit transactions (bundles of program-specific instructions) through an RPC to a Solana cluster to tell programs what to do.
- Solana uses a unique consensus mechanism called Proof of History (PoH) that relies on time and cryptography to timestamp and order transactions that are sent to the network.
- Programs and transaction signers authorize changes to certain data accounts on-chain to update the state of the network.
Hungry for more? Keep reading! ๐
What is Solana?โ
Solana is an open-source blockchain optimized for speed and cost, allowing information and state to be globally synchronized. Solana utilizes a unique version of Proof of Stake consensus called Proof of History, which uses a granular verifiable delay function to effectively timestamp and order transactions sent to the network. According to Anatoly Yakovenko in the Solana Whitepaper, "it uses a cryptographically secure function written so that output cannot be predicted from the input, and must be completely executed to generate the output" (3). "The Leader sequences user messages and orders them such that they can be efficiently processed by other nodes in the system, maximizing throughput" (2).
*This is about as deep as we're going to go into Solana's runtime in this article, and we're going to shift focus to how you can interact and build with Solana. If you want to dig deeper into Solana's architecture and the theory behind some of its design features, here are a few great resources:
Accountsโ
Like files in an operating system, accounts are used to run the system, store data or state, and execute code.
There are 3 main types of accounts on Solana:
- Data accounts that store information and manage the state of the network. There are two types of data accounts:
- System-owned accounts (most commonly recognized as wallets) [example: A User Wallet]
- Program-derived accounts (often referred to as PDAs) - a common example is a user's token account for a specific token. It is derived by the Solana SPL Token Program. [example: A User's Token Account]
- Program accounts that hold executable code. We will talk more about these in the following section, but these accounts are stateless (meaning data passes through them but does not update them) [example: Candy Machine v2 Program Account]
- Native System Accounts (e.g., System Program, Voting, Staking, BPF Loader) [example: Solana System Program]
This article will focus on #1 and #2. If you want to learn more about #3, Native System Accounts, check out docs.solana.com.
In short, almost everything on Solana is an account. The most common type of account is a user's wallet, a data account that manages the state of a user's SOL balance.
Every account has a unique address, or public key, (much like a file path) and a set of associated metadata:
- lamports: The number of lamports owned by this account (64-bit unsigned integer)*
- owner: The program owner this account is assigned to (string address of the owner Program). Owner programs control who has write access to accounts--other programs can read the data of another account, but if they tried to modify that data, the transaction would fail.
- executable: Whether this account can process instructions (boolean). An account with an executable value of true is actually a Program (or Smart Contract)--we will discuss these in the next section.
- data: Any additional data associated with the account, stored as a raw data byte array
- rent_epoch: The next epoch that this account will owe rent (64-bit unsigned integer)
* lamports are the smallest denomination of SOL, Solana's native token (represent one billionth of one SOL)
Any account's metadata can be read by running the getAccountInfo JSON RPC query or searching it on Solana Explorer. Solana Explorer allows you to search for any account and see basic details about what type of account it is and what Program it is associated with. Here's an example of a system-owned data account (a user's wallet):
Let's visualize an example of all of the account types:
A lot is going on here, but everything you see in this figure is an Account. On the left-hand side, we have a System Account called System Program governing all of the user wallets. We also have an executable Program Account, called Solana SPL Token Program, that users can use to mint, burn, and transfer SPL tokens. Finally, we have Program Derived Accounts. In this case, these are token accounts owned by the Token Program and associated with a User's Account. Notice how 1 user wallet can have multiple token accounts.
Remember, you can search any account address in Solana Explorer to get its metadata and understand what type of account it is and what program it's associated with.
Lamports and Rent Epoch Accounts and account data are stored in the validator's memory and are required to pay "rent" in lamports (denoted in the accounts lamports metadata field) to remain there. The more data stored in your account, the more rent required (note: the current maximum account size is 10-MB). You can calculate your rent requirements using the JSON RPC method, getMinimumBalanceForRentExemption. Validators are responsible for periodically scanning all accounts and collecting rent--accounts are checked on the epoch identified in the rent_epoch metadata field. If an account drops to a zero-lamport balance, the network will purge the account, and the data will be lost. At the time of this writing, all new accounts are required to maintain rent-exempt status, which is granted by holding 2-years' worth of rent in the account. Source: https://docs.solana.com/developing/programming-model/accounts Epochs are a measure of time on Solana, currently representing about 2.5 days.
Programsโ
Programs are what Solana calls Smart Contracts--they are the engine that handles information and requests: everything from token transfers and Candy Machine mints to 'hello world' logs and DeFi escrow governance.
- Remember, Solana programs are stateless accounts flagged as "executable": "Programs are considered stateless since the primary data stored in a program account is the compiled BPF code."
- Programs can use "Cross-Program Invocation" to use or build upon other Solana Programs. This is often referred to as composability.
- Unlike many other popular chains, programs are upgradeable.
- Most Programs are user-generated, but Solana Labs has built and maintains a number of on-chain programs known as the Solana Program Library.
Effectively, a user must provide program-specific instructions to a program, the program processes those instructions, and then the program may invoke other programs and/or update program-derived accounts that it owns (more on PDAs below).
Program Derived Address Accountsโ
A Program Derived Address (PDA) is an account created and controlled by a specific program. PDAs:
- Allow programs to control specific addresses, called program addresses, so no external user can generate valid transactions with signatures for those addresses.
- Allow programs to programmatically sign for program addresses that are present in instructions invoked via Cross-Program Invocations. Solana deterministically derives PDAs based on seeds defined by the program and the program ID. More information about generated Program Addresses can be found at docs.solana.
This enables programs to offer trustless services like escrow accounts for safely managing trades, bets, or defi protocols. If we refer to our schematic above, the SPL Token Program generates a PDA for a user based on the user's wallet and the mint address of that specific token. That specific PDA would store the state or data for the quantity of that token owned by a user.
Let's dig into how we make programs work with instructions and transactions.
Instructionsโ
Instructions, at their core, tell a Program to do something. Solana defines instructions as "The smallest contiguous unit of execution logic in a program." (Source: docs.solana.com/terminology).
An instruction consists of 3 parts:
- The Public Key of the Program that you will be invoking,
- An array of Accounts that it will read or modify, and
- A byte array of any additional data that is necessary for the program's execution (program-specific)
That last point is crucial--instructions are program specific. The transaction will fail if you enter the wrong program ID or do not know the expected data structure to pass into the program.
In Javascript, a transaction instruction looks something like this:
new TransactionInstruction({
programId: new PublicKey(PROGRAM_ID),
keys: [
{ pubkey: signerKeypair.publicKey, isSigner: true, isWritable: true },
{ pubkey: pda, isSigner: false, isWritable: true },
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false }
],
data: Buffer.from(SOME_ENCODED_DATA),
})
It is worth mentioning here the Account information required for a Transaction Instruction. We must provide the public key of the account, whether or not that account is a signer on the transaction, and whether the account will be written as a part of the transaction (this helps contribute to Solana's ability to execute quickly). In the example above, a user signs the wallet, and data is written to a PDA. It's commonly necessary to include system program accounts as well (e.g., for creating a new account). Finally, we pass SOME_ENCODED_DATA. The type of data required is defined inside the program. The program must deserialize the input data to process it correctly.
Transactionsโ
Transactions are the means of delivering instructions to a program on the network. Transactions can package multiple instructions together for processing multiple operations simultaneously. For example, imagine an instance when you have three different instructions: an instruction for Wallet A to send Wallet B 1 $SOL, an instruction for Wallet B to Send Wallet A 33 $USDC, and an instruction to log a memo to the transaction that states "Wallet A Swap 1 $SOL for 33 $USDC with Wallet B on YYYY-MM-DD." Though those could each be sent to the network separately, what would happen if one instruction failed, but the others succeeded? You would have a scenario where one person got their tokens, and another did not. By bundling instructions into a package, we can ensure that all of our transactions succeed or fail together.
Transactions are currently limited to 1,232 byes. Each transaction must include the following:
- an array of one or more transaction instruction
- an array of all account addresses the program will be interacting with
- an array of signatures: A subset of the accounts we pass into our transaction must also include a signature. "Those signatures signal on-chain programs that the account holder has authorized the transaction. Typically, the program uses the authorization to permit debiting the account or modifying its data." (Source: docs.solana.com). For example, this could also be necessary for a creator to verify that an NFT is genuine.
- a recent blockhash: a blockhash is basically a PoH timestamp. Solana, by default, drops "old" transactions to prevent double counting, to allow validators to only check transactions against a very recent batch, and to give users quick certainty if their transaction has been processed (or failed, so they can try again). Fetching a recent blockhash before submitting a transaction allows the network to know the age of our transaction and when it should expire (if ever needed).
Once the network receives a transaction, it returns a transaction ID (base-58 encoded string) which can be used to look up the transaction details.
Let's discuss how to get your transaction to the network!
RPCโ
JSON-RPC is a Remote Procedure Call protocol encoded in JSON that enables users to send information to a Solana cluster and get a response from the network. Solana currently maintains three network clusters:
- Devnet: a playground to test the functionality of applications and methods
- Testnet: a development environment used to stress test all of the latest planned upgrades to the system
- Mainnet Beta: production environment
Clients can make read or write requests to any of the three clusters using JSON-RPC requests through a Solana node. Nodes kind of act as "air traffic control" for handling requests to the network. Clients connect to Solana nodes through the Solana Web3 Connection class:
const HTTP_ENDPOINT = 'https://example.solana-devnet.quiknode.pro/000/';
const SOLANA_CONNECTION = new Connection(HTTP_ENDPOINT);
In the sample above, HTTP_ENDPOINT is an HTTP URL that points to a Solana node that will ultimately route your request to one of the three clusters.
Every request requires an API endpoint to connect with the network. This is where QuickNode comes in! You're welcome to use public nodes or deploy and manage your own infrastructure; however, if you'd like 8x faster response times, you can leave the heavy lifting to us.
You can now pay for a QuickNode plan using USDC on Solana. As the first multi-chain provider to accept Solana payments, we're streamlining the process for developers โ whether you're creating a new account or managing an existing one. Learn more about paying with Solana here.
See why over 50% of projects on Solana choose QuickNode and sign up for a free account here. Simply sign in and update the HTTP_ENDPOINT in your app with the one provided in your account dashboard, and you are good to go!
Once you have established a Connection to a Solana cluster, you can perform many read or write methods to the network. A complete list of RPC methods and documentation is available on QuickNode Docs. Though every method has its purpose, a few that are relevant to the discussion so far in this guide are:
- sendTransaction - sends a Transaction to the Solana network
- getAccountInfo - fetches metadata associated with any Account
- getLatestBlockhash - returns the latest blockhash from the network
HTTP calls allow us to tell the network to do something (e.g., process a transaction and its instructions, return current state information about an account, etc.), but what if we want the network to alert us about changes to an account state?
Subscriptionsโ
Subscriptions are a special kind of RPC request where we make a WebSocket (WSS) request instead of using an HTTP endpoint. Solana Web3 JSON RPC includes several subscription methods (e.g., accountSubscribe, which will listen for changes to an account's lamports or data). We can then subscribe to these events with a callback function, allowing some desired action to happen whenever the network changes.
The Connection class constructor allows us to pass an optional commitmentOrConfig. Within it, we can pass a wsEndpoint. This is an option for you to provide an endpoint URL to the full node JSON RPC Websocket Endpoint. If you have created a QuickNode Endpoint, you should see this option on the right side of your endpoint's page.
const HTTP_ENDPOINT = 'https://example.solana-devnet.quiknode.pro/000/';
const WSS_ENDPOINT = 'wss://example.solana-devnet.quiknode.pro/000/';
const solanaConnection = new Connection(
HTTP_ENDPOINT,
{wsEndpoint:WSS_ENDPOINT}
);
What happens if you don't pass a wsEndpoint? Well, Solana accounts for that with a function, makeWebsocketUrl, that replaces your endpoint URL's https with wss or http with ws (source). Because all QuickNode HTTP endpoints have a corresponding WSS endpoint with the same authentication token, it is acceptable for you to omit this parameter unless you would like to use a separate endpoint for your Websocket queries.
To dive deeper into creating WebSocket subscriptions, check out our Guide: How to Create Websocket Subscriptions to Solana Blockchain using Typescript.
Pull it All Togetherโ
Okay! That's a lot of information--you have probably started to see some connective tissue between these primitives, but let's do a quick recap before connecting all of the pieces together:
- Almost every on Solana is an Account: user wallets, programs, system programs, program data, etc.
- Programs are stateless--they process instructions and can call other programs and change the state of data accounts they own.
- Instructions are program-specific and tell a program what to do.
- Transactions package one or more instructions with a signature to authorize a program to execute one or more tasks.
- RPC Protocols allow clients to send read/write requests to one of Solana's three clusters through a node, like QuickNode.
- Clients can subscribe to network changes using WebSocket requests through the RPC provider.
Let's walk through an illustrative example. Imagine a Program called "GM" that holds a counter that logs anytime a user sends a "GM" message to the Program.
In the example above, a client creates a Transaction that includes several Transaction Instructions. The instructions are program specific, so they need to match the expected structure of the GM Program. The Client signs and sends the Transaction to a Solana cluster through an RPC provider, like QuickNode, using the Connection class. The RPC provider routes the request to the cluster. The Program account executes and processes the input instructions received. The Program account is stateless and does not change but authorizes a change in the data of the PDA to increment the value of the GM counter. A subscription created by the client listens for changes to the PDA; then, the network alerts the client that the account has been changed.
Wrap Upโ
Whew! That's A LOT of information we just covered. Do not be afraid to read through this several times to ensure you understand these concepts.
Now that you have a more robust understanding of Solana, it's time to build! Try out some of our Solana development guides to get started. Here are a few examples:
- How to Create and Send (Versioned) Transactions on Solana
- How to Mint an NFT on Solana using Typescript
- How to Get Transaction Logs on Solana
Got questions about any of these concepts or just want to talk shop? Hit us up on Discord or Twitter and let us know what you're building!
We <3 Feedback!โ
If you have any feedback or questions on this guide, let us know. We'd love to hear from you!
Reference and Sourcesโ
- https://solanacookbook.com/core-concepts/accounts.html#deep-dive
- https://www.quicknode.com/guides/solana-development/an-introduction-to-the-solana-account-model
- https://medium.com/@anatolyyakovenko
- https://docs.solana.com/developing/programming-model/accounts
- https://docs.solana.com/developing/clients/jsonrpc-api#getaccountinfo