Solana : Solana is a fast, low cost, decentralized blockchain with thousands of projects spanning DeFi, NFTs, Web3 and more.
Saber : Saber is a Curve-like AMM provider on Solana, support all kinds of stablecoin pair from various bridges
Solana 101
Account : Everything on Solana is an account * Accounts can only be owned by programs * Every Account is like a file in a computer * Accounts are used to store state * Only the account owner may debit an account and adjust its data * All accounts to be written to or read must be passed into Insructions * Developers should use the data field to save data inside accounts >Image from https://paulx.dev/blog/2021/01/14/programming-on-solana-an-introduction/
Image from https://explorer.solana.com/address/6ZRCB7AAqGre6c72PRz3MHLC73VMYvJ8bi9KHf1HFpNk
Program : Program is just an account with excutable enable * Solana programs are stateless * Designed be upgradable (BPF loader 2)
Image from https://explorer.solana.com/address/TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
Program Account : An Account owned by a Program other than System program * Data struct are very different from program to program
Image from https://explorer.solana.com/address/EzkjmZFzWccf2DRQ6uafahvfZh29ntwAmej8nDQ5tY1C
Keyair : A byte array that contain Public key and Private key * first 32 bytes are use as Private key * last 32 bytes are generated by Private key and use as Public key
Rent : Pay for space to store data on Solana * If Rent is paid for over 2 years, the account become rent-exempt
Image from https://explorer.solana.com/address/8UviNr47S8eL6J3WfDxMRa3hvLta1VDJwNWqsDgtN3Cv
RPC : The endpoint to read/write from/to Solana * Free RPCs can be slow or congested when there is high demand.
PDA (Program Derived Address) : A account without a Private key that only can be signed by a Program * PDA is generated by hashing a seed with the Program address
Instruction : The one and only way to interact with Programs * All accounts used when processing should be write into the Instruction
Raw instruction from https://explorer.solana.com/tx/v2s26c5vBzrc7rTEyEsCreBDHaVd41iJ8F4j1gLFAy1QA5q8tqJex2Y2h3xsDmJpU1R9wAyRqziyzNB85cgxFjh
Tx (Transaction) : A message send to RPC that contains one or more Instructions, Signatures and a Fee Payer * Every Tx have a size limit of 1232 bytes * A Tx can be signed by differents accounts * Fee are determined by the amount of Signer at the moment
Image from https://explorer.solana.com/tx/v2s26c5vBzrc7rTEyEsCreBDHaVd41iJ8F4j1gLFAy1QA5q8tqJex2Y2h3xsDmJpU1R9wAyRqziyzNB85cgxFjh
SPL (Solana Program Library) : A example library organized by Solana lab * SPL is not a token protocol on Solana, spl-token is.
ATA (Associated Token Account) : A Token Account which is a PDA created by Associated Token Program * There is only one ATA with every wallet and a token mint
import os from "os";
import fs from "fs";
import BN from "bn.js";
import { Connection, Keypair } from "@solana/web3.js";
import * as raydium from "./raydium";
import { SBR_AMM_ID } from "./raydium/ids";
import * as saber from "./saber";
import { SBR_MINT, USDC_UST_POOL } from "./saber/ids";
import * as utils from "./utils";
// Load keypair
const keyPairPath = `${os.homedir()}/.config/solana/solmeet-keypair-1.json`;
const privateKeyUint8Array = JSON.parse(fs.readFileSync(keyPairPath, "utf-8"));
const privateKey = Uint8Array.from(privateKeyUint8Array);
const wallet = Keypair.fromSecretKey(privateKey);
async function main() {
const conn = new Connection("https://rpc-mainnet-fork.dappio.xyz", { wsEndpoint: "wss://rpc-mainnet-fork.dappio.xyz/ws", commitment: "processed", });
// const connection = new Connection("https://solana-api.tt-prod.net", { commitment: "processed", });
console.log("Fetching all Saber pools...");
const swaps = await saber.getAllSwaps(conn);
console.log("Fetching all Saber miners...");
const miners = await saber.getAllMiners(conn, wallet.publicKey);
console.log("Fetching Saber AMM pool on Raydium...");
const sbrAmm = (await raydium.getAmmPool(SBR_AMM_ID, conn));
// Claim All mining rewards
console.log("Claiming all mining rewards...")
for (const miner of miners) {
for (const swap of swaps) {
if (miner.farmKey.toString() === swap.farmingInfo?.infoPubkey.toString()) {
if (miner.balance.toNumber() > 0) {
// Create claimRewardTx
const claimRewardTx = await saber.claimRewardTx(swap.farmingInfo as saber.FarmInfo, wallet.publicKey, conn)
// Send Tx
const result = await utils.signAndSendAll(claimRewardTx, conn, wallet)
console.log(miner.getUnclaimedRewards(swap), "SBR reward claimed. Tx:", result);
}
}
}
}
let tokenAccounts = await utils.getAllTokenAccount(wallet.publicKey, conn);
let swapOutAmount = new BN(0);
// Swap all SBR to USDC
console.log("Swapping all SBR to USDC...")
for (const token of tokenAccounts) {
if (token.mint === SBR_MINT && token.amount.cmpn(0)) {
swapOutAmount = await (await sbrAmm.calculateSwapOutAmount("coin", token.amount, conn)).divn(0.98);
if (!swapOutAmount.cmpn(1)) {
break;
}
const swapIx = await raydium.swap(sbrAmm, token.mint, sbrAmm.pcMintAddress, wallet.publicKey, token.amount, new BN(0), conn);
const result = await utils.signAndSendAll(swapIx, conn, wallet);
console.log(token.amount.toNumber() / 1000000, "SBR swapped. Tx:", result);
}
}
// Add all USDC swapped out to USDC-UST pool
console.log("Adding all USDC swapped out to USDC-UST pool...")
for (const swap of swaps) {
if (swap.infoPublicKey === USDC_UST_POOL) {
const addLP = await saber.createDepositTx(swap, new BN(0), swapOutAmount, new BN(0), wallet.publicKey, conn)
const result = await utils.signAndSendAll(addLP, conn, wallet)
console.log("LP reinvested. Tx:", result);
}
}
// Deposit all LP to farming
console.log("Depositing all LP to farming...")
tokenAccounts = await utils.getAllTokenAccount(wallet.publicKey, conn)
for (const swap of swaps) {
for (const token of tokenAccounts) {
if (token.mint.toString() === swap.poolMint.toString() && token.amount.cmpn(0)) {
// Create farmIx
const farmIx = await saber.depositToFarm(swap.farmingInfo as saber.FarmInfo, wallet.publicKey, token.amount, conn)
// Send Tx
const result = await utils.signAndSendAll(farmIx, conn, wallet)
console.log("Farm deposited. Tx:", result);
}
}
}
}
async function run() {
try {
main();
}
catch (e) {
console.error(e);
}
}
run();
Finally, let's run the bot:
$ yarn start
Fetching all Saber pools...
Fetching all Saber miners...
Fetching Saber AMM pool on Raydium...
Claiming all mining rewards...
0.008659 SBR reward claimed. Tx: 5xCibLpPokio4YHTwVZ35VM8fG8cww8Ris52E1D1qr2F8VSgdJsrHoBJGRDE77p61kXU3UwTMYKtEP3VB2fj1tFM
0 SBR reward claimed. Tx: 2mwEVHKwQwG384JM9ys9iAoKjRnZVswyQRC4hip44nHPP2uJvkA1YUwLfrDn9bk84GxUpPyZjpjzkxZ2ENmfQPb4
Swapping all SBR to USDC...
Adding all USDC swapped out to USDC-UST pool...
Depositing all LP to farming...
✨ Done in 121.22s.