Twin Documentation
  • Overview
  • Consumer
  • Developer
  • Operations
Arquitectura
Flujos Técnicos
    Minting & RedeemBridge Cross-chainUniswap V4 Liquidity ManagementOracle Price UpdatesAuto-RebalanceRewardsAuthentication (Platform)Circulation ManagementDisperse (Batch Transfer)Market Data PipelineSupply API (CoinGecko)Contract Data CollectionCron Jobs: Flujos Detallados
Base de Datos
Local SetupConventions
powered by Zudoku
Flujos Técnicos

Uniswap V4 Liquidity Management

Twin manages concentrated liquidity positions on Uniswap V4 (Base) via BitGo Express async operations.

Overview

Liquidity operations are executed through BitGo Express sendContractCall, which signs and broadcasts on-chain transactions. All operations are async: the API returns a transactionId immediately, and the on-chain work (approvals, TX broadcast, receipt polling) runs in the background.


Token Ordering

Uniswap V4 sorts tokens by address. Since USDC (0x8335...) < ARGT (0xf016...):

FieldValue
token0USDC
token1ARGT
Price meaning"ARGT per USDC"
Typical price~1466 (1 USDC = 1466 ARGT)

This ordering affects all calculations, tick directions, and amount assignments.


Action IDs

Uniswap V4 PositionManager encodes operations as action bytes:

ActionIDUsed In
INCREASE_LIQUIDITY0Increase
DECREASE_LIQUIDITY1Remove
MINT_POSITION2Mint
BURN_POSITION3(after full remove)
SETTLE_PAIR13Increase, Mint
TAKE_PAIR17Remove
SWEEP20Increase, Mint (refund excess)

1. Increase Liquidity

Add liquidity to an existing position.


2. Mint Position

Create a new concentrated liquidity position. Same flow as Increase but with tick alignment and tokenId extraction.

Tick Alignment

Ticks must be multiples of the pool's tickSpacing. Misalignment causes reverts.

TickRoundingFunctionWhy
LowerMath.floor (round down)alignTickToSpacingExtends range downward
UpperMath.ceil (round up)alignTickToSpacingCeilExtends range upward

Using floor for both ticks creates asymmetric ranges (~1.7% deviation from Uniswap UI).

Out-of-Range Positions

When the entire price range is above or below the current price, only one token is required:

Range PositionRequired TokenOther Token
Below current pricetoken0 onlySend "0"
Above current pricetoken1 onlySend "0"
In rangeBoth tokensAuto-calculated

Backend validates liquidity > 0n with Errors.badRequest('Calculated liquidity is zero...').


3. Remove Liquidity

Remove a percentage (25/50/75/100%) of liquidity from an existing position.

No approvals needed for remove (tokens flow out, not in).


On-chain Data Reading

All data is read directly via eth_call (no subgraphs).

DataContractMethod
Current priceStateViewgetSlot0(poolId) -> sqrtPriceX96
Pool liquidityStateViewgetLiquidity(poolId)
Position infoPositionManagerpositionInfo(nftId)
Position liquidityPositionManagergetPositionLiquidity(nftId)
Token balancesERC20balanceOf(walletAddress)

Price Conversion

Code
price = sqrtPriceX96^2 / 2^192 * 10^(decimals0 - decimals1)

TVL Calculation

Code
If token0 is stablecoin (USDC): TVL = amount0 + amount1 / price If token1 is stablecoin: TVL = amount0 * price + amount1

Slippage Handling

All amount0Max / amount1Max values include a slippage buffer to prevent reverts caused by integer math rounding (error 0x31e30ad0).

Code
amountWithSlippage = amount * (10000 + slippageBps) / 10000

Approvals must also use slippage-adjusted amounts, not the desired amounts. Without this, the contract may try to transfer 1 wei more than approved.

Applies to: increaseLiquidity, mintPosition, rebalance.


Auto-calc Formula (Decimal.js)

Given a price range and one token amount, calculate the complementary amount using concentrated liquidity math. Uses Decimal.js with 40 digits of precision to avoid catastrophic cancellation.

Code
sqrtP = sqrt(currentPrice) sqrtPa = sqrt(priceLower) sqrtPb = sqrt(priceUpper) Given amount0, calculate amount1: L = amount0 / (1/sqrtP - 1/sqrtPb) amount1 = L * (sqrtP - sqrtPa) Given amount1, calculate amount0: L = amount1 / (sqrtP - sqrtPa) amount0 = L * (1/sqrtP - 1/sqrtPb)

Why Decimal.js?

The subtraction 1/sqrtP - 1/sqrtPb involves two very close numbers when the range is narrow. Standard floating-point loses significant digits (catastrophic cancellation). Decimal.js with 40 digits of precision eliminates this error.

The effective deposit ratio differs from the spot price because it depends on the position of the current price within the range.


Async Operation Pattern

All liquidity operations follow the same async pattern:

  • waitForReceipt: 20 attempts x 5s = ~100s max polling.
  • Frontend shows "Operacion iniciada" on the immediate response.
  • validateBalances: Pre-checks wallet balances before creating any TX. Prevents wasting gas on TXs that would revert.

BitGo sendContractCall Pattern

All operations use the same BitGo pattern:

Code
bitgoExpressClient.sendContractCall({ coin: 'baseeth', // base coin, NOT token-qualified walletId: wallet.bitgoWalletId, amount: '0', // no native value transfer data: encodedCalldata, // viem encodeFunctionData walletPassphrase: decrypt(wallet.passphraseEncrypted), });

Note: coin is baseeth (not baseeth:argt) because these are pure contract calls to Uniswap contracts, not token transfers.

Last modified on May 12, 2026
Bridge Cross-chainOracle Price Updates
On this page
  • Overview
  • Token Ordering
  • Action IDs
  • 1. Increase Liquidity
  • 2. Mint Position
    • Tick Alignment
    • Out-of-Range Positions
  • 3. Remove Liquidity
  • On-chain Data Reading
    • Price Conversion
    • TVL Calculation
  • Slippage Handling
  • Auto-calc Formula (Decimal.js)
    • Why Decimal.js?
  • Async Operation Pattern
  • BitGo sendContractCall Pattern
TypeScript