Selaa lähdekoodia

Add project documentation (research notes and implementation plan)

- docs/MEMORY.md: Project overview and architecture summary
- docs/technical-research.md: DLMM SDK details, instruction discriminators, monitoring approach
- docs/implementation-plan.md: Full DB schema, 22-file implementation order, code patterns

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
zhangchunrui 3 viikkoa sitten
vanhempi
commit
f7c8eb0ef1
3 muutettua tiedostoa jossa 349 lisäystä ja 0 poistoa
  1. 26 0
      docs/MEMORY.md
  2. 221 0
      docs/implementation-plan.md
  3. 102 0
      docs/technical-research.md

+ 26 - 0
docs/MEMORY.md

@@ -0,0 +1,26 @@
+# Meteora DLMM Copy Trading Project
+
+## Project Overview
+- **Goal:** Monitor Solana wallets on Meteora DLMM, track open/close position operations, auto-copy trades
+- **Stack:** Next.js 16 + TypeScript, @meteora-ag/dlmm 1.9.3, @solana/web3.js, Prisma/SQLite, Zustand
+- **Strategy:** Backend-first (worker + API routes first, frontend dashboard later)
+- **Private key:** Environment variable `FOLLOWER_PRIVATE_KEY`
+- **Scope:** DLMM only (not Dynamic Pools)
+
+## Architecture
+- **Worker process** (standalone tsx): Monitor → Parser → Executor pipeline
+- **Next.js API routes**: CRUD for wallets, positions, trades, settings
+- Shared Prisma/SQLite database between worker and API
+
+## Implementation Status
+- **Phase:** Not started yet (plan approved, dependencies not yet installed)
+- **Todo:** See [implementation-plan.md](implementation-plan.md) for full 22-file implementation order
+
+## Key Technical Details
+- See [technical-research.md](technical-research.md) for DLMM SDK details, instruction formats, discriminators
+- See [implementation-plan.md](implementation-plan.md) for architecture, DB schema, file-by-file plan
+
+## User Preferences
+- Backend-first development priority
+- Private key via env var (not web UI)
+- DLMM only monitoring

+ 221 - 0
docs/implementation-plan.md

@@ -0,0 +1,221 @@
+# Implementation Plan - Meteora DLMM Copy Trading
+
+## Database Schema (Prisma)
+
+```prisma
+generator client {
+  provider = "prisma-client-js"
+}
+
+datasource db {
+  provider = "sqlite"
+  url      = env("DATABASE_URL")
+}
+
+model MonitoredWallet {
+  id        String   @id @default(cuid())
+  address   String   @unique
+  label     String?
+  isActive  Boolean  @default(true)
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+
+  leaderPositions LeaderPosition[]
+  copyTrades      CopyTrade[]
+}
+
+model LeaderPosition {
+  id              String   @id @default(cuid())
+  walletId        String
+  wallet          MonitoredWallet @relation(fields: [walletId], references: [id], onDelete: Cascade)
+  positionAddress String   @unique
+  lbPairAddress   String
+  tokenXMint      String
+  tokenYMint      String
+  lowerBinId      Int
+  upperBinId      Int
+  strategyType    String?
+  status          String   @default("OPEN")
+  openTxSignature   String?
+  closeTxSignature  String?
+  openedAt          DateTime @default(now())
+  closedAt          DateTime?
+
+  followerPosition  FollowerPosition?
+  copyTrades        CopyTrade[]
+}
+
+model FollowerPosition {
+  id                String   @id @default(cuid())
+  leaderPositionId  String   @unique
+  leaderPosition    LeaderPosition @relation(fields: [leaderPositionId], references: [id], onDelete: Cascade)
+  positionAddress   String   @unique
+  lbPairAddress     String
+  lowerBinId        Int
+  upperBinId        Int
+  status            String   @default("OPEN")
+  amountXDeposited  String   @default("0")
+  amountYDeposited  String   @default("0")
+  openedAt          DateTime @default(now())
+  closedAt          DateTime?
+}
+
+model CopyTrade {
+  id                  String   @id @default(cuid())
+  walletId            String
+  wallet              MonitoredWallet @relation(fields: [walletId], references: [id], onDelete: Cascade)
+  leaderPositionId    String?
+  leaderPosition      LeaderPosition? @relation(fields: [leaderPositionId], references: [id], onDelete: SetNull)
+  action              String
+  leaderTxSignature   String
+  followerTxSignature String?
+  lbPairAddress       String
+  tokenXMint          String?
+  tokenYMint          String?
+  leaderAmountX       String?
+  leaderAmountY       String?
+  leaderMinBinId      Int?
+  leaderMaxBinId      Int?
+  leaderBpsToRemove   Int?
+  followerAmountX     String?
+  followerAmountY     String?
+  status              String  @default("PENDING")
+  errorMessage        String?
+  detectedAt          DateTime @default(now())
+  executedAt          DateTime?
+}
+
+model ActivityLog {
+  id        String   @id @default(cuid())
+  type      String
+  message   String
+  metadata  String?
+  createdAt DateTime @default(now())
+}
+```
+
+## File Implementation Order
+
+| # | File | Description |
+|---|------|-------------|
+| 1 | `prisma/schema.prisma` | DB schema (above) |
+| 2 | `.env.local` | Env vars template |
+| 3 | `src/lib/constants.ts` | Program ID, discriminator map |
+| 4 | `src/lib/db/prisma.ts` | Prisma singleton |
+| 5 | `src/lib/utils.ts` | retry, sleep, formatAddress |
+| 6 | `src/lib/validation.ts` | Zod schemas for API inputs |
+| 7 | `src/lib/solana/connection.ts` | Connection factory |
+| 8 | `src/lib/meteora/types.ts` | Parsed operation types |
+| 9 | `src/lib/solana/transaction-parser.ts` | **Core: tx decoder using Anchor IDL** |
+| 10 | `src/lib/meteora/dlmm-client.ts` | DLMM SDK wrapper for copy execution |
+| 11 | `src/lib/db/queries.ts` | DB query functions |
+| 12 | `src/worker/heartbeat.ts` | WebSocket keepalive |
+| 13 | `src/worker/monitor.ts` | logsSubscribe + tx fetching |
+| 14 | `src/worker/position-tracker.ts` | Leader→follower mapping |
+| 15 | `src/worker/executor.ts` | Copy trade execution |
+| 16 | `src/worker/index.ts` | Worker entry point |
+| 17-21 | `src/app/api/*/route.ts` | REST API routes |
+| 22 | `package.json` | Add scripts: "worker": "tsx src/worker/index.ts" |
+
+## Directory Structure
+```
+src/
+  app/
+    layout.tsx
+    page.tsx
+    api/
+      wallets/route.ts
+      positions/route.ts
+      trades/route.ts
+      settings/route.ts
+      health/route.ts
+  lib/
+    constants.ts
+    utils.ts
+    validation.ts
+    db/
+      prisma.ts
+      queries.ts
+    solana/
+      connection.ts
+      transaction-parser.ts
+    meteora/
+      types.ts
+      dlmm-client.ts
+  worker/
+    index.ts
+    monitor.ts
+    heartbeat.ts
+    executor.ts
+    position-tracker.ts
+prisma/
+  schema.prisma
+.env.local
+```
+
+## Key Code Patterns
+
+### Prisma Singleton
+```typescript
+// src/lib/db/prisma.ts
+import { PrismaClient } from '@prisma/client';
+const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined };
+export const prisma = globalForPrisma.prisma ?? new PrismaClient();
+if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
+```
+
+### Transaction Parser Core Logic
+```typescript
+import { BorshInstructionCoder } from '@coral-xyz/anchor';
+import { IDL } from '@meteora-ag/dlmm';
+const coder = new BorshInstructionCoder(IDL);
+// For each instruction in tx:
+//   1. Check programId matches DLMM
+//   2. coder.decode(Buffer.from(ix.data)) → { name, data }
+//   3. Map account indices to public keys (handle v0 lookup tables)
+```
+
+### Worker Event Pipeline
+```typescript
+monitor.on('meteoraTx', async (event) => {
+  const ops = parser.parse(event.tx);
+  for (const op of ops) {
+    if (autoCopyEnabled) await executor.executeCopy(op, event.walletAddress);
+    await tracker.track(op, event.walletAddress);
+  }
+});
+```
+
+### API Route Pattern
+```typescript
+// Zod validation → Prisma query → NextResponse.json()
+const schema = z.object({ address: z.string().refine(isValidSolanaAddress) });
+```
+
+## Env Variables
+```
+DATABASE_URL="file:./dev.db"
+RPC_ENDPOINT="https://api.mainnet-beta.solana.com"
+WS_ENDPOINT="wss://api.mainnet-beta.solana.com"
+FOLLOWER_PRIVATE_KEY="<base58>"
+COPY_RATIO=1.0
+MAX_POSITION_SIZE_SOL=10
+SLIPPAGE_BPS=100
+```
+
+## Verification
+1. Test transaction-parser with known Meteora tx signatures
+2. Start worker, add active LP address, check logs
+3. simulateTransaction for dry-run copy trades
+4. Small-amount live test: open→copy→close→copy-close
+
+## Commands
+```bash
+npx prisma db push    # Create DB tables
+npm run worker        # Start monitor process
+npm run dev           # Start Next.js API
+```
+
+## Next.js Config Notes
+- Need webpack fallback for fs/net/tls/crypto on client side
+- May need `serverComponentsExternalPackages: ['@coral-xyz/anchor', '@meteora-ag/dlmm']`

+ 102 - 0
docs/technical-research.md

@@ -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.