#9 - Walk Through Solana SDK Design
Author: @SaiyanBs, @wei_sol_, @emersonliuuu
[Updated at 2022.06.22]
TL; DR
Why is SDK design important?
Usage - frontend
Performance
As connector between client side and programs
How to design SDK under Solana architecture
Compare between Bad & Good design
Overview
Get data and show it on the client side.
How to Get the Data
web 2
Get data by API.
Mostly data is from the same or partnership company.
web 3
Get data by SDK.
Mostly need to get the on-chain data or others project open sourced SDK.
Example
How to Show the Data on the Client Side
Basically it's the same no matter it's web2 or web3. Nowadays most frontend developers choose frameworks to get the job done, and for now and especially on Solana, I believe Next.js is the best option.
Show it on the client side.
For instance, using Next.js, we need to split components like puzzles, split the whole page into pieces, and each piece may contain the content (layout and data).
And how we design the components depends on
layout (basically follows the design)
data flow
performance
maintenance (low coupling, readable)
how SDK or API design
Sometimes these points can against each other, so I feel like designing the components and data flow is more like an art, may need to sacrifice some points to achieve another one, so just choose a better way at the moment you did this.
For Instance, NFT staking page in Dappio


Discuss
What's the same part and different part between two pics ?
Different data by different project.
The pending reward card both exist in DappieGang and others project but with different layouts and position.
DappieGang has one utility row and filter part, but the others don't.
What's the similar funtion between two pics ?
Get staked info
Get pending reward
Claim pending reward
Stake / unstaked
What do we need to consider?
Let's check the first part - Overview info

As we can see, NFTs staked in pools which are accounts, so we need to know the address to get the account data.
How do we get the pool public key in v1 ? We hardcoded all of them.
Pending Reward
Then, it's the pending reward part which also exists in DappieGang's second row.

First thing we need to know is where's the reward from? The only reason we can get the NFTU is because we deposit our prove token into farm and farming/mining, so we need to know the farm's account to get the infos we need.
But where's the prove token from? The flow is
We stake our NFTs into pools get prove token.
We deposit our prove tokens to get farming token and mining NFTU.
So we need to know which pool to stake first, because differnt NFTs rarity stake to different pools, and this is defined by initialization, and all the rarity info also stores on chain.
As we can see, just only the pending rewards could make tons of RPC requests. And here we only go through the funtional parts, as a frontend developer, you need to deal with the component design to make it maintainable, readable, and make sure the performance won't destroy the user experience at the same time.

Example - V2 version
Problems
Hard code ID in SDK
Which means once we add new category we will need to update SDK too.
And here's only part of the DappieGang infos, so as we partnership more projects, it'll end up become a file that you don't want to involved.

Too many RPC request As we can see, in the pending rewards part, we need to call a tons of RPC requests to get the data we want, and it's just the pending reward part.
Maintainance Again, by the pending reward example we know, you have to call functions one by one and sometimes not very intuitive, especially from the client side.
From a frontend developer's view, we need to deal with a lot of for loop, sync/async issues, components design, state management.
From a team member's view, with a poor SDK design, we need to setup a clear workflow for different roles (frontend, SDK, program).
For example, the hardcoded pool and farm infos, which side to store all of these infos, and when to update the file after initializing a new one, how to maintain this file ..etc
Goal: Design the Most Friendly SDK or API for Frontend
Stateless, less parameters or arguments.
Call anywhere we want, no need to consider the context.
Good SDK Design
Prerequisites
Solana system model
Account model
Solana system model

Reading from solana starts with sending a http request to the RPC, and the data is sync from validator.
Writing data(sending transaction) to Solana is also first sent to RPC, the RPC will lookup the leader and pass the tx packet to it via a UDP request. The transaction will next be verified and processed.
Each instructions will be executed in the program, modifying account data. New block contained state changes will be sync across validators and voted.
Storing data in Solana

Image from https://paulx.dev/blog/2021/01/14/programming-on-solana-an-introduction/
Rule of Thumb
1. SDK Architecture

This is an overview of a good SDK design. It can be separated into two part, read and write.
The "read" part of the SDK is about fetching the data from RPC and deserialized into an object.
The "write" part of the SDK is to create a transaction object to interact with programs.
Utility, layouts and ids is used across reading and writing.
2. Reduce RPC request
Each Account data only needs to fetch once.
Reduce the usage of
Connection.
3. Stateless design
SDK is for reading/writing data to Solana.
To read form the chain, account needs to be fetched and deserialized.
To write to the chain, a TX is built and send.
SDK only handles data decoding and tx building.
Data is not modified by any functions/methods.
NFT Staking Program

A program that give out
Prove Tokenby locking certain collections of NFT.NFT is stored separately in different Vaults.
Mint list is manage by rarity program.
NFT Staking Program (Implement)
Find the full code base here
Program Architecture
Setup Local Test Validator
Restart local validator, and clone a useful program for creating ATA from Mainnet. See more about the program here
Configure RPC url to localnet and the wallet to the one for deploying. Make sure the wallet have enough balance (~ 10 SOL)
Build And Deploy
Clone the code from github repo
Install dependency and make sure you have replace program keys for all files below.
programs/nft-rarity/src/lib.rsprograms/nft-staking/src/lib.rsts/v1/ids.tsts/v2/ids.tsAnchor.toml
After replacing program keys, we are ready to build and deploy our program with command below.
NOTICE
Make sure the Program Id you get after deployed match with the one in program declare_id!("6Utx...QnKM"), otherwise transaction we send might failed.
SDK v1
Before we start staking NFT to program will need to initialize the allowed mint list to rarity info in rairty program, then initialize pool info with the rarity info key we just initialized.
DEBUG
If you have some error message like something below, run yarn add ts-mocha might solve. Thanks to this post.
Import library and declare variables
After initialization, we can implement staking part in test/v1/1_nft-staking-v1.ts, let's paste the code below to the file.
Add log before and after staking
Implement stake/unstake
Now, we are good to implement staking part! Let's think about what we need for building stake transaction.

Arguments we need for staking:
staking pool info
pool info key
prove token mint
prove token authority
...
user info
user address
NFT mint
prove token ATA

Pool info key is one of the argument we're going to use later, so we need to generate it first.
Add stake and unstake test.
Now, we can run command below to see all test works well or not.
Implement stake/unstake transaction in SDK
After running test, you might found we didn't actually stake our NFT to program since we didn't implememt the logic for stake and unstake in SDK yet. Let's add the logic in ts/v1/transaction.ts and run the test again.
Stake
Unstake
Run command again, then you can stake all your NFT to the program now!
SDK v2
You might notice there are some problems while we implementing stake/unstake.
We need to know corresponding pool info key before we use it, and there are two way to get the key. One is generate with the seed as we done previous, and another is hard coded in SDK. Someone who used this SDK won't know the seed, so pool info key might need to hard coded in SDK and this result in frequently update SDK due to new partner joined.
Since we only have pool info key, we must fetch account info when building transaction. You can imagine if user is going to stake lots of NFT in different pool that might be a disaster for just building transaction by sending tons of RPC request.
Refactor SDK
In order to solve the issues above, we will try to
Make stake/unstake transaction/instruction stateless
Remove hard coded stuff
Let's take a closer look at v1 SDK, the root cause of why we need to make multiple RPC request comes from the bad design of the interface. If we only had pool info key associated with PoolInfo, we would force fetching data in every function that requires PoolInfo data except the key. Actually, we can extract the fetching data from building transaction by implementing a fetch function, so called fetchAll(), to get all account we need for no matter matching NFT with pool or building transaction.
This is what fetchAll() function do, can see the AllInfo class definition in ts/v2/poolInfos.ts
After implementing fetchAll() with some class to store the data, now we only fetch data at the beginning by calling fetchAll(), then we can pass the data as an argument for building transaction.

Implement test with v2 (refactored) SDK
Open tests/v2/1_nft-staking-v2.ts you will see the code below (no need to do any modification). You can see that we don't need to generate pool info key first neither hard coded those keys in SDK now, we get all data with this line of code: allInfos = await nftFinanceSDK.fetchAll(provider);
Run command below to make sure no issue occured.
Implement stake/unstake transaction in SDK v2
Once all test passed, let's add the logic for stake and unstake transaction in ts/v2/transaction.ts. Since we pass in full pool info data instead of only the key, we can remove all RPC request during transaction building.
Stake
Unstake
Run command below again and now we successfully use refactored SDK to stake NFT!
Difference Between v1 and v2 SDK
How we get account data
In v1 we fetch one account since we only get one pool info key at a time, but in v2 we done the fetching part at the beginning, and fetch all account with same structure in one RPC request.
Where we get account data
In v1 we hard coded the account address in SDK instead of storing data in client side by fetching all account we need at a time. In v2 we implement with the opposite way, which reduce the frequency of fetching same account in different function.
Maintainability
In v1 we'll need to update the hard coded stuff if new partner joined or we add new category which is tough to maintain. In v2, by replacing hard coded stuff with storing class in client side, there's no need to modify SDK due to new pool been created.
Reference
Last updated