pcli/command/tx/liquidity_position.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
use anyhow::Result;
use penumbra_asset::asset;
use penumbra_dex::{
lp::{
position::{self, Position},
BuyOrder, SellOrder,
},
TradingPair,
};
use rand_core::CryptoRngCore;
use super::{replicate::ReplicateCmd, FeeTier};
#[derive(Debug, clap::Subcommand)]
pub enum PositionCmd {
/// Open a new liquidity position based on order details and credits an open position NFT.
#[clap(display_order = 100, subcommand)]
Order(OrderCmd),
/// Debits an all opened position NFTs associated with a specific source and credits closed position NFTs.
CloseAll {
/// Only spend fugdnds originally received by the given address index.
#[clap(long, default_value = "0")]
source: u32,
/// Only close positions for the given trading pair.
#[clap(long)]
trading_pair: Option<TradingPair>,
/// The selected fee tier to multiply the fee amount by.
#[clap(short, long, default_value_t)]
fee_tier: FeeTier,
},
/// Debits opened position NFTs and credits closed position NFTs.
Close {
/// Only spend funds originally received by the given address index.
#[clap(long, default_value = "0")]
source: u32,
/// The list of [`position::Id`] of the positions to close.
position_ids: Vec<position::Id>,
/// The selected fee tier to multiply the fee amount by.
#[clap(short, long, default_value_t)]
fee_tier: FeeTier,
},
/// Debits all closed position NFTs associated with a specific account and credits withdrawn position NFTs and the final reserves.
WithdrawAll {
/// Only spend funds originally received by the given address index.
#[clap(long, default_value = "0")]
source: u32,
/// Only withdraw positions for the given trading pair.
#[clap(long)]
trading_pair: Option<TradingPair>,
/// The selected fee tier to multiply the fee amount by.
#[clap(short, long, default_value_t)]
fee_tier: FeeTier,
},
/// Debits closed position NFTs and credits withdrawn position NFTs and the final reserves.
Withdraw {
/// Only spend funds originally received by the given address index.
#[clap(long, default_value = "0")]
source: u32,
/// The list of [`position::Id`] of the positions to withdraw.
position_ids: Vec<position::Id>,
/// The selected fee tier to multiply the fee amount by.
#[clap(short, long, default_value_t)]
fee_tier: FeeTier,
},
/// Debits a withdrawn position NFT and credits a claimed position NFT and any liquidity incentives.
#[clap(hide(true))] // remove when reward claims exist
RewardClaim {},
/// Replicate a trading function
#[clap(subcommand)]
Replicate(ReplicateCmd),
}
impl PositionCmd {
pub fn offline(&self) -> bool {
match self {
PositionCmd::Order(_) => false,
PositionCmd::Close { .. } => false,
PositionCmd::CloseAll { .. } => false,
PositionCmd::Withdraw { .. } => false,
PositionCmd::WithdrawAll { .. } => false,
PositionCmd::RewardClaim { .. } => false,
PositionCmd::Replicate(replicate) => replicate.offline(),
}
}
}
#[derive(Debug, clap::Subcommand)]
pub enum OrderCmd {
Buy {
/// The desired purchase, formatted as a string, e.g. `100penumbra@1.2gm` would attempt
/// to purchase 100 penumbra at a price of 1.2 gm per 1penumbra.
///
/// An optional suffix of the form `/10bps` may be added to specify a fee spread for the
/// resulting position, though this is less useful for buy/sell orders than passive LPs.
buy_order: String,
/// Only spend funds originally received by the given address index.
#[clap(long, default_value = "0")]
source: u32,
/// When set, tags the position as an auto-closing buy.
#[clap(long)]
auto_close: bool,
/// The selected fee tier to multiply the fee amount by.
#[clap(short, long, default_value_t)]
fee_tier: FeeTier,
/// Duplicate the order for the given number of times.
#[clap(short, long, default_value = "1")]
num_copies: u32,
},
Sell {
/// The desired sale, formatted as a string, e.g. `100penumbra@1.2gm` would attempt
/// to sell 100 penumbra at a price of 1.2 gm per 1penumbra.
///
/// An optional suffix of the form `/10bps` may be added to specify a fee spread for the
/// resulting position, though this is less useful for buy/sell orders than passive LPs.
sell_order: String,
/// Only spend funds originally received by the given address index.
#[clap(long, default_value = "0")]
source: u32,
/// When set, tags the position as an auto-closing sell.
#[clap(long)]
auto_close: bool,
/// The selected fee tier to multiply the fee amount by.
#[clap(short, long, default_value_t)]
fee_tier: FeeTier,
/// Duplicate the order for the given number of times.
#[clap(short, long, default_value = "1")]
num_copies: u32,
},
}
impl OrderCmd {
pub fn source(&self) -> u32 {
match self {
OrderCmd::Buy { source, .. } => *source,
OrderCmd::Sell { source, .. } => *source,
}
}
pub fn fee_tier(&self) -> FeeTier {
match self {
OrderCmd::Buy { fee_tier, .. } => *fee_tier,
OrderCmd::Sell { fee_tier, .. } => *fee_tier,
}
}
pub fn is_auto_closing(&self) -> bool {
match self {
OrderCmd::Buy { auto_close, .. } => *auto_close,
OrderCmd::Sell { auto_close, .. } => *auto_close,
}
}
pub fn num_copies(&self) -> u32 {
match self {
OrderCmd::Buy { num_copies, .. } => *num_copies,
OrderCmd::Sell { num_copies, .. } => *num_copies,
}
}
pub fn as_position(
&self,
// Preserved since we'll need it after denom metadata refactor
_asset_cache: &asset::Cache,
mut rng: impl CryptoRngCore,
) -> Result<Vec<Position>> {
let positions = match self {
OrderCmd::Buy { buy_order, .. } => {
tracing::info!(?buy_order, "parsing buy order");
let order = BuyOrder::parse_str(buy_order)?;
let mut positions = Vec::new();
for _ in 0..self.num_copies() {
let mut position = order.into_position(&mut rng);
if self.is_auto_closing() {
position.close_on_fill = true;
}
positions.push(position);
}
positions
}
OrderCmd::Sell { sell_order, .. } => {
tracing::info!(?sell_order, "parsing sell order");
let order = SellOrder::parse_str(sell_order)?;
let mut positions = Vec::new();
for _ in 0..self.num_copies() {
let mut position = order.into_position(&mut rng);
if self.is_auto_closing() {
position.close_on_fill = true;
}
positions.push(position);
}
positions
}
};
Ok(positions)
}
}