Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[example project] LP Repositioning Bot #558

Merged
merged 19 commits into from
Dec 7, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ generated
**/Cargo.lock
.next
.env
wallet.json
solana-release
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ members = [
exclude = [
"rust-sdk",
"ts-sdk",
"docs"
"docs",
"examples"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useful to add a readme to the examples folder with a little more description on each example?

]
31 changes: 31 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Whirlpools SDK Examples

This directory contains example projects showcasing how to use the Whirlpools SDK suite in different environments. Each project demonstrates specific functionalities, providing a starting point for developers.

## Building the Examples
To build the examples, run the following commands from the root of the monorepo:

```bash
yarn install
yarn build
```

### General Note on Dependencies
All examples in this directory use local versions of the Orca SDK dependencies from this monorepo. If you plan to move an example project outside of the monorepo, you must update the dependencies to ensure compatibility.

## Available Examples
### Rust
#### 1. Whirlpool Repositioning Bot
- Path: examples/rust-sdk/whirlpools-repositioning-bot
- Description: A CLI tool to automatically reposition positions based on configurable thresholds.
- Highlights:
- Utilizes the Whirlpools Rust SDKs.
- Dynamically fetches on-chain data to manage LP positions.

### Typescript
#### 2. Next.js Integration
- Path: examples/ts-sdk/whirlpools-next
- Description: Demonstrates how to integrate the Whirlpools TS SDK `@orca-so/whirlpools` with a Next.js application.
- Highlights:
- Configures WebAssembly (`asyncWebAssembly`) support in Next.js.
- Provides a working setup to query and interact with Orca's whirlpools.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
RPC_URL="https://api.mainnet-beta.solana.com"
19 changes: 19 additions & 0 deletions examples/rust-sdk/whirlpool_repositioning_bot/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "whirlpool_repositioning_bot"
version = "0.1.0"
edition = "2021"

[dependencies]
clap = { version = "^4.5.21", features = ["derive"] }
colored = { version = "^2.0" }
orca_whirlpools = { path = '../../../rust-sdk/whirlpool' }
orca_whirlpools_client = { path = '../../../rust-sdk/client' }
orca_whirlpools_core = { path = '../../../rust-sdk/core' }
serde_json = { version = "^1.0" }
solana-client = { version = "^1.18" }
solana-sdk = { version = "^1.18" }
spl-token-2022 = { version = "^3.0" }
spl-associated-token-account = { version = "^3.0" }
tokio = { version = "^1.41.1" }
tokio-retry = { version = "^0.3.0" }
dotenv = { version = "^0.15.0"}
112 changes: 112 additions & 0 deletions examples/rust-sdk/whirlpool_repositioning_bot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# LP Bot - position rebalance
A Rust-based CLI bot for interacting with the Orca Whirlpools program on Solana. This bot monitors and rebalances a liquidity position by closing and reopening positions when price deviations exceed a user-defined threshold.

---

## Features
- **Automated Position Monitoring**: Monitors price deviation of a liquidity position on Orca Whirlpool by calculating the center of the position's price range and comparing it to the current pool price. If the deviation exceeds the specified threshold (in percentage), the bot initiates rebalancing.
- **Automated Rebalancing**: Closes and reopens liquidity positions by centering the new position around the current pool price, maintaining the same width (distance between the lower and upper price bounds) as the initial position.
- **Customizable Priority Fees**: Integrates compute budget priority fees to enhance transaction speed and landing, with options ranging from `none` to `turbo` for different levels of prioritization.

---

## Prerequisites
1. **Solana Wallet**:
- Place a `wallet.json` file in the working directory with the keypair that owns the positions.
- Ensure the wallet has sufficient funds for transactions.
2. **Existing Position**:
- You must have an active position on Orca Whirlpools. You can open a position using our SDKs or through our UI at https://www.orca.so/pools.
3. **Rust**:
- Install Rust using [rustup](https://rustup.rs/).

---

## Installation
1. Clone this repository:
```bash
git clone https://github.com/orca-so/whirlpools.git
cd examples/rust-sdk/lp-bot
```
2. Build the bot:
```bash
cargo build --release
```
3. The executable will be located in target/release/lp-bot

> NOTE: This project uses the local version of the dependency. If you want to move this example project outside of this monorepo, make sure you install the necessary dependecies:
```bash
cargo add orca_whirlpools orca_whirlpools_client orca_whirlpools_core
```


---

## RPC Configuration
The bot connects to an SVM network by using an RPC URL. Make a local copy of `.env.template` to `.env` and set your RPC URL there. It is strongly recommended to you use a URL from an RPC provider, or your own RPC node.

```bash
RPC_URL="https://your-rpc-url.com"
```

---

## Usage
Run the bot with the following arguments
```bash
./target/release/lp-bot \
--position-mint-address <POSITION_MINT_ADDRESS> \
--threshold <THRESHOLD_PERCENTAGE> \
--interval <INTERVAL_IN_SECONDS> \
--priority-fee-tier <PRIORITY_FEE_TIER>
```

### Arguments
- `--position-mint-address` (required): The mint address of the position to monitor and rebalance.
- `--threshold` (optional): The percentage deviation from the center price at which rebalancing is triggered. Default: 1.0.
- `--interval` (optional): The time interval (in seconds) between checks. Default: 60.
- `--priority-fee-tier` (optional): The priority fee tier for transaction processing. Options:
- `none`: No priority fee.
- `low`: Lower 25th quartile prioritization fee.
- `medium`: Median prioritization fee (default).
- `high`: Upper 80th quartile prioritization fee.
- `turbo`: Upper 99th quartile prioritization fee.
- `max_priority_fee_lamports` (optional): Maximum total priority fee in lamports. Default: 10_000_000 (0.01 SOL).
- `slippage_tolerance_bps` (optional): Slippage tolerance in basis points (bps). Default: 100.

### Example Usage
Monitor and rebalance with default settings:
```bash
./target/release/lp-bot \
--position-mint-address 5m1izNWC3ioBaKm63e3gSNFeZ44o13ncre5QknTXBJUS
```

Monitor with custom threshold and interval:
```bash
./target/release/lp-bot \
--position-mint-address 5m1izNWC3ioBaKm63e3gSNFeZ44o13ncre5QknTXBJUS \
--threshold 0.5 \
--interval 30
```

Monitor with turbo priority fees:
```bash
./target/release/lp-bot \
--position-mint-address 5m1izNWC3ioBaKm63e3gSNFeZ44o13ncre5QknTXBJUS \
--priority-fee-tier turbo
```

---

## Directory Structure

```bash
examples/
├── rust-sdk/
└── lp-bot/
└── src/
├── main.rs # Entry point
├── cli.rs # CLI argument parsing
├── wallet.rs # Wallet management
├── position_manager.rs # Position monitoring and rebalancing
├── solana_utils.rs # RPC utilities
```
15 changes: 15 additions & 0 deletions examples/rust-sdk/whirlpool_repositioning_bot/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "@orca-so/whirlpools-example-rust-repositioning-bot",
"version": "0.0.1",
"scripts": {
"build": "cargo build",
"format": "cargo clippy --fix --allow-dirty --allow-staged && cargo fmt",
"lint": "cargo clippy",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this command in #522 so you might want to do that here too

"clean": "cargo clean"
},
"devDependencies": {
"@orca-so/whirlpools-rust": "*",
"@orca-so/whirlpools-rust-client": "*",
"@orca-so/whirlpools-rust-core": "*"
}
}
60 changes: 60 additions & 0 deletions examples/rust-sdk/whirlpool_repositioning_bot/src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use clap::Parser;

use crate::utils::PriorityFeeTier;

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Args {
#[arg(
short = 'p',
long,
help = "The position mint address to monitor and rebalance."
)]
pub position_mint_address: String,

#[arg(
short = 't',
long,
default_value_t = 100,
help = "Threshold for repositioning in bps.\n"
)]
pub threshold: u16,

#[arg(
short = 'i',
long,
default_value_t = 60,
help = "Time interval for checking in seconds.\n"
)]
pub interval: u64,

#[arg(
short = 'f',
long,
value_enum,
default_value_t = PriorityFeeTier::Medium,
help = "Priority fee tier for transaction processing based on recently paid priority fees. Options:\n \
- `none`: No priority fee\n \
- `low`: Lower 25th quartile prioritization fee\n \
- `medium`: Median prioritization fee\n \
- `high`: Upper 80th quartile prioritization fee\n \
- `turbo`: Upper 99th quartile prioritization fee\n"
)]
pub priority_fee_tier: PriorityFeeTier,

#[arg(
short = 'm',
long,
default_value_t = 10_000_000,
help = "Maximum total priority fee in lamports.\n"
)]
pub max_priority_fee_lamports: u64,

#[arg(
short = 's',
long,
default_value_t = 100,
help = "Slippage tolerance in basis points (bps).\n"
)]
pub slippage_tolerance_bps: u16,
}
101 changes: 101 additions & 0 deletions examples/rust-sdk/whirlpool_repositioning_bot/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
mod cli;
mod position_manager;
mod utils;
mod wallet;

use clap::Parser;
use cli::Args;
use colored::Colorize;
use dotenv::dotenv;
use orca_whirlpools::{set_funder, set_whirlpools_config_address, WhirlpoolsConfigInput};
use orca_whirlpools_client::get_position_address;
use position_manager::run_position_manager;
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::pubkey::Pubkey;
use std::env;
use std::str::FromStr;
use tokio::time::{sleep, Duration};
use utils::{
display_position_balances, display_wallet_balances, fetch_mint, fetch_position, fetch_whirlpool,
};

#[tokio::main]
async fn main() {
let args = Args::parse();
dotenv().ok();
let rpc_url = env::var("RPC_URL").unwrap();
let rpc = RpcClient::new(rpc_url.to_string());
set_whirlpools_config_address(WhirlpoolsConfigInput::SolanaMainnet)
.expect("Failed to set Whirlpools config address for specified network.");
let wallet = wallet::load_wallet();
set_funder(wallet.pubkey()).expect("Failed to set funder address.");

let position_mint_address = Pubkey::from_str(&args.position_mint_address)
.expect("Invalid position mint address provided.");

println!(
"\n\
====================\n\
🌀 Whirlpool LP BOT \n\
====================\n"
);
println!("Configuration:");
println!(
" Position Mint Address: {}\n Threshold: {:.2}%\n Interval: {} seconds\n Priority Fee Tier: {:?}\n Slippage tolerance bps: {:?}\n",
args.position_mint_address, args.threshold, args.interval, args.priority_fee_tier, args.slippage_tolerance_bps
);

println!("-------------------------------------\n");

let (position_address, _) =
get_position_address(&position_mint_address).expect("Failed to derive position address.");
let mut position = fetch_position(&rpc, &position_address)
.await
.expect("Failed to fetch position data.");
let whirlpool = fetch_whirlpool(&rpc, &position.whirlpool)
.await
.expect("Failed to fetch Whirlpool data.");
let token_mint_a = fetch_mint(&rpc, &whirlpool.token_mint_a)
.await
.expect("Failed to fetch Token Mint A data.");
let token_mint_b = fetch_mint(&rpc, &whirlpool.token_mint_b)
.await
.expect("Failed to fetch Token Mint B data.");

display_wallet_balances(
&rpc,
&wallet.pubkey(),
&whirlpool.token_mint_a,
&whirlpool.token_mint_b,
)
.await
.expect("Failed to display wallet balances.");

display_position_balances(
&rpc,
&position,
&whirlpool.token_mint_a,
&whirlpool.token_mint_b,
token_mint_a.decimals,
token_mint_b.decimals,
args.slippage_tolerance_bps,
)
.await
.expect("Failed to display position balances.");

loop {
if let Err(err) = run_position_manager(
&rpc,
&args,
&wallet,
&mut position,
&token_mint_a,
&token_mint_b,
)
.await
{
eprintln!("{}", format!("Error: {}", err).to_string().red());
}
sleep(Duration::from_secs(args.interval)).await;
}
}
Loading
Loading