# 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="" 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']`