|
|
@@ -0,0 +1,102 @@
|
|
|
+# Meteora DLMM Technical Research
|
|
|
+
|
|
|
+## Meteora DLMM Program
|
|
|
+- **Program ID:** `LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo`
|
|
|
+- **SDK:** `@meteora-ag/dlmm` (v1.9.3 in project)
|
|
|
+- **GitHub:** MeteoraAg/dlmm-sdk
|
|
|
+
|
|
|
+## Key SDK Methods
|
|
|
+- `DLMM.create(connection, poolAddress)` - get pool instance
|
|
|
+- `dlmm.initializePositionAndAddLiquidityByStrategy()` - open position + add liquidity in one tx
|
|
|
+- `dlmm.addLiquidityByStrategy({ positionPubKey, totalXAmount, totalYAmount, strategy: { minBinId, maxBinId, strategyType } })`
|
|
|
+- `dlmm.removeLiquidity({ user, position, fromBinId, toBinId, bps, shouldClaimAndClose })`
|
|
|
+- `dlmm.closePosition({ owner, position })` - close position account, recover rent
|
|
|
+- Strategy types: SpotBalanced, BidAsk, Curve
|
|
|
+
|
|
|
+## Position Account Structure
|
|
|
+- Positions are program accounts (NOT NFTs/tokens)
|
|
|
+- Key fields: lowerBinId, upperBinId (computed), lbPair, owner, totalClaimedFees
|
|
|
+- LbPair holds pool data: activeId, binStep, tokenMintX, tokenMintY, reserveX, reserveY
|
|
|
+
|
|
|
+## Instructions to Monitor
|
|
|
+| Instruction | Purpose |
|
|
|
+|---|---|
|
|
|
+| `initializePosition` / `initializePosition2` | Create position account |
|
|
|
+| `addLiquidityByStrategy` / `addLiquidityByStrategy2` | Add liquidity with strategy |
|
|
|
+| `removeLiquidity` / `removeLiquidityByRange` / `removeLiquidityByRange2` | Remove liquidity |
|
|
|
+| `closePosition` / `closePosition2` | Close position account |
|
|
|
+
|
|
|
+## Instruction Discriminators (Anchor 8-byte prefix)
|
|
|
+```typescript
|
|
|
+const DISCRIMINATORS = {
|
|
|
+ addLiquidity: [181, 157, 89, 67, 143, 182, 52, 72],
|
|
|
+ addLiquidityByStrategy: [7, 3, 150, 127, 148, 40, 61, 200],
|
|
|
+ addLiquidityByStrategy2: [3, 221, 149, 218, 111, 141, 118, 213],
|
|
|
+ addLiquidityByStrategyOneSide: [41, 5, 238, 175, 100, 225, 6, 205],
|
|
|
+ removeLiquidity: [80, 85, 209, 72, 24, 206, 177, 108],
|
|
|
+ removeLiquidityByRange: [26, 82, 102, 152, 240, 74, 105, 26],
|
|
|
+ removeLiquidityByRange2: [204, 2, 195, 145, 53, 145, 145, 205],
|
|
|
+ closePosition: [123, 134, 81, 0, 49, 68, 98, 98],
|
|
|
+ closePosition2: [174, 90, 35, 115, 186, 40, 147, 226],
|
|
|
+ initializePosition: [219, 192, 234, 71, 190, 191, 102, 80],
|
|
|
+ initializePosition2: [143, 19, 242, 145, 213, 15, 104, 115],
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+## Instruction Account Layouts (key accounts by index)
|
|
|
+| Instruction | Accounts |
|
|
|
+|---|---|
|
|
|
+| `addLiquidityByStrategy` | [0] position, [1] lbPair, [11] sender |
|
|
|
+| `addLiquidityByStrategy2` | [0] position, [1] lbPair, [9] sender |
|
|
|
+| `initializePosition` | [0] payer, [1] position, [2] lbPair, [3] owner |
|
|
|
+| `removeLiquidity` | [0] position, [1] lbPair, [11] sender |
|
|
|
+| `removeLiquidityByRange` | [0] position, [1] lbPair, [11] sender |
|
|
|
+| `closePosition` | [0] position, [1] lbPair, [4] sender |
|
|
|
+
|
|
|
+## Transaction Monitoring
|
|
|
+- **Method:** `connection.onLogs(walletPubkey, callback, 'confirmed')` per monitored wallet
|
|
|
+- Filter callback logs for DLMM program ID mentions
|
|
|
+- Fetch full tx with `getTransaction(signature, { maxSupportedTransactionVersion: 0 })`
|
|
|
+- **WebSocket keepalive:** ping every 30s, auto-reconnect with exponential backoff
|
|
|
+- `logsSubscribe` mentions filter only supports ONE address per subscription
|
|
|
+
|
|
|
+## Transaction Parsing Approach
|
|
|
+- Use `BorshInstructionCoder` from `@coral-xyz/anchor` with DLMM IDL
|
|
|
+- IDL exported from `@meteora-ag/dlmm` package
|
|
|
+- Handle versioned transactions (v0) with address lookup tables
|
|
|
+- `instructionCoder.decode(Buffer.from(ix.data))` returns `{ name, data }`
|
|
|
+
|
|
|
+## Copy Trade Execution Flow
|
|
|
+
|
|
|
+### Open Position:
|
|
|
+1. Detect leader `addLiquidityByStrategy` on pool X
|
|
|
+2. Extract: lbPairAddress, minBinId, maxBinId, strategyType, amountX, amountY
|
|
|
+3. Apply copy ratio: `followerAmount = leaderAmount * COPY_RATIO`
|
|
|
+4. Cap at MAX_POSITION_SIZE_SOL
|
|
|
+5. Call `dlmm.initializePositionAndAddLiquidityByStrategy()`
|
|
|
+6. Record LeaderPosition + FollowerPosition + CopyTrade in DB
|
|
|
+
|
|
|
+### Close Position:
|
|
|
+1. Detect leader `removeLiquidity` or `closePosition`
|
|
|
+2. Look up follower position from DB
|
|
|
+3. Remove follower's liquidity (100% bps if close)
|
|
|
+4. Call `dlmm.closePosition()` if leader closed
|
|
|
+5. Update statuses in DB
|
|
|
+
|
|
|
+## Proportional Sizing
|
|
|
+```
|
|
|
+followerAmountX = leaderAmountX * (COPY_RATIO * 10000) / 10000
|
|
|
+followerAmountY = leaderAmountY * (COPY_RATIO * 10000) / 10000
|
|
|
+```
|
|
|
+Using BN integer math to avoid floating point issues.
|
|
|
+
|
|
|
+## Reference Projects
|
|
|
+- machenxi/meteora-dlmm-copytrading-bot - leader-follower architecture
|
|
|
+- cutupdev/Solana-Copytrading-bot - multi-DEX support
|
|
|
+- Bitquery Meteora DLMM API for querying historical data
|
|
|
+
|
|
|
+## Dependencies to Install
|
|
|
+```bash
|
|
|
+npm install prisma tsx @coral-xyz/anchor @types/bn.js --save-dev
|
|
|
+```
|
|
|
+`@coral-xyz/anchor` is already a transitive dep of `@meteora-ag/dlmm` but needs explicit install for types.
|