- Smart contract = Programs
- BPF bytecode -> Blockchain
- Program = Program ID(PUBKEY) 로 구분
- STATELESS (Unlike Ethereum)
- NO DATA STORED
- Persistent STroage? Use accounts!
- Holds Data
- SOL Tokens (Lamports 1 SOL = 1 billion lamports)
- Executasble OR NOT
- Data modification? = Must be Owner
- Fund -> Create accounts -> Transfer Ownership to the program
- 1 Account -> Argument
- Greeting Count
- Verfies the account owner!
- Desirialize the data ->
Greeting Accountstructure - Increment the counter
- Re-Serialize
- Print a message!
use borsh::{BorshDeserialize, BorshSerialize}; // 시리얼리제이션
// Solana
use solana_program::{
account_info::{next_account_info, AccountInfo}, // READ - Account INFOs
entrypoint, entrypoint::ProgramResult, // ENTRY POINT
msg, // PRINT (LOG)
program_error::ProgramError,
pubkey::Pubkey,
};
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct GreetingAccount {
//Unsigned 32bit
//# of greetings
//ITTRs
pub counter: u32,
}
// Entrypoint
// Function called when the program is executed
entrypoint!(process_instruction);
// The entrypoint function signature
// Solana runtime calls this when the program executes
pub fn process_instruction(
program_id: &Pubkey, // Public key of this program (공개키)
accounts: &[AccountInfo], // Accounts passed in to the program (목적지)
_instruction_data: &[u8], // Serialized instruction data (명령어)
) -> ProgramResult {
msg!("JUN = Hello World program executed"); // Print a message to the program log (결과값)
// Iterate through the accounts
let accounts_iter = &mut accounts.iter();
let account = next_account_info(accounts_iter)?; // 1st Account from the list
// Owner 확인
// �Permission to write!!!!
if account.owner != program_id {
msg!("Account is not owned by this program");
return Err(ProgramError::IncorrectProgramId);
}
// Deserialize (account data) -> GreetingAccount struct
let mut greeting_data = GreetingAccount::try_from_slice(&account.data.borrow())?;
// Increment the counter
greeting_data.counter = greeting_data.counter + 1;
// Serialize (modified data) -> account
greeting_data.serialize(&mut &mut account.data.borrow_mut()[..])?;
msg!("Greeted {} time(s)!", greeting_data.counter);
Ok(())
}- Compile -> Deploy the PRORAM ON A NETWORK!
- Which network?
- Test : DEVNET
- Devnet need airdrops of SOL for free!
- Real : MAINNET(-BETA)
- Mainnet need REAL SOL for fees
- Test : DEVNET
https://solana.com/docs/intro/installation#DEVNET
solana config set --url https://api.devnet.solana.com
#MAINNET
solana config set --url https://api.mainnet-beta.solana.comsolana-keygen new --forceJSON -> Save into the file somewhere
solana airdrop 2- RUST
- Cargo is OK
# Cargo
cargo build-bpf --release
# Solana SDK
npm run build:program-rustsolana program deploy Users/Jun/Documents/testcode.so- Typescript + Solana-web3.js
import { Connection, Keypair, PublicKey, SystemProgram, Transaction } from '@solana/web3.js';
import { BN } from 'bn.js'; // Big number library for handling large integers
// 1. Connect to a Solana cluster
const connection = new Connection('https://api.devnet.solana.com', 'confirmed');
// Use 'confirmed' or ERROR
// 2. Generate a new Keypair for the account that will store the greeting count
const greetedAccount = Keypair.generate();
// 3. Define the program ID
const programId = new PublicKey('Program Id');
// 4. Calculate the required space
// (rent for the data account)
const GREETING_SIZE = 4; // u32 = which is 4 bytes
const rentExemption = await connection.getMinimumBalanceForRentExemption(GREETING_SIZE);
// 5. Create a transaction to create the account and call the program
let tx = new Transaction();
// 6. Add instruction to create the account [system program create account]
tx.add(SystemProgram.createAccount({
fromPubkey: payer.publicKey, // 1. funds the creation
newAccountPubkey: greetedAccount.publicKey, // 2. public key of the account TO BE CREATED
lamports: rentExemption, // 3. Amount of lamports ( fund rent exemption)
space: GREETING_SIZE, // 4. Space in bytes ( account data)
programId: programId, // 5. designate program as the owner of this account
}));
// 7. Add instruction to call the program's entrypoint
tx.add({
keys: [
{ pubkey: greetedAccount.publicKey, isSigner: false, isWritable: true }
],
programId: programId,
data: Buffer.alloc(0) // no instruction data needed for this program (we're just incrementing counter)
});
// 8. Sign and send the transaction
tx.feePayer = payer.publicKey;
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
tx.sign(payer, greetedAccount); // Sign with the payer
const txSignature = await connection.sendRawTransaction(tx.serialize());
console.log("Transaction sent, signature:", txSignature);- Priority Fee : Solana - Add @ Compute Budget Instruction - Tips : Jito (Out of Protocol - Special Program) - Minimum Tip (1000 lamports)
- Normal
- RPC Nodes + gossip network -> Leader's transaction queue
- Jito
- Jito Block Engine -> Current leader (Modified Client) - FASTER!!!
- Bundling transactions (1 , More)
- All-or-nothing
- Atomic multi-tx sequences NOT POSSIBLE w/o combining into 1 transaction
- Executions In order OR NOT
- Normal
- First come first serve
- NO Public Mempool
- Jito
- Private MEMPOOL
- Searchers submit transaction -> Jito Validator picks!
- Flashbot vs Jito
- Flashbot : All BLOCK AUCTION
- Jito : Partial Block auction
- bundleOnly=true
- Only land if they can be includedvia the bundles
- Will not simply fall back to the normal path
- Revert Protection
- Arbitrage -> Fail
- UNLESS certain conditions are met
- Not execute AT ALL
- UNless it's in the intended bundle ordering!
- Simulation
- "PROFIT" calculated by jito block engine!
- Arbitrage -> Fail
1. I want to transfer 0.001 SOL to Bob
2. Prioritize via Jito
3. Attach a tip + send it to the Jito Block engine
- SystemProgram.transfer
- Construct Instructions
- Sign ...
- ComputeBudgetProgram.setComputeUnitPrice({ microLamports: X })
- Higher Fee per compute unit
- Jito Validator accepts both normal fees and Jito tips.
- Include a SOL transfer to one of the Jito's tip accounts
- Jito RPC -
getTIPAccounts
- Jito RPC -
- After the transfer ->
Tip Distribution Program- Lamports -> Block Producer : MEV Rewards
- The fee payer signs the transaction as usual.
- NOT USING connection.sendTransaction
- Use Jito Block Engine's endpoint (RPC)
https://<region>.block-engine.jito.wtf/api/v1/transactions
HAS TO BE JSON!
- sendTransaction with my transaction (in BASE64)
- Jito will foward it directly to a jito validator!
- Bypass the normal gossip propagators
- Additionally bundleOnly=true can be enabled!
import {
Transaction,
SystemProgram,
ComputeBudgetProgram,
sendAndConfirmRawTransaction,
} from '@solana/web3.js';
const transaction = new Transaction();
// 1. Normal transfer instruction
transaction.add(SystemProgram.transfer({
fromPubkey: me.publicKey,
toPubkey: bob.publicKey,
lamports: 1_000_000, // 0.001 SOL (1e6 lamports)
}));
// 2. Priority fee
// Pay 1000 micro-lamports per ComputingUnit for priority
transaction.add(ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1000n }));
// 3. JITO TIP
// Transfer 10000 lamports (0.00001 SOL) to a Jito tip account
const tipAccount = new PublicKey("키키키KEYKEYKEY");
// ^ One of the known Jito tip accounts (getTipAccounts or from docs)
transaction.add(SystemProgram.transfer({
fromPubkey: jun.publicKey,
toPubkey: tipAccount,
lamports: 10000,
}));
// 4. Sign the transaction as ME (fee payer and source of funds)
transaction.feePayer = jun.publicKey;
transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
transaction.sign(jun);
// 5. Encode and send via Jito RPC
const serializedTx = transaction.serialize();
const base64Tx = serializedTx.toString('base64');
// 6. Use fetch to call Jito's sendTransaction RPC endpoint
const jitoUrl = "https://testnet.block-engine.jito.wtf";
const res = await fetch(`${jitoUrl}?bundleOnly=true`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "sendTransaction",
params: [ base64Tx, "base64" ] // sending the tx in base64 encoding
})
});
const json = await res.json();
if(json.error) {
console.error("Jito submission failed:", json.error);
} else {
console.log("Jito transaction signature:", json.result);
}- getInflightBundleStatuses : Last 5 minutes check , 5 MAX
- getBundleStatuses : Final outcome
- getTipAccounts : Current tip accounts
//JUNBEOM IN, 2025
//1. What to query
const bundleIds = ["BUNDLE ID_1", "BUNDLE ID_2"];
//2. Fetch!
const response = await fetch('https://mainnet.block-engine.jito.wtf/api/v1/bundles', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "getInflightBundleStatuses",
params: [ bundleIds ]
})
});
//3. Response
const statusInfo = await response.json();
//4. Print
console.log(statusInfo.result);- Result
- Pending
- Landed
- Failed
- Dropped
- NOT POSSIBLE: Jito does not have full mempool
import bs58 from 'bs58'; //base 58
import { Transaction, SystemProgram } from '@solana/web3.js';
//1. Add Transactions
const tx1 = new Transaction().add(
SystemProgram.transfer({
fromPubkey: searcher.publicKey,
toPubkey: target.publicKey,
lamports: 500000, // Arbitrary action
})
);
//2. Sign tx1
tx1.feePayer = searcher.publicKey;
tx1.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
tx1.sign(searcher);
//3. Sign tx2
const tx2 = new Transaction().add(
SystemProgram.transfer({
fromPubkey: searcher.publicKey,
toPubkey: tipAccount, // tipAccount from getTipAccounts
lamports: 2000000, // e.g., 0.002 SOL tip (2000000 lamports)
})
);
tx2.feePayer = searcher.publicKey;
tx2.recentBlockhash = tx1.recentBlockhash; // use same blockhash to ensure bundle goes in same block if possible
tx2.sign(searcher);
// Prepare bundle for submission
const bundleTxs = [ bs58.encode(tx1.serialize()), bs58.encode(tx2.serialize()) ];
// Submit the bundle via Jito RPC
const res = await fetch('https://mainnet.block-engine.jito.wtf/api/v1/bundles', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "sendBundle",
params: [ bundleTxs ] // an array of base58 encoded transactions
})
});
// SEND!!!!
const result = await res.json();
if(result.error) {
console.error("Bundle submission failed:", result.error);
} else {
const bundleId = result.result;
console.log("Bundle submitted! ID:", bundleId);
}