Skip to content

Commit 50a3e93

Browse files
committed
feat(clamm): implement XLS-99 Concentrated Liquidity AMM
Implement a Concentrated Liquidity Automated Market Maker for the XRP Ledger, enabling LPs to concentrate capital within specific price ranges for up to 4000x capital efficiency improvement over XLS-30. Ledger entries: - CLAMM (0x008A): pool state with current tick, sqrt price, liquidity - CLAMMTick (0x008B): per-tick liquidity and fee growth data - CLAMMPosition (0x008C): LP position linked to NFToken (XLS-20) - CLAMMTickBitmap (0x008D): compressed bitmap for tick lookup Transactions: - CLAMMCreate (85): create pool with asset pair, fee tier, initial price - CLAMMDeposit (86): add liquidity within a price range, mint NFToken - CLAMMWithdraw (87): remove liquidity, burn NFToken on full withdrawal - CLAMMSwap (88): direct swap through a pool - CLAMMCollectFees (89): collect accumulated fees without removing liquidity - CLAMMVote (90): vote on trading fee parameters - CLAMMBid (91): bid for auction slot (discounted fees, MEV protection) - CLAMMDelete (92): remove empty pools from ledger - CLAMMClawback (93): issuer clawback of tokens from CLAMM positions RPCs: clamm_info, clamm_positions, clamm_ticks, clamm_quote Integration: - Payment engine routing through CLAMM pools (CLAMMLiquidity/CLAMMOffer) - NFToken position transfer with fee settlement - NFTokenBurn blocked for CLAMM positions - ValidCLAMM invariant checker Tests: unit tests, payment routing, tick bitmap, fuzz testing Spec: XRPLF/XRPL-Standards#498
1 parent 92983d8 commit 50a3e93

57 files changed

Lines changed: 15130 additions & 46 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

include/xrpl/protocol/CLAMMCore.h

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#pragma once
2+
3+
#include <array>
4+
#include <cstdint>
5+
6+
namespace xrpl {
7+
8+
// Tick boundaries matching Uniswap V3
9+
constexpr std::int32_t CLAMM_MIN_TICK = -887272;
10+
constexpr std::int32_t CLAMM_MAX_TICK = 887272;
11+
12+
// Maximum fee tier index
13+
constexpr std::uint8_t CLAMM_MAX_FEE_TIER = 3;
14+
15+
// Fee tier configuration
16+
struct CLAMMFeeTierConfig
17+
{
18+
std::uint16_t tradingFee; // in 1/1,000,000 (e.g. 100 = 0.01%)
19+
std::uint16_t tickSpacing;
20+
};
21+
22+
// The four fee tiers supported by CLAMM
23+
constexpr std::array<CLAMMFeeTierConfig, 4> clammFeeTiers = {{
24+
{100, 1}, // STABLE: 0.01%, spacing 1
25+
{500, 10}, // LOW: 0.05%, spacing 10
26+
{3000, 60}, // MEDIUM: 0.30%, spacing 60
27+
{10000, 200} // HIGH: 1.00%, spacing 200
28+
}};
29+
30+
// NFToken taxon used for CLAMM position NFTs: "CLAM" in ASCII
31+
constexpr std::uint32_t CLAMM_NFTOKEN_TAXON = 0x434C414D;
32+
33+
// Voting constants (reuse AMM patterns)
34+
constexpr std::uint16_t CLAMM_VOTE_MAX_SLOTS = 8;
35+
constexpr std::uint32_t CLAMM_VOTE_WEIGHT_SCALE_FACTOR = 100000;
36+
37+
// Minimum liquidity for a new deposit
38+
constexpr std::uint64_t CLAMM_MIN_LIQUIDITY = 1000;
39+
40+
// Auction slot constants (reuse AMM patterns)
41+
constexpr std::uint32_t CLAMM_TOTAL_TIME_SLOT_SECS = 24 * 3600;
42+
constexpr std::uint16_t CLAMM_AUCTION_SLOT_TIME_INTERVALS = 20;
43+
constexpr std::uint16_t CLAMM_AUCTION_SLOT_MAX_AUTH_ACCOUNTS = 4;
44+
45+
class Rules; // forward declaration for clammEnabled
46+
47+
/** Return true if the CLAMM amendment is enabled */
48+
bool
49+
clammEnabled(Rules const&);
50+
51+
/** Validate fee tier index is in valid range */
52+
bool
53+
isValidCLAMMFeeTier(std::uint8_t feeTier);
54+
55+
/** Get tick spacing for a given fee tier */
56+
std::uint16_t
57+
clammTickSpacing(std::uint8_t feeTier);
58+
59+
/** Get trading fee for a given fee tier (in 1/1,000,000) */
60+
std::uint16_t
61+
clammTradingFee(std::uint8_t feeTier);
62+
63+
/** Check if a tick index is valid and aligned to the given tick spacing */
64+
bool
65+
isValidCLAMMTick(std::int32_t tick, std::uint16_t tickSpacing);
66+
67+
} // namespace xrpl

include/xrpl/protocol/Indexes.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,29 @@ permissionedDomain(AccountID const& account, std::uint32_t seq) noexcept;
342342

343343
Keylet
344344
permissionedDomain(uint256 const& domainID) noexcept;
345+
346+
/** CLAMM pool entry */
347+
Keylet
348+
clamm(Asset const& issue1, Asset const& issue2, std::uint8_t feeTier) noexcept;
349+
350+
inline Keylet
351+
clamm(uint256 const& id) noexcept
352+
{
353+
return {ltCLAMM, id};
354+
}
355+
356+
/** CLAMM tick entry */
357+
Keylet
358+
clammTick(uint256 const& poolID, std::int32_t tickIndex) noexcept;
359+
360+
/** CLAMM position entry */
361+
Keylet
362+
clammPosition(uint256 const& nfTokenID) noexcept;
363+
364+
/** CLAMM tick bitmap entry */
365+
Keylet
366+
clammTickBitmap(uint256 const& poolID, std::int16_t wordPosition) noexcept;
367+
345368
} // namespace keylet
346369

347370
// Everything below is deprecated and should be removed in favor of keylets:

include/xrpl/protocol/TxFlags.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
177177
TF_FLAG(tfClawTwoAssets, 0x00000001), \
178178
MASK_ADJ(0)) \
179179
\
180+
TRANSACTION(CLAMMClawback, \
181+
TF_FLAG2(tfClawTwoAssets, 0x00000001), \
182+
MASK_ADJ(0)) \
183+
\
180184
TRANSACTION(XChainModifyBridge, \
181185
TF_FLAG(tfClearAccountCreateAmount, 0x00010000), \
182186
MASK_ADJ(0)) \

include/xrpl/protocol/detail/features.macro

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@
1616
// Add new amendments to the top of this list.
1717
// Keep it sorted in reverse chronological order.
1818

19+
XRPL_FEATURE(CLAMM, Supported::yes, VoteBehavior::DefaultNo)
1920
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
2021
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)
2122
XRPL_FIX (BatchInnerSigs, Supported::no, VoteBehavior::DefaultNo)
2223
XRPL_FEATURE(LendingProtocol, Supported::yes, VoteBehavior::DefaultNo)
23-
XRPL_FEATURE(PermissionDelegationV1_1, Supported::no, VoteBehavior::DefaultNo)
24+
XRPL_FEATURE(PermissionDelegationV1_1, Supported::yes, VoteBehavior::DefaultNo)
2425
XRPL_FIX (DirectoryLimit, Supported::yes, VoteBehavior::DefaultNo)
2526
XRPL_FIX (IncludeKeyletFields, Supported::yes, VoteBehavior::DefaultNo)
26-
XRPL_FEATURE(DynamicMPT, Supported::no, VoteBehavior::DefaultNo)
27+
XRPL_FEATURE(DynamicMPT, Supported::yes, VoteBehavior::DefaultNo)
2728
XRPL_FIX (TokenEscrowV1, Supported::yes, VoteBehavior::DefaultNo)
2829
XRPL_FIX (PriceOracleOrder, Supported::yes, VoteBehavior::DefaultNo)
2930
XRPL_FIX (MPTDeliveredAmount, Supported::yes, VoteBehavior::DefaultNo)

include/xrpl/protocol/detail/ledger_entries.macro

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({
150150
{sfAMMID, soeOPTIONAL}, // pseudo-account designator
151151
{sfVaultID, soeOPTIONAL}, // pseudo-account designator
152152
{sfLoanBrokerID, soeOPTIONAL}, // pseudo-account designator
153+
{sfCLAMMID, soeOPTIONAL}, // pseudo-account designator
153154
}))
154155

155156
/** A ledger object which contains a list of object identifiers.
@@ -603,5 +604,81 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
603604
{sfLoanScale, soeDEFAULT},
604605
}))
605606

607+
/** A ledger object which tracks a CLAMM pool (Concentrated Liquidity AMM).
608+
609+
\sa keylet::clamm
610+
*/
611+
LEDGER_ENTRY(ltCLAMM, 0x008A, CLAMM, clamm, ({
612+
{sfAccount, soeREQUIRED},
613+
{sfAsset, soeREQUIRED},
614+
{sfAsset2, soeREQUIRED},
615+
{sfFeeTier, soeREQUIRED},
616+
{sfTickSpacing, soeREQUIRED},
617+
{sfTradingFee, soeDEFAULT},
618+
{sfCurrentTick, soeREQUIRED},
619+
{sfSqrtPrice, soeREQUIRED},
620+
{sfLiquidityAmount, soeDEFAULT},
621+
{sfFeeGrowthGlobal0, soeDEFAULT},
622+
{sfFeeGrowthGlobal1, soeDEFAULT},
623+
{sfProtocolFees0, soeDEFAULT},
624+
{sfProtocolFees1, soeDEFAULT},
625+
{sfAuctionSlot, soeOPTIONAL},
626+
{sfVoteSlots, soeOPTIONAL},
627+
{sfOwnerNode, soeREQUIRED},
628+
{sfPreviousTxnID, soeOPTIONAL},
629+
{sfPreviousTxnLgrSeq, soeOPTIONAL},
630+
}))
631+
632+
/** A ledger object which tracks a tick in a CLAMM pool.
633+
634+
\sa keylet::clammTick
635+
*/
636+
LEDGER_ENTRY(ltCLAMM_TICK, 0x008B, CLAMMTick, clamm_tick, ({
637+
{sfPoolID, soeREQUIRED},
638+
{sfTickIndex, soeREQUIRED},
639+
{sfLiquidityGross, soeDEFAULT},
640+
{sfLiquidityNet, soeDEFAULT},
641+
{sfFeeGrowthOutside0, soeDEFAULT},
642+
{sfFeeGrowthOutside1, soeDEFAULT},
643+
{sfOwnerNode, soeREQUIRED},
644+
{sfPreviousTxnID, soeREQUIRED},
645+
{sfPreviousTxnLgrSeq, soeREQUIRED},
646+
}))
647+
648+
/** A ledger object which tracks a liquidity position in a CLAMM pool.
649+
650+
\sa keylet::clammPosition
651+
*/
652+
LEDGER_ENTRY(ltCLAMM_POSITION, 0x008C, CLAMMPosition, clamm_position, ({
653+
{sfPoolID, soeREQUIRED},
654+
{sfNFTokenID, soeREQUIRED},
655+
{sfOwner, soeREQUIRED},
656+
{sfLowerTick, soeREQUIRED},
657+
{sfUpperTick, soeREQUIRED},
658+
{sfLiquidityAmount, soeREQUIRED},
659+
{sfFeeGrowthInside0Last, soeDEFAULT},
660+
{sfFeeGrowthInside1Last, soeDEFAULT},
661+
{sfTokensOwed0, soeDEFAULT},
662+
{sfTokensOwed1, soeDEFAULT},
663+
{sfOwnerNode, soeREQUIRED},
664+
{sfPreviousTxnID, soeREQUIRED},
665+
{sfPreviousTxnLgrSeq, soeREQUIRED},
666+
}))
667+
668+
/** A ledger object which stores a bitmap of initialized ticks in a CLAMM pool.
669+
Each entry represents a 256-bit word where each bit indicates whether
670+
a compressed tick is initialized, enabling efficient next-tick lookup.
671+
672+
\sa keylet::clammTickBitmap
673+
*/
674+
LEDGER_ENTRY(ltCLAMM_TICK_BITMAP, 0x008D, CLAMMTickBitmap, clamm_tick_bitmap, ({
675+
{sfPoolID, soeREQUIRED},
676+
{sfTickIndex, soeREQUIRED},
677+
{sfDigest, soeREQUIRED},
678+
{sfOwnerNode, soeREQUIRED},
679+
{sfPreviousTxnID, soeREQUIRED},
680+
{sfPreviousTxnLgrSeq, soeREQUIRED},
681+
}))
682+
606683
#undef EXPAND
607684
#undef LEDGER_ENTRY_DUPLICATE

include/xrpl/protocol/detail/sfields.macro

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ TYPED_SFIELD(sfUNLModifyDisabling, UINT8, 17)
2626
TYPED_SFIELD(sfHookResult, UINT8, 18)
2727
TYPED_SFIELD(sfWasLockingChainSend, UINT8, 19)
2828
TYPED_SFIELD(sfWithdrawalPolicy, UINT8, 20)
29+
TYPED_SFIELD(sfFeeTier, UINT8, 21)
2930

3031
// 16-bit integers (common)
3132
TYPED_SFIELD(sfLedgerEntryType, UINT16, 1, SField::sMD_Never)
@@ -43,6 +44,7 @@ TYPED_SFIELD(sfHookExecutionIndex, UINT16, 19)
4344
TYPED_SFIELD(sfHookApiVersion, UINT16, 20)
4445
TYPED_SFIELD(sfLedgerFixType, UINT16, 21)
4546
TYPED_SFIELD(sfManagementFeeRate, UINT16, 22) // 1/10 basis points (bips)
47+
TYPED_SFIELD(sfTickSpacing, UINT16, 23)
4648

4749
// 32-bit integers (common)
4850
TYPED_SFIELD(sfNetworkID, UINT32, 1)
@@ -147,9 +149,27 @@ TYPED_SFIELD(sfSubjectNode, UINT64, 28)
147149
TYPED_SFIELD(sfLockedAmount, UINT64, 29, SField::sMD_BaseTen|SField::sMD_Default)
148150
TYPED_SFIELD(sfVaultNode, UINT64, 30)
149151
TYPED_SFIELD(sfLoanBrokerNode, UINT64, 31)
152+
TYPED_SFIELD(sfProtocolFees0, UINT64, 32)
153+
TYPED_SFIELD(sfProtocolFees1, UINT64, 33)
154+
TYPED_SFIELD(sfTokensOwed0, UINT64, 34)
155+
TYPED_SFIELD(sfTokensOwed1, UINT64, 35)
156+
TYPED_SFIELD(sfCLAMMNode, UINT64, 36)
150157

151158
// 128-bit
152159
TYPED_SFIELD(sfEmailHash, UINT128, 1)
160+
TYPED_SFIELD(sfSqrtPrice, UINT128, 2)
161+
TYPED_SFIELD(sfLiquidityAmount, UINT128, 3)
162+
TYPED_SFIELD(sfFeeGrowthGlobal0, UINT128, 4)
163+
TYPED_SFIELD(sfFeeGrowthGlobal1, UINT128, 5)
164+
TYPED_SFIELD(sfFeeGrowthOutside0, UINT128, 6)
165+
TYPED_SFIELD(sfFeeGrowthOutside1, UINT128, 7)
166+
TYPED_SFIELD(sfLiquidityGross, UINT128, 8)
167+
TYPED_SFIELD(sfFeeGrowthInside0Last, UINT128, 9)
168+
TYPED_SFIELD(sfFeeGrowthInside1Last, UINT128, 10)
169+
TYPED_SFIELD(sfInitialSqrtPrice, UINT128, 11)
170+
TYPED_SFIELD(sfLiquidityNet, UINT128, 12)
171+
TYPED_SFIELD(sfSqrtPriceLimit, UINT128, 13)
172+
TYPED_SFIELD(sfMinLiquidity, UINT128, 14)
153173

154174
// 160-bit (common)
155175
TYPED_SFIELD(sfTakerPaysCurrency, UINT160, 1)
@@ -204,6 +224,9 @@ TYPED_SFIELD(sfParentBatchID, UINT256, 36)
204224
TYPED_SFIELD(sfLoanBrokerID, UINT256, 37,
205225
SField::sMD_PseudoAccount | SField::sMD_Default)
206226
TYPED_SFIELD(sfLoanID, UINT256, 38)
227+
TYPED_SFIELD(sfPoolID, UINT256, 39)
228+
TYPED_SFIELD(sfCLAMMID, UINT256, 40,
229+
SField::sMD_PseudoAccount | SField::sMD_Default)
207230

208231
// number (common)
209232
TYPED_SFIELD(sfNumber, NUMBER, 1)
@@ -226,6 +249,10 @@ TYPED_SFIELD(sfManagementFeeOutstanding, NUMBER, 17, SField::sMD_NeedsAsset
226249

227250
// int32
228251
TYPED_SFIELD(sfLoanScale, INT32, 1)
252+
TYPED_SFIELD(sfTickIndex, INT32, 2)
253+
TYPED_SFIELD(sfLowerTick, INT32, 3)
254+
TYPED_SFIELD(sfUpperTick, INT32, 4)
255+
TYPED_SFIELD(sfCurrentTick, INT32, 5)
229256

230257
// currency amount (common)
231258
TYPED_SFIELD(sfAmount, AMOUNT, 1)
@@ -241,6 +268,8 @@ TYPED_SFIELD(sfDeliverMin, AMOUNT, 10)
241268
TYPED_SFIELD(sfAmount2, AMOUNT, 11)
242269
TYPED_SFIELD(sfBidMin, AMOUNT, 12)
243270
TYPED_SFIELD(sfBidMax, AMOUNT, 13)
271+
TYPED_SFIELD(sfMinAmount, AMOUNT, 14)
272+
TYPED_SFIELD(sfMinAmount2, AMOUNT, 15)
244273

245274
// currency amount (uncommon)
246275
TYPED_SFIELD(sfMinimumOffer, AMOUNT, 16)
@@ -264,6 +293,10 @@ TYPED_SFIELD(sfSignatureReward, AMOUNT, 29)
264293
TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30)
265294
TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31)
266295

296+
// currency amount (CLAMM)
297+
TYPED_SFIELD(sfMaxAmount, AMOUNT, 32)
298+
TYPED_SFIELD(sfMaxAmount2, AMOUNT, 33)
299+
267300
// variable length (common)
268301
TYPED_SFIELD(sfPublicKey, VL, 1)
269302
TYPED_SFIELD(sfMessageKey, VL, 2)

0 commit comments

Comments
 (0)