implementation-plan.md 6.3 KB

Implementation Plan - Meteora DLMM Copy Trading

Database Schema (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

// 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

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

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

// 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

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']