SolMeet
Search…
BUIDL an Auto-compounding Bot on Saber
[Updated at 2022.3.31]
See the example repo here

TL; DR

  • Build SDKs only using @solana/web3.js
  • Learn how to interact with Solana
  • Builld an auto-compounding bot with SDK

Introduction

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
[26,151,18,191,115,212,220,144,52,66,74,133,251,235,69,161,254,121,70,227,171,227,17,170,154,227,32,151,40,125,37,158,0,94,253,210,209,242,208,122,162,84,158,36,211,63,243,252,104,36,58,243,120,134,127,132,193,186,63,50,0,230,93,200] 12T1dsupQBqwgQYsXWqhhmzQgreRtvkP95W8rW3Pk23R
Address : A Public key encoded in Base58
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
Serialized instruction https://explorer.solana.com/tx/37oPunF5tLw6DEqQUk4gMHeb6n1wdCAPWBF7e291znWwPXW7gCvPh9Srv5m5UMoxZoHSMR4BcQau1RnmU5yQeWKj
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
Image from 白上フブキ.eth

Solana system model

Overview

  • Learn Solana basic from building a SDK
  • Build a bot that collect, sell, reinvest the yield from LP farming

Architecture

File Structure

1
├── 📂 raydium
2
│ │
3
│ ├── 📄 ids.ts
4
│ │
5
│ ├── 📄 index.ts
6
│ │
7
│ ├── 📄 infos.ts
8
│ │
9
│ ├── 📄 instructions.ts
10
│ │
11
│ ├── 📄 layouts.ts
12
│ │
13
│ └── 📄 transactions.ts
14
15
16
├── 📂 saber
17
│ │
18
│ ├── 📄 ids.ts
19
│ │
20
│ ├── 📄 index.ts
21
│ │
22
│ ├── 📄 infos.ts
23
│ │
24
│ ├── 📄 instructions.ts
25
│ │
26
│ ├── 📄 layouts.ts
27
│ │
28
│ └── 📄 transactions.ts
29
30
31
│── 📄 index.ts
32
33
│── 📄 utils.ts
34
35
│── 📄 package.json
36
37
│── 📄 tsconfig.json
38
39
└── ...
Copied!

Setup

Install Rust and Solana

1
$ sh -c "$(curl -sSfL https://release.solana.com/v1.9.8/install)"
2
...
Copied!
See https://hackmd.io/@ironaddicteddog/solana-starter-kit#Install-Rust-and-Solana-Cli for more details.

Recover your Wallet (Using Phantom)

FIrst, click Show Secret Reconvery Phrase and copy your recovery phrase at this point.
Next, let's recover the wallet locally:
1
$ solana-keygen recover 'prompt:?key=0/0' -o ~/.config/solana/solmeet-keypair-1.json
Copied!
There should be a prompt asking for entering the recovery phrase in yout terminal. Paste your recovery phrase at this point.
  • Set keypair
1
$ solana config set --keypair ~/.config/solana/solmeet-keypair-1.json
Copied!

Config to solana-mf

1
$ solana config set --url https://rpc-mainnet-fork.dappio.xyz
2
$ solana config set --ws wss://rpc-mainnet-fork.dappio.xyz/ws
3
$ solana config set --commitment processed
4
$ solana airdrop 1
Copied!

Scaffold

1
$ mkdir solmeet-5-bot
2
$ cd solmeet-5-bot
3
$ tsc --init
Copied!
1
$ touch {index.ts,utils.ts}
2
$ mkdir saber && touch saber/{index.ts,ids.ts,layouts.ts,infos.ts,instructions.ts,transactions.ts}
3
$ mkdir raydium && touch raydium/{index.ts,ids.ts,layouts.ts,infos.ts,instructions.ts,transactions.ts}
Copied!

Add package.json

1
{
2
"name": "solmeet-5-bot",
3
"version": "1.0.0",
4
"description": "",
5
"main": "./index.ts",
6
"scripts": {
7
"start": "ts-node ./index.ts"
8
},
9
"dependencies": {
10
"@project-serum/borsh": "^0.2.5",
11
"@project-serum/serum": "^0.13.61",
12
"@solana/buffer-layout": "^4.0.0",
13
"@solana/spl-token": "^0.2.0",
14
"@solana/web3.js": "^1.35.0",
15
"bignumber.js": "^9.0.1",
16
"buffer-layout": "^1.2.2",
17
"js-sha256": "^0.9.0"
18
},
19
"devDependencies": {
20
"@project-serum/borsh": "^0.2.5",
21
"@solana/web3.js": "^1.35.0",
22
"@types/express": "^4.17.13",
23
"@types/node": "^17.0.18",
24
"buffer-layout": "^1.2.2",
25
"ts-node": "^10.5.0",
26
"typescript": "^4.5.5"
27
}
28
}
Copied!

Install Dependencies

1
$ yarn
Copied!

Part 1: Implement Common Modules

In this part, we will implement the common modules for the bot:
  • ids.ts
  • layouts.ts
  • utils.ts

ids.ts

In Solana, execution (programs) and states are decoupled. As a result, we have to be very clear on the scope of the the programs and states:

saber/ids.ts

1
import { PublicKey } from "@solana/web3.js";
2
3
export const SBR_MINT = new PublicKey("Saber2gLauYim4Mvftnrasomsv6NvAuncvMEZwcLpD1");
4
export const USDC_UST_POOL = new PublicKey("KwnjUuZhTMTSGAaavkLEmSyfobY16JNH4poL9oeeEvE");
5
export const ADMIN_KEY = new PublicKey("H9XuKqszWYirDmXDQ12TZXGtxqUYYn4oi7FKzAm7RHGc");
6
export const SWAP_PROGRAM_ID = new PublicKey("SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ");
7
export const SABER_WRAP_PROGRAM_ID = new PublicKey("DecZY86MU5Gj7kppfUCEmd4LbXXuyZH1yHaP2NTqdiZB");
8
export const SABER_QUARRY_REWARDER = new PublicKey("rXhAofQCT7NN9TUqigyEAUzV1uLL4boeD8CRkNBSkYk");
9
export const QURARRY_MINE_PROGRAM_ID = new PublicKey("QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB");
10
export const SABER_MINT_WRAPPER = new PublicKey("EVVDA3ZiAjTizemLGXNUN3gb6cffQFEYkFjFZokPmUPz");
11
export const QURARRY_MINT_WRAPPER = new PublicKey("QMWoBmAyJLAsA1Lh9ugMTw2gciTihncciphzdNzdZYV");
12
export const SABER_FARM_MINTER = new PublicKey("GEoTC3gN12qHDniaDD7Zxvd5xtcZyEKkTPy42B44s82y");
13
export const IOU_TOKEN_MINT = new PublicKey("iouQcQBAiEXe6cKLS85zmZxUqaCqBdeHFpqKoSz615u");
14
export const CLAIM_FEE_TOKEN_ACCOUNT = new PublicKey("4Snkea6wv3K6qzDTdyJiF2VTiLPmCoyHJCzAdkdTStBK");
15
export const SABER_TOKEN_MINT = new PublicKey("Saber2gLauYim4Mvftnrasomsv6NvAuncvMEZwcLpD1");
16
export const MINTER_PROGRAM_ID = new PublicKey("RDM23yr8pr1kEAmhnFpaabPny6C9UVcEcok3Py5v86X");
17
export const DEPRECATED_POOLS = [
18
new PublicKey("LeekqF2NMKiFNtYD6qXJHZaHx4hUdj4UiPu4t8sz7uK"),
19
new PublicKey("2jQoGQRixdcfuRPt9Zui7pk6ivnrQv79mf8h13Tyoa9K"),
20
new PublicKey("SPaiZAYyJBQHaSjtxFBKtLtQiCuG328r1mTfmvvydR5"),
21
new PublicKey("HoNG9Z4jsA1qtkZhDRYBc67LF2cbusZahjyxXtXdKZgR"),
22
new PublicKey("4Fss9Dy3vAUBuQ4SyEZz4vcLxeQqoFLZjdXhEUr3wqz3")
23
]
Copied!

raydium/ids.ts

1
import { PublicKey } from "@solana/web3.js";
2
3
export const SBR_AMM_ID = new PublicKey("5cmAS6Mj4pG2Vp9hhyu3kpK9yvC7P6ejh9HiobpTE6Jc")
4
export const LIQUIDITY_POOL_PROGRAM_ID_V3 = new PublicKey('27haf8L6oxUeXrHrgEgsexjSY5hbVUWEmvv9Nyxg8vQv')
5
export const LIQUIDITY_POOL_PROGRAM_ID_V4 = new PublicKey('675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8')
6
export const STAKE_PROGRAM_ID = new PublicKey('EhhTKczWMGQt46ynNeRX1WfeagwwJd7ufHvCDjRxjo5Q')
7
export const STAKE_PROGRAM_ID_V5 = new PublicKey('9KEPoZmtHUrBbhWN1v1KWLMkkvwY6WLtAVUCPRtRjP4z')
8
export const AMM_AUTHORITY = new PublicKey("5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1")
Copied!

layouts.ts

layouts play an important role in both reading and writing on-chain data.
  • Reading: It indicates how the stored bytes is arranged and what their types are.
  • Writing: It indicates how the instruction data should be assembled to call a certain program.

saber/layouts.ts

1
import { publicKey, struct, u64, u128, u8, u16, i64, bool } from "@project-serum/borsh";
2
3
export const FARM_LAYOUT = struct([
4
publicKey("rewarderKey"),
5
publicKey("tokenMintKey"),
6
u8("bump"),
7
u16("index"),
8
u8("tokenMintDecimals"),
9
i64("famineTs"),
10
i64("lastUpdateTs"),
11
u128("rewardsPerTokenStored"),
12
u64("annualRewardsRate"),
13
u64("rewardsShare"),
14
u64("totalTokensDeposited"),
15
u64("numMiners"),
16
]);
17
18
export const MINER_LAYOUT = struct([
19
publicKey("farmKey"),
20
publicKey("owner"),
21
u8("bump"),
22
publicKey("vault"),
23
u64("rewardsEarned"),
24
u128("rewardsPerTokenPaid"),
25
u64("balance"),
26
u64("index"),
27
]);
28
29
export const SWAPINFO_LAYOUT = struct([
30
bool("isInitialized"),
31
bool("isPaused"),
32
u8("nonce"),
33
u64("initialAmpFactor"),
34
u64("targetAmpFactor"),
35
i64("startRampTs"),
36
i64("stopRampTs"),
37
i64("futureAdminDeadline"),
38
publicKey("futureAdminKey"),
39
publicKey("adminKey"),
40
publicKey("tokenAccountA"),
41
publicKey("tokenAccountB"),
42
publicKey("poolMint"),
43
publicKey("mintA"),
44
publicKey("mintB"),
45
publicKey("adminFeeAccountA"),
46
publicKey("adminFeeAccountB"),
47
]);
48
49
export const WRAPINFO_LAYOUT = struct([
50
u8("decimal"),
51
u64("multiplyer"),
52
publicKey("underlyingWrappedTokenMint"),
53
publicKey("underlyingTokenAccount"),
54
publicKey("wrappedTokenMint"),
55
]);
56
57
export const DEPOSIT_LAYPOUT = struct([
58
u8('instruction'),
59
u64('AtokenAmount'),
60
u64('BtokenAmount'),
61
u64('minimalRecieve'),
62
]);
63
64
export const WITHDRAW_LAYOUT = struct([
65
u8('instruction'),
66
u64('LPtokenAmount'),
67
u64('minimalRecieve'),
68
]);
69
70
export const WRAP_LAYOUT = struct([
71
u64('amount'),
72
]);
73
74
export const UNWRAP_LAYOUT = struct([
75
u64('amount'),
76
]);
77
78
export const DEPOSIT_TO_FARM_LAYOUT = struct([
79
u64('amount'),
80
]);
81
82
export const CREATE_MINER_LAYOUT = struct([
83
u64('amount'),
84
]);
85
86
export const WITHDRAW_FROM_FARM_LAYOUT = struct([
87
u64('amount'),
88
]);
Copied!

raydium/layouts.ts

1
import { publicKey, struct, u8, u64, u128 } from "@project-serum/borsh";
2
3
export const SWAP_LAYOUT = struct([
4
u8('instruction'),
5
u64('amountIn'),
6
u64('minAmountOut')
7
]);
8
9
export const ADD_LIQUIDITY_LAYOUT = struct([
10
u8('instruction'),
11
u64('maxCoinAmount'),
12
u64('maxPcAmount'),
13
u64('fixedFromCoin')
14
]);
15
16
export const REMOVE_LIQUIDITY_LAYOUT = struct([
17
u8('instruction'),
18
u64('amount')
19
]);
20
21
export const AMM_INFO_LAYOUT_V4 = struct([
22
u64("status"),
23
u64("nonce"),
24
u64("orderNum"),
25
u64("depth"),
26
u64("coinDecimals"),
27
u64("pcDecimals"),
28
u64("state"),
29
u64("resetFlag"),
30
u64("minSize"),
31
u64("volMaxCutRatio"),
32
u64("amountWaveRatio"),
33
u64("coinLotSize"),
34
u64("pcLotSize"),
35
u64("minPriceMultiplier"),
36
u64("maxPriceMultiplier"),
37
u64("systemDecimalsValue"),
38
// Fees
39
u64("minSeparateNumerator"),
40
u64("minSeparateDenominator"),
41
u64("tradeFeeNumerator"),
42
u64("tradeFeeDenominator"),
43
u64("pnlNumerator"),
44
u64("pnlDenominator"),
45
u64("swapFeeNumerator"),
46
u64("swapFeeDenominator"),
47
// OutPutData
48
u64("needTakePnlCoin"),
49
u64("needTakePnlPc"),
50
u64("totalPnlPc"),
51
u64("totalPnlCoin"),
52
u128("poolTotalDepositPc"),
53
u128("poolTotalDepositCoin"),
54
u128("swapCoinInAmount"),
55
u128("swapPcOutAmount"),
56
u64("swapCoin2PcFee"),
57
u128("swapPcInAmount"),
58
u128("swapCoinOutAmount"),
59
u64("swapPc2CoinFee"),
60
publicKey("poolCoinTokenAccount"),
61
publicKey("poolPcTokenAccount"),
62
publicKey("coinMintAddress"),
63
publicKey("pcMintAddress"),
64
publicKey("lpMintAddress"),
65
publicKey("ammOpenOrders"),
66
publicKey("serumMarket"),
67
publicKey("serumProgramId"),
68
publicKey("ammTargetOrders"),
69
publicKey("poolWithdrawQueue"),
70
publicKey("poolTempLpTokenAccount"),
71
publicKey("ammOwner"),
72
publicKey("pnlOwner"),
73
]);
Copied!

utils.ts

Copy this code snippet to utils.ts.

getAnchorInsByIdl

Calculate Anchor Identifier:
1
// Example
2
3
function getAnchorInsByIdl(name: string): Buffer {
4
const SIGHASH_GLOBAL_NAMESPACE = "global";
5
const preimage = `${SIGHASH_GLOBAL_NAMESPACE}:${name}`;
6
const hash = sha256.sha256.digest(preimage)
7
const data = Buffer.from(hash).slice(0, 8)
8
return data;
9
}
Copied!

Part 2: Implement "Read" Modules

  • DataSizeFilter and MemCmpFilter
1
// Example
2
3
const adminIdMemcmp: MemcmpFilter = {
4
memcmp: {
5
offset: 8,
6
bytes: rewarderKey.toString(),
7
}
8
};
9
10
const sizeFilter: DataSizeFilter = {
11
dataSize: 140
12
}
13
const filters = [adminIdMemcmp, sizeFilter];
14
const config: GetProgramAccountsConfig = { filters };
15
const allFarmAccount = await connection.getProgramAccounts(QURARRY_MINE_PROGRAM_ID, config);
Copied!

infos.tx

Copy this code snippet to saber/infos.ts and this code snippet to raydium/infos.ts.

index.ts

Copy this code snippet to saber/index.ts and this code snippet to raydium/index.ts.

Part 3: Implement "Write" Modules

  • Need to add Anchor identifier manually
  • One single Solana tx includes multiple ixs
    • Solana ix = Ethereum tx
    • Solana tx = Ethereum multicall

instructions.ts

Copy this code snippet to saber/instructions.ts and this code snippet to raydium/instructions.ts.

transactions.ts

Copy this code snippet to saber/transactions.ts and this code snippet to raydium/transactions.ts.

Part 4: Implement the Auto-compounding Bot

  • Load Keypair
  • Claim All mining rewards
  • Swap all SBR to USDC
  • Add all USDC swapped out to USDC-UST pool
  • Deposit all LP to farming

index.ts

1
import os from "os";
2
import fs from "fs";
3
import BN from "bn.js";
4
import { Connection, Keypair } from "@solana/web3.js";
5
import * as raydium from "./raydium";
6
import { SBR_AMM_ID } from "./raydium/ids";
7
import * as saber from "./saber";
8
import { SBR_MINT, USDC_UST_POOL } from "./saber/ids";
9
import * as utils from "./utils";
10
11
// Load keypair
12
const keyPairPath = `${os.homedir()}/.config/solana/solmeet-keypair-1.json`;
13
const privateKeyUint8Array = JSON.parse(fs.readFileSync(keyPairPath, "utf-8"));
14
const privateKey = Uint8Array.from(privateKeyUint8Array);
15
const wallet = Keypair.fromSecretKey(privateKey);
16
17
async function main() {
18
const conn = new Connection("https://rpc-mainnet-fork.dappio.xyz", { wsEndpoint: "wss://rpc-mainnet-fork.dappio.xyz/ws", commitment: "processed", });
19
// const connection = new Connection("https://solana-api.tt-prod.net", { commitment: "processed", });
20
console.log("Fetching all Saber pools...");
21
const swaps = await saber.getAllSwaps(conn);
22
console.log("Fetching all Saber miners...");
23
const miners = await saber.getAllMiners(conn, wallet.publicKey);
24
console.log("Fetching Saber AMM pool on Raydium...");
25
const sbrAmm = (await raydium.getAmmPool(SBR_AMM_ID, conn));
26
27
// Claim All mining rewards
28
console.log("Claiming all mining rewards...")
29
for (const miner of miners) {
30
for (const swap of swaps) {
31
if (miner.farmKey.toString() === swap.farmingInfo?.infoPubkey.toString()) {
32
if (miner.balance.toNumber() > 0) {
33
// Create claimRewardTx
34
const claimRewardTx = await saber.claimRewardTx(swap.farmingInfo as saber.FarmInfo, wallet.publicKey, conn)
35
// Send Tx
36
const result = await utils.signAndSendAll(claimRewardTx, conn, wallet)
37
console.log(miner.getUnclaimedRewards(swap), "SBR reward claimed. Tx:", result);
38
}
39
}
40
}
41
}
42
43
let tokenAccounts = await utils.getAllTokenAccount(wallet.publicKey, conn);
44
let swapOutAmount = new BN(0);
45
46
// Swap all SBR to USDC
47
console.log("Swapping all SBR to USDC...")
48
for (const token of tokenAccounts) {
49
if (token.mint === SBR_MINT && token.amount.cmpn(0)) {
50
swapOutAmount = await (await sbrAmm.calculateSwapOutAmount("coin", token.amount, conn)).divn(0.98);
51
if (!swapOutAmount.cmpn(1)) {
52
break;
53
}
54
const swapIx = await raydium.swap(sbrAmm, token.mint, sbrAmm.pcMintAddress, wallet.publicKey, token.amount, new BN(0), conn);
55
const result = await utils.signAndSendAll(swapIx, conn, wallet);
56
console.log(token.amount.toNumber() / 1000000, "SBR swapped. Tx:", result);
57
}
58
}
59
60
// Add all USDC swapped out to USDC-UST pool
61
console.log("Adding all USDC swapped out to USDC-UST pool...")
62
for (const swap of swaps) {
63
if (swap.infoPublicKey === USDC_UST_POOL) {
64
const addLP = await saber.createDepositTx(swap, new BN(0), swapOutAmount, new BN(0), wallet.publicKey, conn)
65
const result = await utils.signAndSendAll(addLP, conn, wallet)
66
console.log("LP reinvested. Tx:", result);
67
}
68
}
69
70
// Deposit all LP to farming
71
console.log("Depositing all LP to farming...")
72
tokenAccounts = await utils.getAllTokenAccount(wallet.publicKey, conn)
73
for (const swap of swaps) {
74
for (const token of tokenAccounts) {
75
if (token.mint.toString() === swap.poolMint.toString() && token.amount.cmpn(0)) {
76
// Create farmIx
77
const farmIx = await saber.depositToFarm(swap.farmingInfo as saber.FarmInfo, wallet.publicKey, token.amount, conn)
78
// Send Tx
79
const result = await utils.signAndSendAll(farmIx, conn, wallet)
80
console.log("Farm deposited. Tx:", result);
81
}
82
}
83
}
84
}
85
86
async function run() {
87
try {
88
main();
89
}
90
catch (e) {
91
console.error(e);
92
}
93
}
94
95
run();
Copied!
Finally, let's run the bot:
1
$ yarn start
2
Fetching all Saber pools...
3
Fetching all Saber miners...
4
Fetching Saber AMM pool on Raydium...
5
Claiming all mining rewards...
6
0.008659 SBR reward claimed. Tx: 5xCibLpPokio4YHTwVZ35VM8fG8cww8Ris52E1D1qr2F8VSgdJsrHoBJGRDE77p61kXU3UwTMYKtEP3VB2fj1tFM
7
0 SBR reward claimed. Tx: 2mwEVHKwQwG384JM9ys9iAoKjRnZVswyQRC4hip44nHPP2uJvkA1YUwLfrDn9bk84GxUpPyZjpjzkxZ2ENmfQPb4
8
Swapping all SBR to USDC...
9
Adding all USDC swapped out to USDC-UST pool...
10
Depositing all LP to farming...
11
✨ Done in 121.22s.
Copied!

References

  • https://github.com/DappioWonderland/auto-compounding-bot
  • https://hackmd.io/@ironaddicteddog/solana-starter-kit
  • https://hackmd.io/@ironaddicteddog/solana-anchor-escrow
tags: notes