Documentation
G-UNI is a framework for Uniswap V3 Positions wrapped into fractionalized, fungible, auto-fee-compounding ERC20 tokens. Customizable LP strategies for a pool can be implemented via the 'manager' role.

Contribute to the docs

Feel free to help improving the docs by doing a PR to this repo:
GitHub - gelatodigital/g-uni-docs
GitHub

Introduction

G-UNI is a generic ERC20 wrapper on a Uniswap V3 Position. Pools with any price bounds on any Uniswap V3 pair can be deployed via the GUniFactory instantiating a tokenized V3 Position. When liquidity is added into the pool, G-UNI tokens are minted and credited to the provider. Inversely, G-UNI tokens can be burned to redeem that proportion of the pool's V3 position liquidity and fees earned. Thus, G-UNI tokens represent proportional ownership (or "shares") of the underlying Uniswap V3 position. Similar to the Uniswap V2 LP experience, anyone can add liquidity to or remove liquidity from a G-UNI Pool, and can earn their portion of the fees generated just by holding the fungible tokens. Some G-UNI pools may have a special privileged manager role (see the createManagedPool and Manager Functions sections).

GUniFactory

The GUniFactory smart contract governs over the creation of G-UNI Pools. In theory, any account or smart contract can create a G-UNI Pool via the factory by calling createPool. This creates a tokenized UniswapV3 Position with a given immutable price range on the token pair and fee tier of your choice. Anyone can now participate as an LP in that range, by adding liquidity into this position and minting G-UNI tokens.

createPool

Deploy a G-UNI Pool on the Uniswap V3 pair and with the Position parameters of your choosing.
GUniFactory.sol
1
function createPool(
2
address tokenA,
3
address tokenB,
4
uint24 uniFee,
5
int24 lowerTick,
6
int24 upperTick
7
) external returns (address pool)
Copied!
Arguments:
  • tokenA One of the tokens in the Uniswap V3 pair
  • tokenB The other token in the Uniswap V3 pair
  • uniFee Fee tier of the Uniswap V3 pair (500, 3000, 10000)
  • lowerTick Initial lower price bound for the position, represented as a Uniswap V3 tick.
  • upperTick Initial upper price bound for the position, represented as a Uniswap V3 tick.
The lowerTick and upperTick: 1. MUST be set to integers between -887272 and 887272 where upperTick > lowerTick 2. MUST be integers divisible by the tickSpacing of the Uniswap pair.
Returns: Address of newly deployed G-UNI Pool ERC20 contract (proxied). To have full verification and functionality on etherscan (read/write methods) verify the proxy contract. Etherscan will recognize the contract address as an ERC20 token and generate the token page after minting of the first G-UNI tokens.

createManagedPool

The simplest type of G-UNI pool to create is via the createPool method above, which creates an immutable G-UNI pool that no account has special privileges for. If you create a pool via the sorbet UI this is indeed the type of pool you create. However G-UNI pools are further customizable and can be used to implement tokenized dynamic strategies on top of Uniswap V3. To do this one needs to create a "managed" G-UNI pool, which creates a pool with a privileged manager role with the ability to executiveRebalance the position moving the liquidity to a new range on that Uniswap V3 token pair. Via this manager functionality arbitrary rebalancing strategies can be implemented.
GUniFactory.sol
1
function createManagedPool(
2
address tokenA,
3
address tokenB,
4
uint24 uniFee,
5
uint16 managerFee,
6
int24 lowerTick,
7
int24 upperTick
8
) external returns (address pool)
Copied!
Arguments:
  • tokenA One of the tokens in the Uniswap V3 pair
  • tokenB The other token in the Uniswap V3 pair
  • uniFee Fee tier of the Uniswap V3 pair (500, 3000, 10000)
  • managerFee The proportion of fees earned that manager takes as a cut in Basis Points (deployer address is initial manager). Note: The managerFee can only be set to a non-zero value once (immutable afterwards)
  • lowerTick Initial lower price bound for the position, represented as a Uniswap V3 tick.
  • upperTick Initial upper price bound for the position, represented as a Uniswap V3 tick.

GUniRouter02

The GUniRouter02 smart contract exposes methods for adding liquidity to and removing liquidity from any G-UNI Pool. It supports a number of different methods for these operations. _Router operations require the router address to be approved to spend the tokens forwarded by _ msg.sender

addLiquidity

Add liquidity to a G-UNI Pool and mint G-UNI tokens.
GUniRouter.sol
1
function addLiquidity(
2
address pool,
3
uint256 amount0Max,
4
uint256 amount1Max,
5
uint256 amount0Min,
6
uint256 amount1Min,
7
address receiver
8
)
9
external
10
returns (
11
uint256 amount0,
12
uint256 amount1,
13
uint256 mintAmount
14
)
Copied!
Arguments:
  • pool address of G-UNI pool to add liquidity to
  • amount0Max maximum amount of token0 to deposit into pool
  • amount1Max maximum amount of token1 to deposit into pool
  • amount0Min minimum amount of token0 deposited
  • amount1Min minimum amount of token1 deposited
  • receiver account that receives minted G-UNI tokens
Returns:
  • amount0 actual amount of token0 deposited into G-UNI
  • amount1 actual amount of token1 deposited into G-UNI
  • mintAmount amount of G-UNI tokens minted
Min and Max amounts are specified because exact proportions of token0 and token1 are determined by the Uniswap V3 price which can move while the transaction is waiting to be mined. Minimum amounts can be used to revert the transaction if the price has moved too far.
Approve router address to spend amount0Max and amount1Max before calling addLiquidity

rebalanceAndAddLiquidity

Add Maximal Liquidity to a G-UNI Pool with any ratio of token0 and token1 relative to the G-UNI Pool's total inventory. A swap is performed before adding liquidity to rebalance the tokens forwarded so it more closely matches the Pool's inventory.
GUniRouter.sol
1
function rebalanceAndAddLiquidity(
2
address pool,
3
uint256 amount0In,
4
uint256 amount1In,
5
uint256 amountSwap,
6
bool zeroForOne,
7
address[] swapActions,
8
bytes[] swapDatas,
9
uint256 amount0Min,
10
uint256 amount1Min,
11
address receiver
12
)
13
external
14
returns (
15
uint256 amount0,
16
uint256 amount1,
17
uint256 mintAmount
18
)
Copied!
Arguments:
  • pool address of G-UNI pool to add liquidity to
  • amount0In amount of token0 sender forwards to router
  • amount1In amount of token1 sender forwards to router
  • amountSwap amount to input into 1inch swap
  • zeroForOne direction of swap
  • swapActions array of addresses for 1inch swap calls
  • swapDatas array of payloads for 1inch swap calls
  • amount0Min minimum amount of token0 supplied
  • amount1Min minimum amount of token1 supplied
  • receiver account that receives minted G-UNI tokens
Returns:
  • amount0 actual amount of token0 deposited into G-UNI
  • amount1 actual amount of token1 deposited into G-UNI
  • mintAmount amount of G-UNI tokens minted
In this operation the router transfersamount0In andamount1In from user and performs a swap before depositing into the G-UNI Pool. Based on the swap parameters and the current prices relative to the position, proportion of token0 and token1 may not be perfect after the swap. Any leftover tokens after maximal deposit will be returned to the msg.sender
swapActions and swapDatas are addresses and corresponding payloads that can be generated off chain using 1inch's api. Usually this will involve encoding an "approve" call which approves 1inch to spend tokens that the router is holding and attempting to swap, and then a "swap" call which actually performs the swap. In theory, any set of calls that successfully spends the input token and receives the output token would work here.
** See GUniResolver02 getRebalanceParams method to generate a rough estimate of the right swap parameters (zeroForOne, swapAmount, swapThreshold)

removeLiquidity

Burn G-UNI tokens and remove underlying liquidity and earned fees.
1
function removeLiquidity(
2
address pool,
3
uint256 burnAmount,
4
uint256 amount0Min,
5
uint256 amount1Min,
6
address receiver
7
)
8
external
9
returns (
10
uint256 amount0,
11
uint256 amount1,
12
uint128 liquidityBurned
13
)
Copied!
Arguments:
  • pool address of G-UNI Pool to remove liquidity from
  • burnAmount amount of G-UNI tokens to burn.
  • amount0Min minimum amount of token0 received after burn
  • amount1Min minimum amount of token1 received after burn
  • receiver account to receive the token0 and token1 remitted
Returns:
  • amount0 amount of token0 remitted to receiver
  • amount1 amount of token1 remitted to receiver
  • liquidityBurned amount of liquidity removed from the underlying Uniswap V3 position
Remember that msg.sender must approve router to spend burnAmount of G-UNI tokens for transaction not to revert

addLiquidityETH

GUniRouter.sol
1
function addLiquidityETH(
2
address pool,
3
uint256 amount0Max,
4
uint256 amount1Max,
5
uint256 amount0Min,
6
uint256 amount1Min,
7
address receiver
8
)
9
external
10
payable
11
returns (
12
uint256 amount0,
13
uint256 amount1,
14
uint256 mintAmount
15
)
Copied!
Identical to addLiquidity method but caller spends ETH rather than WETH. Applicable for any G-UNI Pool on a Uniswap Pair with WETH as one of the tokens.

rebalanceAndAddLiquidityETH

GUniRouter.sol
1
function rebalanceAndAddLiquidityETH(
2
address pool,
3
uint256 amount0In,
4
uint256 amount1In,
5
uint256 amountSwap,
6
bool zeroForOne,
7
address[] swapActions,
8
bytes[] swapDatas,
9
uint256 amount0Min,
10
uint256 amount1Min,
11
address receiver
12
)
13
external
14
payable
15
returns (
16
uint256 amount0,
17
uint256 amount1,
18
uint256 mintAmount
19
)
Copied!
Identical to rebalanceAndAddLiquidity but caller spends ETH rather than WETH. Applicable for any G-UNI Pool on a Uniswap Pair with WETH as one of the tokens.

removeLiquidityETH

1
function removeLiquidityETH(
2
address pool,
3
uint256 burnAmount,
4
uint256 amount0Min,
5
uint256 amount1Min,
6
address payable receiver
7
)
8
external
9
returns (
10
uint256 amount0,
11
uint256 amount1,
12
uint128 liquidityBurned
13
)
Copied!
Identical to removeLiquidity but receiver is remitted ETH rather than WETH from the liquidity withdrawn. Applicable for any G-UNI Pool on a Uniswap Pair with WETH as one of the tokens.

GUniResolver02

The GUniResolver02 smart contract is a library of simple helper methods for G-UNI positions. When exposing a UI for users to addLiquidity to G-UNI one may want to allow users to enter the pool with any amount of either asset using the rebalanceAndAddLiquidity Router method. However the swap parameters passed as argument to this function must be carefully chosen to deposit maximal liquidity and produce the least leftover (any leftover is returned to msg.sender ). This resolver contract exposes a helper for just that.

getRebalanceParams

Generate the (roughly approximated) swap parameters for a rebalanceAndAddLiquidity call
1
function getRebalanceParams(
2
address pool,
3
uint256 amount0In,
4
uint256 amount1In,
5
uint256 price18Decimals
6
)
7
external
8
view
9
returns (
10
bool zeroForOne,
11
uint256 swapAmount
12
)
Copied!
Arguments:
  • pool Address of G-UNI pool of interest
  • amount0In amount of token0 sender forwards to router
  • amount1In amount of token1 sender forwards to router
  • price18Decimals price ratio of token1/token0 disregarding (normalizing by) token decimals and then expressed as a WAD (multiplied by 10^18).
Returns:
  • zeroForOne direction of swap
  • swapAmount amount of token to swap
Because the price ratio often changes depending on the amount being swapped (slippage) you may want to call this method twice - first with a "reasonable guess" price18Decimals to get a preliminary swapAmount, then get a more accurate price18Decimals (using this swapAmount as input), and finally call this method again with the more accurate price to get a final and more precise swapAmount

Manager Functions

Pools created with createManagedPool are assigned a manager account who can configure the Gelato Executor meta-parameters and also can control and alter the range of the underlying Uniswap V3 position.
The manager is the most important and centrally trusted role in the GUniPool. It is the only role that has the power to potentially extract value from the principal invested or potentially grief the pool in a number of ways. One should only put funds into "managed" pools if they have some information about the manager account: manager could be fully controlled by a DAO (token voting), or could simply be a project's multi-sig, or be locked in a smart contract that automates rebalances under a certain codified strategy, or be trusted by the user for some other reason.

executiveRebalance

By far the most important manager function is the executiveRebalance method on the GUniPool. This permissioned method is the only way to change the price range of the underlying Uniswap V3 Position. Manager accounts who control this function are the means by which custom rebalancing strategies can be built on top of G-UNI. These strategies can be implemented by governance (slow, but decentralized) or by some central managerial party (more responsive but requiring much more trust) and in the future the manager role can be granted to a Gelato automated smart contract so that they have a sophisticated LP strategy fully automated by Gelato (both reinvesting fees and in rebalancing ranges).
GUniPool.sol
1
function executiveRebalance(
2
int24 newLowerTick,
3
int24 newUpperTick,
4
uint160 swapThresholdPrice,
5
uint256 swapAmountBPS,
6
bool zeroForOne
7
) external onlyManager {
Copied!
Arguments:
  • newLowerTick The new lower price bound of the position represented as a Uniswap V3 tick.
  • newUpperTickThe new upper price bound of the position represented as a Uniswap V3 tick.
  • swapThresholdPrice A sqrtPriceX96 that acts as a slippage parameter for the swap that rebalances the inventory (to deposit maximal liquidity around the new price bounds).
  • swapAmountBPS Amount of inventory to swap represented as Basis Points of the remaining amount of the token to swap.
  • zeroForOne The direction of the rebalancing swap.
In order to generate the parameters for an executive rebalance one has to understand the flow of this operation. First, the GUNiPool removes all the liquidity and fees earned. Then, it tries to deposit as much liquidity as possible around the new price range. Next, whatever is leftover is then swapped based on the swap parameters. Finally, another deposit of maximal liquidity to the position is attempted and any leftover sits in the contract balance waiting to be reinvested.
To generate the swap parameters for an executive rebalance tx, simulate the entire operation. It works like this:
1. Call getUnderlyingBalances on the GUniPool to obtain amount0Current and amount1Current 2. Compute amount0Liquidity and amount1Liquidity using LiquidityAmounts.sol library, the new position bounds, the current price and the current amounts from step 1.
3. Compute amount0Leftover and amount1Leftover with formula amount0Leftover = amount0Current - amount0Liquidity. In most cases one of these values will be 0 (or very close to 0). 4. Use amount0Liquidity and amount1Liquidity to compute the current proportion of each asset needed. 5. Use the amount0Leftover and amount1Leftover and the proportion from previous step to compute which token to swap and the swapAmount.
6. Convert swapAmount to swapAmountBPS by doing swapAmount * 10000 / amountLeftover for the token being swapped.
​
A complete example of this calculation can be found in this test file​

updateGelatoParams

Another important role of the manager is to configure the parameters that restrict the functionality of Gelato Executors. These parameters include how often Gelato bots can reinvest fees and withdraw manager fees, as well as other safety params like the slippage check. Only the manager can call updateGelatoParams .
GUniPoolStorage.sol
1
function updateGelatoParams(
2
uint16 newRebalanceBPS,
3
uint16 newWithdrawBPS,
4
uint16 newSlippageBPS,
5
uint32 newSlippageInterval,
6
address newTreasury
7
) external onlyManager {
Copied!
Arguments:
  • newRebalanceBPS The maximum percentage the auto fee reinvestment transaction cost can be compared to the fees earned in that feeToken. The percentage is given in Basis Points (where 10000 mean 100%). Example: if rebalanceBPS is 200 and the transaction fee is 10 USDC then the fees earned in USDC must be 500 UDSC or the transaction will revert.
  • newWithdrawBPS The maximum percentage the auto withdraw transaction fee can be compared to the amount withdrawn of that feeToken.
  • newSlippageBPS The maximum percentage that the rebalance slippage can be from the TWAP.
  • newSlippageInterval length of time in seconds for to compute the TWAP (time weighted average price). I.e. 300 means a 5 minute average price for the pool.
  • newTreasury The treasury address where manager fees are auto withdrawn.

transferOwnership

Standard transfer of the manager role to a new account.
GUniPoolStorage.sol
1
function transferOwnership(address newOwner)
2
public onlyManager
Copied!
Arguments: newOwner the new account to act as manager.

initializeManagerFee

GUniPoolStorage.sol
1
function initializeManagerFee(uint16 _managerFeeBPS)
Copied!
Arguments: _managerFeeBPS the percent commission of fees earned to go to the manager account.
The manager fee can only be set to a non-zero value one time and then can never be changed (unless the manager role is renounced).

renounceOwnership

Burn the manager role and set all manager fees to 0.
GUniPoolStorage.sol
1
function renounceOwnership() public onlyManager
Copied!

G-UNI Subgraph

The G-UNI subgraph tracks relevant data for all G-UNI Positions created through the GUniFactory createPool method. Most relevant information about about a G-UNI position is indexed and queryable via the subgraph. Query URL is: https://api.thegraph.com/subgraphs/name/gelatodigital/g-uni Here is an example query which fetches all information about all G-UNI Positions:
1
query {
2
pools {
3
id
4
blockCreated
5
manager
6
address
7
uniswapPool
8
token0 {
9
address
10
name
11
symbol
12
}
13
token1 {
14
address
15
name
16
symbol
17
}
18
feeTier
19
liquidity
20
lowerTick
21
upperTick
22
totalSupply
23
positionId
24
supplySnapshots {
25
id
26
block
27
reserves0
28
reserves1
29
}
30
feeSnapshots {
31
id
32
block
33
feesEarned0
34
feesEarned1
35
}
36
latestInfo {
37
sqrtPriceX96
38
reserves0
39
reserves1
40
leftover0
41
leftover1
42
unclaimedFees0
43
unclaimedFees1
44
block
45
}
46
}
47
}
Copied!
id: subgraph identifier for the pool (contract address) blockCreated: block G-UNI position was deployed address: contract address of G-UNI positions uniswapPool: contract address of Uniswap V3 pair token0: address of token0 token1: address of token1 feeTier: Uniswap V3 Pair fee tier (500, 3000, 1000) liquidity: amount of liquidity currently in G-UNI position lowerTick: current lower tick of G-UNI Position upperTick: current upper tick of G-UNI Position totalSupply: current total supply of G-UNI token positionId: Uniswap V3 ID of the G-UNI position supplySnapshots: snapshots of the supply when it changes feeSnapshots: snapshots of fees earned
latestInfo: has up to date info about the position in a recent block. The supplySnapshots, feeSnapshots, and latestInfo can be ingested together to produce an estimated APR for fees generated by the position. APR is calculated from these values in the following way: 1. We use the supplySnapshots to calculate the time weighted average value of reserves Vr. This value includes all fees earned.
2. We use the feeSnapshotsto add up all the fees earned to generate the total value of fees earned Vf . 3. We compute APR as Vf / (Vr - Vf) (when Vr > Vf ). In rare cases when Vf > Vr i.e. feesEarned are larger than the time weighted average reserves, one can simply useVf/Vr
An npm library for these APR calculations is forthcoming but an example of the calculation is here: https://github.com/kassandraoftroy/apr-script/blob/main/index.ts

GUniPool

Methods on the GUniPoolcontract are low level and are NOT RECOMMENDED for direct interaction by end users unless they know why and what they are doing. For standard mint/burn interaction with G-UNI see GUniRouter
The GUniPool smart contract is the core implementation that powers the G-UNI protocol (all G-UNI token proxy contracts point to this implementation). It is an extension of the ERC20 interface so all normal ERC20 token functions apply, as well as a number of custom methods particular to G-UNI which will be outlined below. Most of the methods on the GUniPool are low level and end users should likely be interacting with peripheral contracts rather than directly with these low level methods. Nevertheless these methods are publicly exposed and functional.

mint

1
function mint(uint256 mintAmount, address receiver)
2
external
3
nonReentrant
4
returns (
5
uint256 amount0,
6
uint256 amount1,
7
uint128 liquidityMinted
8
)
Copied!

burn

1
function burn(uint256 burnAmount, address receiver)
2
external
3
nonReentrant
4
returns (
5
uint256 amount0,
6
uint256 amount1,
7
uint128 liquidityBurned
8
)
Copied!

getMintAmounts

View method to compute the amount of G-UNI tokens minted (and exact amounts of token0 and token1 forwarded) from and amount0Max and amount1Max
1
function getMintAmounts(uint256 amount0Max, uint256 amount1Max)
2
external
3
view
4
returns (
5
uint256 amount0,
6
uint256 amount1,
7
uint256 mintAmount
8
)
Copied!

getUnderlyingBalances

get the current underlying balances of the entire G-UNI Pool
1
function getUnderlyingBalances()
2
public
3
view
4
returns (
5
uint256 amount0Current,
6
uint256 amount1Current
7
)
8
Copied!

getUnderlyingBalancesAtPrice

Get the current underlying balances given a custom price (not simply taking the current Uniswap price which can be manipulated).
1
function getUnderlyingBalancesAtPrice(
2
uint160 sqrtRatioX96
3
)
4
external
5
view
6
returns (
7
uint256 amount0Current,
8
uint256 amount1Current
9
)
Copied!
This function is useful for getting a fair unmanipulatable price of a G-UNI token for things like lending protocols (simply pass a time weighted average sqrtPrice to this function to get the unmanipulated underlying balances).

getPositionId

1
function getPositionID()
2
external view returns (bytes32 positionID)
Copied!
get the Identifier of the G-UNI position on the Uniswap V3 pair (useful for fetching data about the Position with the positions method on UniswapV3Pool.sol).

Contact

Questions about integrating G-UNI? Join the Gelato Telegram or Discord and find a dev or community manager to talk to! Email [email protected]
Last modified 11d ago