🌞
Solana Chain analysis Program && Web3 API
August 01, 2022
Solana analysis: Program && Web3 API
import type {NextApiRequest, NextApiResponse} from 'next';
import {getNodeURL} from '@figment-solana/lib';
import {Connection} from '@solana/web3.js';
export default async function connect(
req: NextApiRequest,
res: NextApiResponse<string>,
) {
try {
const {network} = req.body;
const url = getNodeURL(network);
const connection = new Connection(url);
const version = await connection.getVersion();
res.status(200).json(version['solana-core']);
} catch (error) {
let errorMessage = error instanceof Error ? error.message : 'Unknown Error';
res.status(500).json(errorMessage);
}
}
- 3party JSON RPC connections (web3 provider)
- → netowkr object instance to getNodeURL ⇒ url parser
- @figment-solana/lib
- → Connection input url ← solana-core
- @solana/web3.js
generate key pair
import type {NextApiRequest, NextApiResponse} from 'next';
import {Keypair} from '@solana/web3.js';
type ResponseT = {
secret: string;
address: string;
};
export default function keypair(
_req: NextApiRequest,
res: NextApiResponse<string | ResponseT>,
) {
try {
const keypair = new Keypair();
const address = keypair.publicKey.toString();
const secret = JSON.stringify(Array.from(keypair.secretKey));
res.status(200).json({
secret,
address,
});
} catch (error) {
let errorMessage = error instanceof Error ? error.message : 'Unknown Error';
res.status(500).json(errorMessage);
}
}
- Ed25519 Crypto Alg use
- pubKey, PrivKey
SOL balance (mainnet, testnet)
- network provider name ⇒ token name diffirent
Air Drop
- Account Provider to Balance ⇒ Air drop
1 SOL = 1,000,000,000 lamports
- Arid Drop reques
import {Connection, PublicKey, LAMPORTS_PER_SOL} from '@solana/web3.js';
import type {NextApiRequest, NextApiResponse} from 'next';
import {getNodeURL} from '@figment-solana/lib';
export default async function fund(
req: NextApiRequest,
res: NextApiResponse<string>,
) {
try {
const {network, address} = req.body;
const url = getNodeURL(network);
const connection = new Connection(url, 'confirmed');
const publicKey = new PublicKey(address);
const hash = await connection.requestAirdrop(publicKey, LAMPORTS_PER_SOL);
await connection.confirmTransaction(hash);
res.status(200).json(hash);
} catch (error) {
let errorMessage = error instanceof Error ? error.message : 'Unknown Error';
res.status(500).json(errorMessage);
}
}
- PublicKey(secretkey) ⇒ pub key generate
- Connection.requestAirdrop(pubkey, LAMPORTS_PER_SOL)
- LAMPORTS_PER_SOL is 1 SOL == 1,000,000,000
- and… confirmTransaction await timming
User Balance Check
import type {NextApiRequest, NextApiResponse} from 'next';
import {Connection, PublicKey} from '@solana/web3.js';
import {getNodeURL} from '@figment-solana/lib';
export default async function balance(
req: NextApiRequest,
res: NextApiResponse<string | number>,
) {
try {
const {network, address} = req.body;
const url = getNodeURL(network);
const connection = new Connection(url, 'confirmed');
const publicKey = new PublicKey(address);
const balance = await connection.getBalance(publicKey);
if (balance === 0 || balance === undefined) {
throw new Error('Account not funded');
}
res.status(200).json(balance);
} catch (error) {
let errorMessage = error instanceof Error ? error.message : 'Unknown Error';
res.status(500).json(errorMessage);
}
}
- account address ⇒ publickey
- getBaalnce(pub key) check
SOL Transfer
- Balance ⇒ Other Sigined Account (Cluster)
- Transaction Cluster, Solana Runtime
- lamport ? 0 check
import type {NextApiRequest, NextApiResponse} from 'next';
import {getNodeURL} from '@figment-solana/lib';
import {
Connection,
PublicKey,
SystemProgram,
Transaction,
sendAndConfirmTransaction,
} from '@solana/web3.js';
export default async function transfer(
req: NextApiRequest,
res: NextApiResponse<string>,
) {
try {
const {address, secret, recipient, lamports, network} = req.body;
const url = getNodeURL(network);
const connection = new Connection(url, 'confirmed');
const fromPubkey = new PublicKey(address);
const toPubkey = new PublicKey(recipient);
const secretKey = Uint8Array.from(JSON.parse(secret as string));
const instructions = SystemProgram.transfer({
fromPubkey,
toPubkey,
lamports,
});
const signers = [
{
publicKey: fromPubkey,
secretKey,
},
];
const transaction = new Transaction().add(instructions);
const hash = await sendAndConfirmTransaction(
connection,
transaction,
signers,
);
res.status(200).json(hash);
} catch (error) {
let errorMessage = error instanceof Error ? error.message : 'Unknown Error';
res.status(500).json(errorMessage);
}
}
SystemProgram
- solana-web3.js
- Factory class for transaxtions to interact with the system program
SystemProgram.transfer
- generate a transaction instruction
const instructions = SystemProgram.transfer({
fromPubkey,
toPubkey,
lamports,
});
generate signers object (array’s in pub key, priv key)
const signers = [
{
publicKey: fromPubkey,
secretKey,
},
];
transaction instance (generate, add, …)
const transaction = new Transaction().add(instructions);
- add in transaction factory instance
send
const hash = await sendAndConfirmTransaction(
connection,
transaction,
signers,
);
program deploy (with simple Contract lib.rs)
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint,
entrypoint::ProgramResult,
msg,
program_error::ProgramError,
pubkey::Pubkey,
};
- borsh
- Binary Object Representation Serializer for Hashing
- crate, solana_program
- account_info
- entrypoint
- ProgramResult
- msg
- logging
- program_error
- ProgramError
- On-Chain Program Specifiy Error return (Solana Runtime)
- pubkey
- Pubkey
GreetingAccount Struct
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct GreetingAccount {
pub counter: u32,
}
- derive macro is Compile-time useful BorshSerialze, BorshDeserialize, Debug
- Struct Member
- counter : u32
Program EntryPointer Settings
// Declare and export the program's entrypoint
entrypoint!(process_instruction);
- program run-time entrypoiter settings ⇒ fuction pointer call
process_instruction
// Program entrypoint's implementation
pub fn process_instruction(
program_id: &Pubkey, // Public key of the account the hello world program was loaded into
accounts: &[AccountInfo], // The account to say hello to
_instruction_data: &[u8], // Ignored, all helloworld instructions are hellos
) -> ProgramResult {
msg!("Hello World Rust program entrypoint");
// Iterating accounts is safer than indexing
let accounts_iter = &mut accounts.iter();
// Get the account to say hello to
let account = next_account_info(accounts_iter)?;
// The account must be owned by the program in order to modify its data
if account.owner != program_id {
msg!("Greeted account does not have the correct program id");
return Err(ProgramError::IncorrectProgramId);
}
// Increment and store the number of times the account has been greeted
let mut greeting_account = GreetingAccount::try_from_slice(&account.data.borrow())?;
greeting_account.counter += 1;
greeting_account.serialize(&mut &mut account.data.borrow_mut()[..])?;
msg!("Greeted {} time(s)!", greeting_account.counter);
Ok(())
}
- entrypoint’s implementation
- retrun value ⇒ process_instruction entrypoint (ProgramResult {})
- parms
program_id: &Pubkey,
- refrence ← Account Pubkey
accounts: &[AccountInfo],
- Iterator Refrence (accoutns_iter)
- next_account_info(account_iter)? ⇒ AccountInfo or NotEnoughAccountKeys error
// Iterating accounts is safer than indexing
let accounts_iter = &mut accounts.iter();
// Get the account to say hello to
let account = next_account_info(accounts_iter)?;
instructiondata: &[u8],
- iterator Refrence
// The account must be owned by the program in order to modify its data
if account.owner != program_id {
msg!("Greeted account does not have the correct program id");
return Err(ProgramError::IncorrectProgramId);
}
- accounts.owner check
- public key does not equal the program_id
// Increment and store the number of times the account has been greeted
let mut greeting_account = GreetingAccount::try_from_slice(&account.data.borrow())?;
greeting_account.counter += 1;
greeting_account.serialize(&mut &mut account.data.borrow_mut()[..])?;
msg!("Greeted {} time(s)!", greeting_account.counter);
Ok(())
- GreetingAccount Struct ⇒ Field : counter: u32\
- &account.data.borrow() ⇒ Ref ⇒ try_from_slice
- Deserialized and slice (byte)
- BorshDeserialize ⇒ try_from_slice() ⇒ account.data (mut Ref)
- serialize(&mut &mut account.data.borrow_mut()[..])?
- mutable borrow ⇒ account.data ⇒ all data
- &mut
- &mut ⇒ Serialize
Solana cli (air drop, pub keypair)
☁ app [main] ⚡ solana-keygen new --outfile solana-wallet/keypair.json
Generating a new keypair
For added security, enter a BIP39 passphrase
NOTE! This passphrase improves security of the recovery seed phrase NOT the
keypair file itself, which is stored as insecure plain text
BIP39 Passphrase (empty for none):
Wrote new keypair to solana-wallet/keypair.json
=====================================================================
pubkey: <PUB-KEY>
=====================================================================
Save this seed phrase and your BIP39 passphrase to recover your new keypair:
jelly panel immense gadget task cat ship paper knock gap prevent just
=====================================================================
☁ app [main] ⚡ solana airdrop 1 $(solana-keygen pubkey solana-wallet/keypair.json)
Requesting airdrop of 1 SOL
Signature: <Signature>
Deploy a Solana Program
Building the program
yarn run solana:build:program
"solana:build:program": "cargo build-bpf --manifest-path=contracts/solana/program/Cargo.toml --bpf-out-dir=dist/solana/program",
.so
file is shared object file
$ solana program deploy /Users/zer0luck/project/app/dist/solana/program/helloworld.so
Deploying the program
☁ Contract [main] ⚡ solana deploy -v --keypair solana-wallet/keypair.json dist/solana/program/helloworld.so
RPC URL: <https://api.devnet.solana.com>
Default Signer Path: solana-wallet/keypair.json
Commitment: confirmed
Program Id: <ProgramID>
import type {NextApiRequest, NextApiResponse} from 'next';
import {Connection, PublicKey} from '@solana/web3.js';
import {getNodeURL} from '@figment-solana/lib';
import path from 'path';
import fs from 'mz/fs';
const PROGRAM_PATH = path.resolve('dist/solana/program');
const PROGRAM_SO_PATH = path.join(PROGRAM_PATH, 'helloworld.so');
export default async function deploy(
req: NextApiRequest,
res: NextApiResponse<string | boolean>,
) {
try {
const {network, programId} = req.body;
const url = getNodeURL(network);
const connection = new Connection(url, 'confirmed');
// Re-create publicKeys from params
const publicKey = new PublicKey(programId);
const programInfo = await connection.getAccountInfo(publicKey);
if (programInfo === null) {
if (fs.existsSync(PROGRAM_SO_PATH)) {
throw new Error(
'Program needs to be deployed with `solana program deploy`',
);
} else {
throw new Error('Program needs to be built and deployed');
}
} else if (!programInfo.executable) {
throw new Error(`Program is not executable`);
}
res.status(200).json(true);
} catch (error) {
let errorMessage = error instanceof Error ? error.message : 'Unknown Error';
res.status(500).json(errorMessage);
}
}
- Deploy Contract ⇒ programId ⇒ PublicKey address
- Connection.getAccountInfo(pubkey) ⇒ programInfo ⇒
Progmra Storage Create
- Solana Program non-storage state
import {
Connection,
PublicKey,
Keypair,
SystemProgram,
Transaction,
sendAndConfirmTransaction,
} from '@solana/web3.js';
import type {NextApiRequest, NextApiResponse} from 'next';
import {getNodeURL} from '@figment-solana/lib';
import * as borsh from 'borsh';
// The state of a greeting account managed by the hello world program
class GreetingAccount {
counter = 0;
constructor(fields: {counter: number} | undefined = undefined) {
if (fields) {
this.counter = fields.counter;
}
}
}
// Borsh schema definition for greeting accounts
const GreetingSchema = new Map([
[GreetingAccount, {kind: 'struct', fields: [['counter', 'u32']]}],
]);
// The expected size of each greeting account.
const GREETING_SIZE = borsh.serialize(
GreetingSchema,
new GreetingAccount(),
).length;
type ResponseT = {
hash: string;
greeter: string;
};
export default async function greeter(
req: NextApiRequest,
res: NextApiResponse<string | ResponseT>,
) {
try {
const {network, secret, programId: programAddress} = req.body;
const url = getNodeURL(network);
const connection = new Connection(url, 'confirmed');
const programId = new PublicKey(programAddress);
const payer = Keypair.fromSecretKey(new Uint8Array(JSON.parse(secret)));
const GREETING_SEED = 'hello';
// Are there any methods from PublicKey to derive a public key from a seed?
const greetedPubkey = await PublicKey.createWithSeed(
payer.publicKey,
GREETING_SEED,
programId,
);
// This function calculates the fees we have to pay to keep the newly
// created account alive on the blockchain. We're naming it lamports because
// that is the denomination of the amount being returned by the function.
const lamports = await connection.getMinimumBalanceForRentExemption(
GREETING_SIZE,
);
// Find which instructions are expected and complete SystemProgram with
// the required arguments.
const transaction = new Transaction().add(
SystemProgram.createAccountWithSeed({
fromPubkey: payer.publicKey,
basePubkey: payer.publicKey,
seed: GREETING_SEED,
newAccountPubkey: greetedPubkey,
lamports: lamports,
space: GREETING_SIZE,
programId,
}),
);
// Complete this function call with the expected arguments.
const hash = await sendAndConfirmTransaction(connection, transaction, [
payer,
]);
res.status(200).json({
hash: hash,
greeter: greetedPubkey.toBase58(),
});
} catch (error) {
let errorMessage = error instanceof Error ? error.message : 'Unknown Error';
res.status(500).json(errorMessage);
}
}
PublicKey Generate
const programId = new PublicKey(programAddress);
const payer = Keypair.fromSecretKey(new Uint8Array(JSON.parse(secret)));
const GREETING_SEED = 'hello';
// Are there any methods from PublicKey to derive a public key from a seed?
const greetedPubkey = await PublicKey.createWithSeed(
payer.publicKey,
GREETING_SEED,
programId,
);
- ProgramId, payer publickey, random Seed ⇒ PublicKey
const lamports = await connection.getMinimumBalanceForRentExemption(
GREETING_SIZE,
);
// Find which instructions are expected and complete SystemProgram with
// the required arguments.
const transaction = new Transaction().add(
SystemProgram.createAccountWithSeed({
fromPubkey: payer.publicKey,
basePubkey: payer.publicKey,
seed: GREETING_SEED,
newAccountPubkey: greetedPubkey,
lamports: lamports,
space: GREETING_SIZE,
programId,
}),
);
- createAccountWithSeed call
- fromPubKey ⇒ Create Account ⇒ lamports Transaction
- basePubKey ⇒ Create Account Address (newAccountPubKey create use basekey)
- seed: Created Account Address use sedd
- lamports : Created Account deposit lamport Quantity (GREETING_SIZE latest balance)
- space: Create Account Alloc Space(Byte), storage
- programID : Created Account Owner Program Pub Key
- newAccountPubKey : Created Pub key ← PublicKey.createWithSeed()
Program getter data
- buffer in data storage
- Data Access ⇒ Data extract ⇒ Well-Defination Struct
import type {NextApiRequest, NextApiResponse} from 'next';
import {Connection, PublicKey} from '@solana/web3.js';
import {getNodeURL} from '@figment-solana/lib';
import * as borsh from 'borsh';
// The state of a greeting account managed by the hello world program
class GreetingAccount {
counter = 0;
constructor(fields: {counter: number} | undefined = undefined) {
if (fields) {
this.counter = fields.counter;
}
}
}
// Borsh schema definition for greeting accounts
const GreetingSchema = new Map([
[GreetingAccount, {kind: 'struct', fields: [['counter', 'u32']]}],
]);
export default async function getter(
req: NextApiRequest,
res: NextApiResponse<string | number>,
) {
try {
const {network, greeter} = req.body;
const url = getNodeURL(network);
const connection = new Connection(url, 'confirmed');
const greeterPublicKey = new PublicKey(greeter);
const accountInfo = await connection.getAccountInfo(greeterPublicKey);
if (accountInfo === null) {
throw new Error('Error: cannot find the greeted account');
}
// Find the expected parameters.
// const value = new Test({ x: 255, y: 20, z: '123', q: [1, 2, 3] });
const greeting = borsh.deserialize(
GreetingSchema,
GreetingAccount,
accountInfo.data,
);
// A little helper
console.log(greeting);
// Pass the counter to the client-side as JSON
res.status(200).json(greeting.counter);
} catch (error) {
let errorMessage = error instanceof Error ? error.message : 'Unknown Error';
console.log(errorMessage);
res.status(500).json(errorMessage);
}
}
- on-chain data(serialize) ⇒ borsh.deserialize(schema, class, buffer) ⇒ data
Program send data
import {
Connection,
PublicKey,
Keypair,
TransactionInstruction,
Transaction,
sendAndConfirmTransaction,
} from '@solana/web3.js';
import type {NextApiRequest, NextApiResponse} from 'next';
import {getNodeURL} from '@figment-solana/lib';
export default async function setter(
req: NextApiRequest,
res: NextApiResponse<string>,
) {
try {
const {greeter, secret, programId, network} = req.body;
const url = getNodeURL(network);
const connection = new Connection(url, 'confirmed');
const greeterPublicKey = new PublicKey(greeter);
const programKey = new PublicKey(programId);
const payerSecretKey = new Uint8Array(JSON.parse(secret));
const payerKeypair = Keypair.fromSecretKey(payerSecretKey);
// this your turn to figure out
// how to create this instruction
const instruction = new TransactionInstruction({
keys: [{pubkey: greeterPublicKey, isSigner: false, isWritable: true}],
programId: programKey,
data: Buffer.alloc(0), // All instructions are hellos
});
const hash = await sendAndConfirmTransaction(
connection,
new Transaction().add(instruction),
[payerKeypair],
);
res.status(200).json(hash);
} catch (error) {
console.error(error);
res.status(500).json('Get balance failed');
}
}
const instruction = new TransactionInstruction({
keys: [{pubkey: greeterPublicKey, isSigner: false, isWritable: true}],
programId: programKey,
data: Buffer.alloc(0), // All instructions are hellos
});
const hash = await sendAndConfirmTransaction(
connection,
new Transaction().add(instruction),
[payerKeypair],
);
- new TransactionInstruction instance
- recepient pub key , isWritable : true
- programId
Other Resources
- 🏗 The Solana dapp-scaffold
- ⚓️ Project Serum’s Anchor framework
- 📚 A complete guide to full-stack Solana development