Providing Liquidity
Liquidity provision involves risk. This section describes how to use software tools, but your trading strategies are solely up to you.
Penumbra's DEX is built around order-book-style concentrated liquidity. Liquidity positions are constant-sum AMMs, offering to trade between assets at a fixed price. Each position is its own independent AMM with its own fee tier, and as described in the DEX chapter, the DEX engine indexes active liquidity positions and sorts them by price, then traverses the liquidity graph to optimally fill trades.
Liquidity positions are public, but are created out of and withdrawn back to Penumbra's shielded pool. Thus, while everyone can see the aggregate state of the market, they cannot see which positions are controlled by which accounts, or view an account's trading history or strategy (unless they have access to that account's viewing key).
Liquidity Positions
The data of a liquidity position is contained in the Position
message (opens in a new tab).
At a high level, this consists of:
- the position's trading function, which specifies:
- the trading pair (order-independent, as assets 1 and 2 are ordered by asset ID);
- the trading function, via parameters pandq;
- the percentage fee applied to trades;
 
- the position's reserves, R_1andR_2;
- a close_on_fillboolean, controlling whether the position is closed when a trade completely consumes eitherR_1orR_2.
The trading function for the CFMM is:
phi(R_1, R_2) = p * R_1 + q * R_2A trade is accepted when phi(R_1, R_2) = phi(R_1', R_2'), so the ratio of p
and q determines the trading price. For instance, if the position is
initialized with reserves (0, R_2), the position will accept a trade that
updates its reserves to R_1 = (q/p)*R_2. In other words, the position is
offering up to R_2 of asset 2 at price q/p. More details can be found in the
Concentrated
Liquidity (opens in a new tab)
section of the protocol specification.
The DEX engine indexes positions by pair and by effective price (inclusive of fee tier) and routes trades across the liquidity graph. More details on routing can be found in the On-chain Routing (opens in a new tab) section of the protocol specification.
Position Lifecycle
The lifecycle of a liquidity position is:
Opened
On creation (by a PositionOpen action (opens in a new tab)), the position is in the "opened" state, in which it is actively
providing liquidity to the DEX engine. Trade executions against the position
update its reserves.
Closed
An opened position can be closed explicitly, by a user-submitted
PositionClose action (opens in a new tab), or implicitly by the chain, if the Position (opens in a new tab)'s
close_on_fill field is set to true and the position's reserves were completely consumed by a trade.
When a position is closed, it no longer provides liquidity to the DEX and becomes "inert".
Withdrawn
Closing a position does not immediately withdraw funds, because Penumbra transactions (like any ZK transaction model) are early-binding: the prover must know the state transition they prove knowledge of, and they cannot know the final reserves with certainty until after the position has been deactivated.
Instead, reserves are explicitly withdrawn following closure.
Control over liquidity positions is recorded using LPNFTs that track both the position ID and its state. This allows using the transaction's value balance mechanism to track the state machine:
- the PositionOpenaction consumes the initial reserves and produces an "opened-LPNFT";
- the PositionCloseaction consumes an opened-LPNFT and produces a "closed-LPNFT";
- the PositionWithdrawaction consumes a closed- or withdrawn-LPNFT and produces a withdrawn-LPNFT with an incremented withdrawal sequence number and any reserves withdrawn from the position.
The protocol supports withdrawing from a position multiple times, though there is currently no reason to do this as the reserves are consumed on the first withdrawal.
This design opens interesting possibilities not possible on a conventional DEX:
- 
Block-scoped JIT liquidity: a position can be opened and closed in the same transaction. As the opening is processed in a batch in the first phase of the DEX EndBlockhandler and the closure is processed in a batch following all trade executions, this allows a liquidity provider to quote a price and be guaranteed that it will only be valid for a single block's batch executions.
- 
Replicating Market Makers: the payoff function of any CFMM can be approximated via constant-sum AMMs, allowing market makers to replicate whatever trading function they want. This functionality is implemented in pclifor two strategies: a linear spread across a range, and an approximation of the payoff of anxy=k(UniV2) curve.
Managing Liquidity Positions
There is currently no GUI support for managing liquidity positions.
Individual limit orders with pcli
pcli can create positions replicating limit order behavior using pcli tx lp order buy and pcli tx lp order sell. For instance:
pcli tx lp order sell 100gm@1gn # sells 100 gm at 1 gn per gm
pcli tx lp order sell 100gm@1gn/50bps # sells 100gm at 1 gn per gm, with 50bps feeThese positions will remain open following execution. For instance, if a user traded 100gn to gm against the first position, its new reserves would be 100gn, which it would offer at 1gm/gn. If this is not desired, the --auto-close parameter can be used:
pcli tx lp order sell 100gm@1gn --auto-close # sells 100 gm at 1 gn per gm, closing on fillA "buy" order can also be expressed using lp order buy:
pcli tx lp order buy 100gm@2gn --auto-close # sells 200gn at 0.5gm per gn, closing on fillThere is no actual distinction at the protocol level between "buy" and "sell"
orders; this command will create a position with initial reserves of 200gn,
equivalent to
pcli tx lp order sell 200gn@0.5gm --auto-closeOwned positions can be queried with pcli v balance.
Positions can be closed and withdrawn individually or all at once:
pcli tx lp close plpid1tvj0vpdxhuu0tjxsskgf05f6l6dp0xqjqvywptrs0flvvvhvecjqtaxvf7
pcli tx lp withdraw plpid1tvj0vpdxhuu0tjxsskgf05f6l6dp0xqjqvywptrs0flvvvhvecjqtaxvf7
pcli tx lp close-all
pcli tx lp withdraw-allRange liquidity with pcli
As of v0.80.1, pcli can compute a set of positions that spread liquidity
across a price range.  To do this, use
pcli tx lp replicate linear --helpThis strategy takes a number of parameters (see --help for details), most importantly:
- the trading pair
- the lower bound of the price range
- the upper bound of the price range
- the fee tier for the positions
For example, the following invocation will create 5 positions spread across the range 1.8 to 2.2 USDC per UM:
$ pcli tx lp replicate linear upenumbra:transfer/channel-2/uusdc 100000000transfer/channel-2/uusdc --lower-price 1.8 --upper-price 2.2 --fee-bps 100 --num-positions 5
#################################################################################
########################### LIQUIDITY SUMMARY ###################################
#################################################################################
You want to provide liquidity on the pair upenumbra:transfer/channel-2/uusdc
You will need:
 -> 50252144upenumbra
 -> 0transfer/channel-2/uusdc
You will create the following positions:
 ID                                                                State      Fee                       Sell Price           Reserves
 plpid1klkeg9ve0mu5hu4v5afrg8qaneh3tmhzjww9c4yd7203v89qv90q0ruva3  opened  100bps  1.800001transfer/channel-2/usdc  11.111111penumbra
 plpid1d5dmxgtaf34k3846kw858wqzyamg40ks20tprzxe0sgdyqcqdfzqj580m7  opened  100bps  1.900001transfer/channel-2/usdc  10.526315penumbra
 plpid1vynfad7qzccl0p3zlqjttfv9smpdlxw86w0c7hxq2fcll9qqlpdqaqhjwv  opened  100bps         2transfer/channel-2/usdc         10penumbra
 plpid1kn5y806w98n0gnr28vq79r0fx7nd0dlclzzsktrla47efhxy87usuzxnae  opened  100bps  2.100001transfer/channel-2/usdc   9.523809penumbra
 plpid1c95eyegaeganw25c79q0etrryneg0ppgy5c9jxv5vh0e0p4yxeqs9a4dtr  opened  100bps  2.200001transfer/channel-2/usdc   9.090909penumbra
Do you want to open those liquidity positions on-chain? [y/n]The portfolio value function of this strategy (not accounting for fees) can be visualized as follows:

The x-axis shows the price of UM in terms of USDC, and the y-axis shows the
value of the position in the presence of traders (i.e., assuming the portfolio
always holds the less valuable asset, as a trader will trade away the more
valuable asset).  The lower part of the chart shows the value of each individual
liquidity position. When the market price is less than the position's price, the
position holds UM, so the position's value increases with the price of UM up to
its sell price, at which point the position's value is constant. The upper part
of the chart shows the combined PVF of the portfolio of positions (in blue), as
well as the PVF of a UniV3 position in the same range for reference. Notice that
even with only 5 positions, the portfolio value fairly closely approximates the
UniV3 curve.
This can be understood as a special case of some of the contents of the Replicating Market Makers (opens in a new tab) paper.
pcli currently has limited support for external asset metadata, including denom exponents. Double-check proposed positions to be sure they're not mispriced by a factor of 1,000,000.
pcli can load an external registry of asset metadata, placed in $PCLI_HOME/registry.json. The Prax wallet registry can be downloaded via
wget -O PATH/TO/PCLI_HOME/registry.json https://raw.githubusercontent.com/prax-wallet/registry/main/registry/chains/penumbra-1.jsonEven using an external registry, pcli cannot use it to parse command-line arguments, due to limitations that will be removed in a future release.
Thus it is necessary to specify upenumbra:transfer/channel-2/uusdc (the base denom for both assets, so that the specified prices are 1.8 upenumbra per uusdc, not 1.8 penumbra per uusdc, a very different price) and 100000000transfer/channel-2/uusdc.
This limitation will be removed in a future release.
Programmatic access with pclientd
This section is incomplete. You can help by expanding it!
Programmatic access to a wallet's private state and transaction construction is
possible using pclientd. See the pclientd section of
the guide for details. This allows client software to work only with a local
GRPC endpoint and not have to worry about any Penumbra-specific cryptography or
ZK proving.
Managing liquidity with Maat
This section is incomplete. You can help by expanding it!
There's a work-in-progress arbitrage bot backed by pcli exists
called Maat (opens in a new tab). See the project README for
configuration details.