Solana Transactions in Depth

Alex Miller
17 min readSep 10, 2021

--

An Ethereum developer (sort of) learns how Solana transactions work.

I’ve been hacking/building on Ethereum since 2015 and have largely remained focused on EVM-based blockchains since. As a result, I have acquired a reasonably good handle on how Ethereum works up and down the stack. I also built a hardware wallet (Lattice), which is designed to enhance the user experience of transacting on smart contract platforms. Until recently, EVM-based chains have held nearly 100% of this market share.

Solana started growing in usage earlier this year so I took a look at their docs and realized how different it was from Ethereum. It is a completely different system with an entirely different set of tradeoffs. Even though I am a blockchain boomer I have to admit I found it intriguing. I’ve finally had some time to dig in and learn a bit more about how Solana works and I decided that studying a transaction in depth would be the best way to start, so I set off to do that and recorded my thoughts and analysis here.

NOTE: I’m definitely not a Solana expert so some things may be wrong — please let me know if that is the case and I will correct the article.

Setting up a Wallet and Minting

Before doing anything, set up a burner Sollet wallet on testnet. I actually cloned the wallet repo and ran it locally so I could get some descriptive prints to figure out what was going on, so feel free to do that too if you are interested in doing your own deep dive.

Once you set up your wallet and switch to testnet, request an airdrop and then mint the test token. That mint transaction is what I will be discussing here.

Structure of a Solana Transaction

I’ll first describe how a generic Solana transaction is structured, per the official docs:

Basically, a transaction is made up of a few parts:

1. Signatures — this is a list of ed25519 curve signatures of the message hash, where the “message” is made up of metadata and “instructions”. Unlike Ethereum, Solana transactions can contain several signers (this will make more sense later) and this signature field needs all of them. Any account that authorizes any state update in a transaction must sign that transaction, similar to Bitcoin where all UTXOs must be signed by their owners, but different in that the relationship is not 1:1 — an account updated multiple times in a transaction only needs to sign it once.

2. Metadata — there is some metadata that goes into the message which I didn’t get in my printout and I’ll just briefly mention here because it’s in the docs. The message has a header, which includes 3 uint8s describing how many accounts will sign the payload, how many won’t, and how many are read-only. The metadata also contains a list of accounts that will be referenced/used in this transaction and a “recent” block hash. I’m not sure what the exact rules on this hash are, but it doesn’t have to be the most recent block, it just has to be a recent block. How recent is “recent enough”? Idk.

3. Instructions — this is the meat of the message (and this post). When you make a transaction in Solana you are feeding the runtime a series of “instructions”, which each do different things to update state in the global Solana system. An instruction contains three main pieces of info: a) set of accounts being used and whether each one is a signer and/or writable, b) a program ID which references the location of the code you will be calling, c) a buffer with some data in it, which functions as calldata.

Accounts and Programs

An “account” is essentially a memory region in the Solana system with an associated address. A “program” is a special kind of account which contains static, executable code.

Aside: There are actually two types of “addresses”, which will be confusing if you’re coming from Ethereum. Solana uses ed25519 which has 32-byte public keys — this is one type of “address” (i.e. it is not hashed). If you are creating an account that you want to be able to sign from, your address needs to be a point on the ed25519 curve (a.k.a. a valid public key). However, you can also create a “program derived address” (PDA) which must be off the curve, which means there is no corresponding private key. You can’t create an account with a PDA directly — instead, a program-owned account must call System Program using a program function. PDAs are generally used when programs need to manage accounts internally for more complex stuff (e.g. DeFi) and we won’t use them in the token mint transaction.

To “install” a program on the Solana system you would create a new account (with a public/private key pair) and send a bunch of compiled bytecode as the calldata. The code will be deployed to the address matching the public key you provided. There are some system-level programs in Solana and one of them is the BPF Loader (BPF stands for “Berkeley Packet Filter”) — this is the program you would call to create a new program and it becomes the “owner” of your new program.

So anyway you can think of a “program” as a memory region in the Solana system which contains immutable, stateless executable code. Once the program code is deployed, you create a new account and mark the program as its “owner” — I’ll call this a “program-owned account” from now on. Note that if you create an account with a public/private key pair and assign a program as the owner, you cannot mutate this account’s state directly — you must use the linked program’s API to do so, similar to a proxy contract pattern in Ethereum. To be clear, this means the private key you generate to create the program-owned account can be thrown away after it signs the transaction to create the account — this key can’t do anything once the account is created.

The Example: Minting a Token

Okay enough background. Let’s look at our transaction. Here’s the one I ran:

And here is my local Sollet wallet JSON output:

{  "instructions": [// instruction 1/5
{
"keys": [
{
"pubkey": {
"_bn": "63e8ee43a8583013ba6fef5c09181f26720d0621debe8c57eaf4ed87e704843b"
},
"isSigner": true,
"isWritable": true
},
{
"pubkey": {
"_bn": "f7fa7a4d4e8dda19a3afbec13a5fb4a540289b869b6a5a46162b7c3e3d9161ab"
},
"isSigner": true,
"isWritable": true
}
],
"programId": {
"_bn": "00"
},
"data": {
"type": "Buffer",
"data": [
0, 0, 0, 0, 96, 77, 22, 0, 0, 0, 0,
0, 82, 0, 0, 0, 0, 0, 0, 0, 6, 221,
246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206,
235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145,
58, 140, 245, 133, 126, 255, 0, 169
]
}
},
// instruction 2/5
{
"keys": [
{
"pubkey": {
"_bn": "f7fa7a4d4e8dda19a3afbec13a5fb4a540289b869b6a5a46162b7c3e3d9161ab"
},
"isSigner": false,
"isWritable": true
},
{
"pubkey": {
"_bn": "06a7d517192c5c51218cc94c3d4af17f58daee089ba1fd44e3dbd98a00000000"
},
"isSigner": false,
"isWritable": false
}
],
"programId": {
"_bn": "06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9"
},
"data": {
"type": "Buffer",
"data": [
0, 2, 99, 232, 238, 67, 168, 88, 48, 19, 186, 111,
239, 92, 9, 24, 31, 38, 114, 13, 6, 33, 222, 190,
140, 87, 234, 244, 237, 135, 231, 4, 132, 59, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0
]
}
},
// instruction 3/5
{
"keys": [
{
"pubkey": {
"_bn": "63e8ee43a8583013ba6fef5c09181f26720d0621debe8c57eaf4ed87e704843b"
},
"isSigner": true,
"isWritable": true
},
{
"pubkey": {
"_bn": "f2560320922ee24d0086e5769e8b3cdf261c646f4f17184532ec7380220b980c"
},
"isSigner": true,
"isWritable": true
}
],
"programId": {
"_bn": "00"
},
"data": {
"type": "Buffer",
"data": [
0, 0, 0, 0, 240, 29, 31, 0, 0, 0, 0,
0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221,
246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206,
235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145,
58, 140, 245, 133, 126, 255, 0, 169
]
}
},
// instruction 4/5
{
"keys": [
{
"pubkey": {
"_bn": "f2560320922ee24d0086e5769e8b3cdf261c646f4f17184532ec7380220b980c"
},
"isSigner": false,
"isWritable": true
},
{
"pubkey": {
"_bn": "f7fa7a4d4e8dda19a3afbec13a5fb4a540289b869b6a5a46162b7c3e3d9161ab"
},
"isSigner": false,
"isWritable": false
},
{
"pubkey": {
"_bn": "63e8ee43a8583013ba6fef5c09181f26720d0621debe8c57eaf4ed87e704843b"
},
"isSigner": false,
"isWritable": false
},
{
"pubkey": {
"_bn": "06a7d517192c5c51218cc94c3d4af17f58daee089ba1fd44e3dbd98a00000000"
},
"isSigner": false,
"isWritable": false
}
],
"programId": {
"_bn": "06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9"
},
"data": {
"type": "Buffer",
"data": [
1
]
}
},
// instruction 5/5
{
"keys": [
{
"pubkey": {
"_bn": "f7fa7a4d4e8dda19a3afbec13a5fb4a540289b869b6a5a46162b7c3e3d9161ab"
},
"isSigner": false,
"isWritable": true
},
{
"pubkey": {
"_bn": "f2560320922ee24d0086e5769e8b3cdf261c646f4f17184532ec7380220b980c"
},
"isSigner": false,
"isWritable": true
},
{
"pubkey": {
"_bn": "63e8ee43a8583013ba6fef5c09181f26720d0621debe8c57eaf4ed87e704843b"
},
"isSigner": true,
"isWritable": false
}
],
"programId": {
"_bn": "06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9"
},
"data": {
"type": "Buffer",
"data": [
7, 232, 3, 0, 0, 0, 0, 0, 0
]
}
}
],
"signatures": [
{
"signature": {
"type": "Buffer",
"data": [
20,
... // omitting because sigdata isn't important
5
]
},
"publicKey": {
"_bn": "63e8ee43a8583013ba6fef5c09181f26720d0621debe8c57eaf4ed87e704843b"
}
},
{
"signature": {
"type": "Buffer",
"data": [
0,
...
15
]
},
"publicKey": {
"_bn": "f7fa7a4d4e8dda19a3afbec13a5fb4a540289b869b6a5a46162b7c3e3d9161ab"
}
},
{
"signature": {
"type": "Buffer",
"data": [
230,
...
8
]
},
"publicKey": {
"_bn": "f2560320922ee24d0086e5769e8b3cdf261c646f4f17184532ec7380220b980c"
}
}
]
}

This is not a perfect mapping to the transaction structure described above but it contains all the info we need. One important thing to note is that all of the _bn entries are hex string representations of addresses. Addresses are normally encoded/displayed as base58 strings in things like Solscan and I will use base58 from now on. There was also a recentBlockhash but I’ve omitted it because it isn’t relevant to this transaction and I already discussed the concept.

Let’s go through each piece now.

— Instruction #1/5 —

The first instruction creates a program-derived account which will be owned by Token Program. This instruction contains two keys:

  • 7j1NJDkeg35gtRt4nco9fN4wak6M6rD16okMYgvLJeHp: this is a signer and needs to be writable to deduct SOL from my balance.
  • Hh1E4LwRxAiJdLV4gzKX3BpdbNXJ2AYVdv8UDrfTZAbL: signer and writable. We don’t know the story on this one yet.

Aside: atomic units of SOL are called “lamports”, which (much to my chagrin) follows in the footsteps of basically every other crypto project naming atomic units after random cryptographers rather than, you know, a transformation of the base unit. No milli-SOL or nano-SOL for us. Enjoy your lamports.

programId is set to 00, which means we are calling System Program, which is the only Solana program that can create new accounts in the system.

data is this buffer:

[
0, 0, 0, 0, 96, 77, 22, 0, 0, 0, 0,
0, 82, 0, 0, 0, 0, 0, 0, 0, 6, 221,
246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206,
235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145,
58, 140, 245, 133, 126, 255, 0, 169
]

The first four bytes are all 0s. Program functions are indexed with enums so the first parameter of every instruction’s calldata references which function in the given program API is being called. Since the first u32is 0, it means we are calling System Program's function 0, which is CreateAccount.

Aside: Solana system-level programs use u32s as enum index types, but the size of this parameter is up to the program author. The Token Program we will look at next uses a u8 enum index type, so we only need to inspect the first byte to determine which function we are calling.

lamports

The first field in our calldata is a u64 type describing how many lamports to transfer to the new account:

[96,  77,  22,   0,   0,  0,   0,   0]

Woah that’s a huge number… or is it? Hah, it turns out Solana uses little endian number encoding. After implementing Ethereum things in C I have to say I am very happy about the decision to use LE in Solana. If you know, you know.

Anyway, in little endian this number is:

0x0000000000164d60
-> 1461600 lamports

What is this? It’s a payment of “rent” to keep the program alive in the Solana system. I’m not sure about the exact mechanics, but here is the official documentation on rent. The takeaway seems to be that rent cost is proportional to storage size. You can also pay 2 years worth of rent up front and your program will stay alive forever, which is what is happening here. I don’t know how they came to this decision but okay.

space

The next field is another u64 indicating how many bytes of memory to allocate for this program:

[82,   0,   0,   0,   0,   0,   0,   0]

So… 82 bytes? Seems small. This is because the actual memory we are allocating is just for a pointer to the Token Program , a decimal number, and maybe one other thing. Importantly, this program will not hold balances for users — instead, users create their own programs to interact with this one. It’s confusing. We’ll get to it later.

owner

The final field in this System Program Create Account call is the “owner”, which in this case will be Token Program. This is a very common program deployed to every Solana network (i.e. mainnet and testnets) because everyone wants to reference it to create their own tokens. People love tokens.

Here’s that field:

[
6, 221, 246, 225, 215, 101, 161,
147, 217, 203, 225, 70, 206, 235,
121, 172, 28, 180, 133, 237, 95,
91, 55, 145, 58, 140, 245, 133,
126, 255, 0, 169
]

Encoded in base58 this is:

TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA

Which is the address of the Token Program on the testnet I used (link).

Aside: I want to pay respect to whoever took the time to mine these addresses for Solana. I love that they wrote human readable prefixes like Token and BPFLoader. Fun stuff. This also raises an important difference between Solana and Ethereum. With Ethereum you get a deterministic contract address based on your address and a nonce at deploy time — with Solana you can keep creating new keypairs until you get a public key you like since you reference the account address (pubkey) when instantiating it in the Solana system. This is what it means to “mine” an address (pubkey).

Instruction #1 Summary:

We called Create Account via System Program with our wallet account (7j1NJDkeg35gtRt4nco9fN4wak6M6rD16okMYgvLJeHp)— this created a new program-owned account at address Hh1E4LwRxAiJdLV4gzKX3BpdbNXJ2AYVdv8UDrfTZAbL which now holds 1461600 lamports and is owned by TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA (a.k.a the Token Program on this testnet). This account is marked as a signer because it needs to sign off on creating its own memory region which will later be used to store state. After this transaction, though, the private key can be thrown away since the account is owned by Token Program and therefore cannot modify its own state.

— Instruction #2/5 —

In this instruction we are callingToken Program directly:

programId = 06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9base58 -> TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA

Our two keys are:

Hh1E4LwRxAiJdLV4gzKX3BpdbNXJ2AYVdv8UDrfTZAbL (!signer, writable)
SysvarRent111111111111111111111111111111111 (!signer, !writable)

Looking at the comments in the InitializeMint reference:

/// Accounts expected by this instruction:    
/// 0. `[writable]` The mint to initialize.
/// 1. `[]` Rent sysvar

The first address is the account we will “mint” from — it’s the one we created in the previous instruction. This concept will become clearer later but basically this account is (sort of) an instance of Token Program — it describes the token you are creating. It will be used to “mint” the tokens, which will be sent to another account (more on this soon). This mint account needs to be writable in this instruction because it needs to get its state updated.

The second address is the “Rent sysvar” — this appears to be required for some low-level thing in the Solana runtime and I have no idea why it’s included here except maybe to allow for the payment of rent but it doesn’t seem important so let’s move on.

We can look at theToken Program API to figure out how the calldata is structured. The Solscan entry indicates this is calling InitializeMint, as evidenced by the first byte, which is 0, corresponding to the 0-th function index in the Token Program instruction API enum (which, as I mentioned previously, is a u8 and not a u32because that’s what the Token Program authors wanted).

Calldata:

[
0, 2, 99, 232, 238, 67, 168, 88, 48, 19, 186, 111,
239, 92, 9, 24, 31, 38, 114, 13, 6, 33, 222, 190,
140, 87, 234, 244, 237, 135, 231, 4, 132, 59, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0
]

The first 0 is the index of the function in the program’s API enum set. In this case that is InitializeMint (see how it’s the first enum index here).

The second byte is 2 , which is the u8 indicating the number of decimals for our token. Note that there must be a fairly small limitation on decimal precision because token transfer amounts are u64 types, but I’m not sure what that limit is or how it is enforced — that’s all program-specific stuff.

The next 32 bytes are the mint_authority , which is just the account who is creating this initial mint of tokens. Here this is my wallet account 7j1NJDkeg35gtRt4nco9fN4wak6M6rD16okMYgvLJeHp.

The final 32 bytes are empty and correspond to an optional freeze_authority , which is I assume the account which can freeze transfers of this token. Well not with our token — our token is permissionless!

Instruction #2 Summary:

We called InitializeMint from the Token Program to let it know where we will be minting tokens. We referenced outside programs in the keys of this instruction: our mint account (created in instruction #1) and SysvarRent. Again, no idea what the latter is doing here.

We let the Token Program know our token will have 2 decimals and that the recipient account of the token mint will be owned by my “authority” account, which is my SOL wallet account. We also let it know there is no account which can freeze this token instance.

— Instruction #3/5 —

Moving on… we will now create another account using System Program. Looking at our keys we see the first one is the same as before (my 7j1N.. wallet account) while the second one is new:

HJyjV883iDC3C3vBkqSXiRqCrmFWsk9ER3MFc3KqgF8f

Quick run through of the calldata:

First four bytes are 0 again. We already covered this. We’re calling CreateAccount (function index 0).

lamports is 2039280.

space is 165 bytes.

owner is, once again, TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA.

Okay so we created another account owned by Token Program with 165 bytes of space instead of 82 bytes. If you’re wondering why the lamport/space ratio is different for these two accounts we created (2039280 / 165 = 12359, 1461600 / 82 = 17824)… I’m sorry, I don’t have an answer for you. I guess they’re directionally correct?

Summary of Instruction #3

Basically this was just the same as instruction #1. We created another account at HJyjV883iDC3C3vBkqSXiRqCrmFWsk9ER3MFc3KqgF8f with more storage and the same owner. This shows how different program-owned accounts can interact with (and have state updated by) different parts of the program’s code, so in Solana the program:program-owned-account relationship is 1:many.

— Instruction #4/5 —

We are now going to make another instruction call to TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA. This time our calldata is much shorter:

[ 1 ]

This means we’re calling function at index 1, which must have no arguments. This function is called InitializeAccount (reference).

With no real calldata, the essence of this instruction is just which accounts are being passed in. Let’s look at our keys for this instruction:

HJyjV883iDC3C3vBkqSXiRqCrmFWsk9ER3MFc3KqgF8f (!signer, writable)
Hh1E4LwRxAiJdLV4gzKX3BpdbNXJ2AYVdv8UDrfTZAbL (!signer, !writable)
7j1NJDkeg35gtRt4nco9fN4wak6M6rD16okMYgvLJeHp (!signer, !writable)
SysvarRent111111111111111111111111111111111 (!signer, !writable)

I’ll just paste the Token Program documentation:

///   0. `[writable]`  The account to initialize.    
/// 1. `[]` The mint this account will be associated with.
/// 2. `[]` The new account's owner/multisignature.
/// 3. `[]` Rent sysvar

We have some new terminology to associate with these accounts:

  • HJyjV883iDC3C3vBkqSXiRqCrmFWsk9ER3MFc3KqgF8f is the second account we created (instruction 3). This is the token account whose state we are initializing. It needs to be writable to do something with the state data. This is the account that will receive the minted tokens.
  • Hh1E4LwRxAiJdLV4gzKX3BpdbNXJ2AYVdv8UDrfTZAbL is the first account we created (instruction 1). This is the “token mint” account, which is the account that holds information about this token instance. We already set up the state on this account in instruction 2.
  • 7j1NJDkeg35gtRt4nco9fN4wak6M6rD16okMYgvLJeHp is the owner of this holding account. The best way to think about this now is that it’s my SOL account. It can hold SOL and make signatures but it cannot hold tokens. I need a separate token holding account for that, which is the one we are initializing here.
  • The rent sysvar account is here again. I still don’t know why but I guess the answers lie in the Solana system internals which I won’t worry about right now.

Instruction #4 Summary

We basically just used Token Program to define the account we will be minting tokens to and referenced the mint account, which is a different account with a different structure for its state data. At this point, we are ready to mint!

— Instruction #5/5 —

In the final instruction we call Token Program one last time. Our calldata is:

[7, 232, 3, 0, 0, 0, 0, 0, 0]

We’re calling function #7, which is MintTo (reference). There’s one u64 param here called amount — pretty self explanatory. We’re minting 1000 token units and since there are 2 decimals, we are minting 10 whole tokens.

Reference code comment:

///   * Single authority    
/// 0. `[writable]` The mint.
/// 1. `[writable]` The account to mint tokens to.
/// 2. `[signer]` The mint's minting authority.

Instruction keys:

Hh1E4LwRxAiJdLV4gzKX3BpdbNXJ2AYVdv8UDrfTZAbL (!signer, writable)
HJyjV883iDC3C3vBkqSXiRqCrmFWsk9ER3MFc3KqgF8f (!signer, writable)
7j1NJDkeg35gtRt4nco9fN4wak6M6rD16okMYgvLJeHp (signer, !writable)

We are signing off on this transfer from the owner account (7j1N..). We are minting from our “token mint” account Hh1E... and sending those tokens to our new “token holding” account HJyj.... Both of these need to be marked as writable in this instruction because their state data is being modified.

Instruction #5 Summary

We finally did a thing! We minted 1000 token units from our mint account and transferred them to our token holding account. At this point we can spend those tokens from our holding account using our SOL account as a signer since it was defined as the owner of this holding account in program space (instruction #4).

Aside: Although specific program code is outside the scope of this exercise, it’s important to note that the mint owner account also needs to remove itself as an authority in a separate instruction/transaction or else it can continue minting tokens (credit).

— signers —

I won’t cover this in much detail, but basically at this point you would take the set of instructions, metadata, signing keys, etc and serialize a “message”. You would then hash this message and sign it from all the necessary accounts (remember that when you create any account you have its private key). This set of signing keys can be determined by finding all unique accounts that are marked with isSigner=true in at least one instruction. For this example, the funder (my SOL account), token mint, and token holder accounts are all signers.

The signatures part of the above JSON payload contains three signatures associated with three different addresses:

63e8ee43a8583013ba6fef5c09181f26720d0621debe8c57eaf4ed87e704843b
f7fa7a4d4e8dda19a3afbec13a5fb4a540289b869b6a5a46162b7c3e3d9161ab
f2560320922ee24d0086e5769e8b3cdf261c646f4f17184532ec7380220b980c

In base58 these are, respectively:

7j1NJDkeg35gtRt4nco9fN4wak6M6rD16okMYgvLJeHp
Hh1E4LwRxAiJdLV4gzKX3BpdbNXJ2AYVdv8UDrfTZAbL
HJyjV883iDC3C3vBkqSXiRqCrmFWsk9ER3MFc3KqgF8f

You should recognize all of these by now. If you go through the JSON object you should be convinced that this is the full set of accounts marked with isSigner=true in at least instruction. Also if you open the Solscan record you will see those same addresses. ✅

Summary

If you made it this far, I hope it means you found this somewhat useful and now know more about Solana than you did before. Sweet, me too.

Here is an unstructured list of learnings I took from this exercise:

  • “Wallet” accounts can’t actually do much directly; they can hold SOL, interact with system-level programs (e.g.System Program), and act as authorities for program-owned accounts (linked in program space). In this example, we created a token holding account that the Token Program considers as “owned” by my SOL account (instruction #4) — this is what I mean by an “authority” and this link only exists in program space, i.e. not at the Solana system level.
  • You have to pay rent to fill accounts with data, though the exact process is not really exposed at the transaction level. Rent is proportional to the amount of storage taken up by the account. If you pay some arbitrary amount (2 years worth) up front your program will live on forever. Otherwise it will disappear when it can no longer pay rent.
  • Instructions can be packaged together in an atomic transaction and don’t need to relate to one another. We could have also thrown in an instruction to Transfer SOL to an unrelated account if we wanted.
  • Program code functions are indexed by an enum. Each instruction has its own calldata buffer whose first parameter is always an unsigned integer corresponding to the index of the function to call. Strangely, this integer can be whatever size the program writer wants (u8-u64).
  • Calldata doesn’t need to be ABI encoded like in Ethereum and Solana does not use 256-bit words, so payloads generally end up being fairly short.
  • Solana’s execution environment is probably more efficient than the EVM, but to me it seems the real performance benefit comes from the fact that you don’t need to reference the most recent transaction hash in your transaction — you just need a recent one. This means you can parallelize transactions that don’t affect the same state and as a result it seems very common to split state across multiple accounts that are all owned by the same program.

Overall, Solana’s architecture is very different than Ethereum’s. It’s certainly more confusing but I can see it having advantages as well. I’m glad to have gone through this exercise and I think it’s a neat system. I hope I have the opportunity to learn more about it!

--

--

Alex Miller

Developer/writer/thinker living in the cryptoverse. Co-founder of GridPlus