--- name: alpha-haus-solana description: Interact with alpha.haus directly on Solana blockchain. Read epoch state (TOP ALPHA, TOP BURNER, current epoch, leaderboards), post memos, burn epoch tokens, and tip SOL — all without relying on the web UI. Use when monitoring alpha.haus positions, automating reclaims, or when the web indexer is unreliable. --- # Alpha.haus Solana Integration Skill Direct blockchain interaction for alpha.haus — bypasses the web UI and indexer for reliable state reading and transaction submission. ## Program Overview **alpha.haus** is a Solana-based public square with competitive tipping and token burning mechanics. Every ~48 hours (epoch), the highest tipper (TOP ALPHA) and highest burner (TOP BURNER) earn proportional shares of epoch tokens. ### Core Mechanics | Position | How to Win | Reward | |----------|-----------|--------| | TOP ALPHA | Highest SOL tip (flip = current + 0.001 SOL) | 20% of epoch tokens | | Other Alphas | Any SOL tip during epoch | 20% split among all alphas | | TOP BURNER | Highest token burn (flip = current + 1 token) | 15% of epoch tokens | | Other Burners | Any token burn during epoch | 15% split among all burners | | LP | — | 30% → AMM liquidity | > 🔥 **Epoch Token Interchangeability**: Tokens from ANY previous epoch (EP914, EP916, EP920, etc.) > can be burned to compete for TOP BURNER in the CURRENT epoch. The burn counts toward the active > epoch's competition regardless of which epoch's tokens you burn. Verified Feb 5, 2026 by burning > EP916 tokens during epoch 921. --- ## Critical Constants ```typescript // Program ID (MAINNET - verified on Solscan) const PROGRAM_ID = 'A1PhATY12DpvpHGfGosxuruc7gqkcUUt9eFihb996rNn'; // Program ID (devnet/localnet - from GitHub Anchor.toml, DO NOT USE for mainnet) // const DEVNET_PROGRAM_ID = 'CrKnJCRkfJMAKrAUZL8a6vqRDZMoFng8tRR1k2X3wu2Q'; // Token Constants const TOKEN_DECIMALS = 6; const TOKEN_BASE = 1_000_000; // For amounts: 1 token = 1,000,000 base units // Flip Increment (CRITICAL!) const MINIMUM_FLIP_INCREMENT = 1_000_000; // 0.001 SOL in lamports // For burner: 1 token (1,000,000 base units) // Related Tokens const ALPHA_SOL_MINT = 'A1PHaeFxsDX6Un1g1UpWYR2MDV5vvsv2g4Mi6sNGC5cb'; // alphaSOL LST const WSOL_MINT = 'So11111111111111111111111111111111111111112'; // Distribution per epoch (1M tokens total) const EPOCH_TOKEN_MINT_AMOUNT = 1_000_000 * TOKEN_BASE; // 1M tokens // LP: 30%, Alpha Winner: 20%, Other Alphas: 20%, Top Burner: 15%, Other Burners: 15% // Discriminators (for getProgramAccounts filtering) const EPOCH_STATUS_DISCRIMINATOR = [53, 208, 49, 235, 139, 1, 230, 180]; const TOP_BURNER_DISCRIMINATOR = [219, 107, 67, 174, 204, 88, 22, 90]; // Note: For TOP ALPHA, use direct PDA derivation instead of discriminator filtering (see getCurrentTopAlpha) ``` --- ## ⚠️ CRITICAL: Alpha.haus Epoch vs Solana Cluster Epoch **Alpha.haus has its OWN epoch counter** that is completely separate from Solana's cluster epoch. | Epoch Type | Example Value | How to Get | |------------|---------------|------------| | **Solana Cluster Epoch** | 922 | `connection.getEpochInfo()` | | **Alpha.haus Epoch** | 922 (coincidentally same, but often different!) | `getProgramAccounts()` with discriminator filter | > 🚨 **DO NOT use `connection.getEpochInfo()`** for alpha.haus transactions! The Solana cluster epoch > and alpha.haus epoch are NOT the same. Using the wrong epoch will cause PDA derivation to fail > with `ConstraintSeeds` errors. ### Get Current Alpha.haus Epoch (CORRECT METHOD) ```typescript import { Connection, PublicKey } from '@solana/web3.js'; import bs58 from 'bs58'; const PROGRAM_ID = new PublicKey('A1PhATY12DpvpHGfGosxuruc7gqkcUUt9eFihb996rNn'); const EPOCH_STATUS_DISCRIMINATOR = [53, 208, 49, 235, 139, 1, 230, 180]; async function getAlphaHausEpoch(connection: Connection): Promise { const discriminatorBase58 = bs58.encode(Uint8Array.from(EPOCH_STATUS_DISCRIMINATOR)); const accounts = await connection.getProgramAccounts(PROGRAM_ID, { filters: [ { memcmp: { offset: 0, bytes: discriminatorBase58 } }, { dataSize: 20 } ] }); // Find the highest epoch number (current active epoch) let highestEpoch = 0; for (const acc of accounts) { const epochNum = Number(acc.account.data.readBigUInt64LE(8)); if (epochNum > highestEpoch) { highestEpoch = epochNum; } } return highestEpoch; } // Usage: const epoch = await getAlphaHausEpoch(connection); console.log('Alpha.haus epoch:', epoch); // e.g., 922 ``` This returns the **alpha.haus program's internal epoch counter**, which is what you need for all PDA derivations and transaction building. --- ## PDA Derivation (Critical for RPC) All account addresses are PDAs (Program Derived Addresses). To read state or build transactions, you must derive them correctly: ```typescript import { PublicKey } from '@solana/web3.js'; const PROGRAM_ID = new PublicKey('A1PhATY12DpvpHGfGosxuruc7gqkcUUt9eFihb996rNn'); // Helper: Convert epoch to 8-byte little-endian buffer function epochToBuffer(epoch: number): Buffer { const buf = Buffer.alloc(8); buf.writeBigUInt64LE(BigInt(epoch)); return buf; } // Alpha account (TOP ALPHA for epoch) const [alphaPda] = PublicKey.findProgramAddressSync( [Buffer.from('alpha'), epochToBuffer(epoch)], PROGRAM_ID ); // Top Burner account (TOP BURNER for epoch) const [topBurnerPda] = PublicKey.findProgramAddressSync( [Buffer.from('top_burner'), epochToBuffer(epoch)], PROGRAM_ID ); // Individual tip account const [tipPda] = PublicKey.findProgramAddressSync( [Buffer.from('tip'), epochToBuffer(epoch), Buffer.from(uuid)], PROGRAM_ID ); // Epoch token mint info const [epochTokenMintInfoPda] = PublicKey.findProgramAddressSync( [Buffer.from('epoch_token_mint_info'), epochToBuffer(epoch)], PROGRAM_ID ); // Actual epoch token mint const [epochMintPda] = PublicKey.findProgramAddressSync( [Buffer.from('epoch_mint'), epochToBuffer(epoch)], PROGRAM_ID ); // Was Alpha Tipper (tracks if wallet was ever alpha this epoch) const [wasAlphaTipperPda] = PublicKey.findProgramAddressSync( [Buffer.from('was_alpha_tipper'), epochToBuffer(epoch), walletPubkey.toBuffer()], PROGRAM_ID ); // Was Top Burner (tracks if wallet was ever top burner this epoch) const [wasTopBurnerPda] = PublicKey.findProgramAddressSync( [Buffer.from('was_top_burner'), epochToBuffer(epoch), walletPubkey.toBuffer()], PROGRAM_ID ); // Global config const [globalConfigPda] = PublicKey.findProgramAddressSync( [Buffer.from('global_config')], PROGRAM_ID ); // Protocol vault const [protocolVaultPda] = PublicKey.findProgramAddressSync( [Buffer.from('protocol_vault')], PROGRAM_ID ); ``` --- ## Account Data Layouts ### Alpha Account (629 bytes total) ```typescript // Seeds: ["alpha", epoch.to_le_bytes()] interface Alpha { // Discriminator: 8 bytes (offset 0-7) epochStatus: EpochStatus; // 1 byte (offset 8) tipper: PublicKey; // 32 bytes (offset 9-40) - CURRENT TOP ALPHA memo: string; // 4 + up to 560 bytes (variable) epoch: u64; // 8 bytes uuid: string; // 4 + up to 32 bytes highestTipAmount: u64; // 8 bytes - lamports totalTips: u64; // 8 bytes bump: u8; // 1 byte } // EpochStatus enum enum EpochStatus { Ongoing = 0, FinishedPhase0 = 1, FinishedPhase1 = 2, FinishedPhase2 = 3, FinishedFull = 4 } ``` ### TopBurner Account (629 bytes total) ```typescript // Seeds: ["top_burner", epoch.to_le_bytes()] // Discriminator: [219, 107, 67, 174, 204, 88, 22, 90] interface TopBurner { // Discriminator: 8 bytes (offset 0-7) topBurner: PublicKey; // 32 bytes (offset 8-39) - CURRENT TOP BURNER epoch: u64; // 8 bytes (offset 40-47) burnAmount: u64; // 8 bytes (offset 48-55) - TOKEN AMOUNT (6 decimals) memo: string; // 4 + up to 560 bytes (variable) timestamp: i64; // 8 bytes bump: u8; // 1 byte } ``` ### Tip Account ```typescript // Seeds: ["tip", epoch.to_le_bytes(), uuid.as_bytes()] interface Tip { tipper: PublicKey; // 32 bytes - who sent epoch: u64; // 8 bytes uuid: string; // 4 + 32 bytes max amount: u64; // 8 bytes - lamports timestamp: i64; // 8 bytes bump: u8; // 1 byte } ``` --- ## Reading Current State ### Get Current Alpha.haus Epoch > ⚠️ **Do NOT use `connection.getEpochInfo()`** — that returns the Solana cluster epoch, not the alpha.haus epoch. See the "CRITICAL" section above for the correct method. ```typescript // CORRECT: Use getProgramAccounts to find the alpha.haus epoch const epoch = await getAlphaHausEpoch(connection); // Function defined above ``` ### Read TOP BURNER Position (Verified Method) ```typescript import { Connection, PublicKey } from '@solana/web3.js'; import bs58 from 'bs58'; const PROGRAM_ID = new PublicKey('A1PhATY12DpvpHGfGosxuruc7gqkcUUt9eFihb996rNn'); const TOP_BURNER_DISCRIMINATOR = [219, 107, 67, 174, 204, 88, 22, 90]; async function getTopBurner(connection: Connection) { const accounts = await connection.getProgramAccounts(PROGRAM_ID, { filters: [ { memcmp: { offset: 0, bytes: bs58.encode(Uint8Array.from(TOP_BURNER_DISCRIMINATOR)) } }, { dataSize: 629 } ], encoding: 'base64' }); if (accounts.length === 0) return null; // Parse account with highest epoch (current) let highest = null; for (const { account } of accounts) { const data = Buffer.from(account.data); const epoch = Number(data.readBigUInt64LE(40)); if (!highest || epoch > highest.epoch) { highest = { wallet: new PublicKey(data.subarray(8, 40)).toBase58(), epoch, burnAmount: Number(data.readBigUInt64LE(48)) / 1e6 // Convert to tokens }; } } return highest; } ``` ### Read TOP ALPHA Position ```typescript const ALPHA_DISCRIMINATOR = [/* get from IDL or program */]; async function getTopAlpha(connection: Connection, epoch: number) { const [alphaPda] = PublicKey.findProgramAddressSync( [Buffer.from('alpha'), epochToBuffer(epoch)], PROGRAM_ID ); const account = await connection.getAccountInfo(alphaPda); if (!account) return null; const data = account.data; return { tipper: new PublicKey(data.subarray(9, 41)).toBase58(), epoch: Number(data.readBigUInt64LE(/* offset after memo */)), highestTipAmount: Number(data.readBigUInt64LE(/* offset */)) / 1e9 // lamports to SOL }; } ``` --- ## Posting Transactions ### Instruction: Tip (Post Alpha Memo) — MAINNET VERIFIED **Parameters:** - `epoch: u64` — Current **alpha.haus** epoch (NOT Solana cluster epoch!) - `uuid: String` — 32-character unique ID (no dashes) - `amount: u64` — Tip amount in lamports - `message: String` — Memo text (max 560 characters) - `tagged_addresses: Vec` — Addresses to tag (4-byte length prefix + pubkeys) > 📌 **Tagging**: Use `Vec` format with 4-byte length prefix, NOT a single pubkey. > Empty tag = `[0, 0, 0, 0]` (length 0). Verified working Feb 5, 2026. **Accounts Required (MAINNET — 6 accounts):** ⚠️ **The mainnet program differs from the GitHub devnet code!** | # | Account | Seeds | Writable | Signer | |---|---------|-------|----------|--------| | 0 | tipper | — | ✓ | ✓ | | 1 | epoch_status_info | `["epoch_status_info", epoch]` | ✓ | | | 2 | alpha | `["alpha", epoch]` | ✓ | | | 3 | other_alphas_info | `["other_alphas_info", epoch]` | ✓ | | | 4 | was_alpha_tipper | `["was_alpha_tipper", epoch, tipper]` | ✓ | | | 5 | system_program | — | | | ```typescript import { Connection, PublicKey, Keypair, TransactionInstruction, ComputeBudgetProgram, SystemProgram, VersionedTransaction, TransactionMessage } from '@solana/web3.js'; import crypto from 'crypto'; import bs58 from 'bs58'; const PROGRAM_ID = new PublicKey('A1PhATY12DpvpHGfGosxuruc7gqkcUUt9eFihb996rNn'); const EPOCH_STATUS_DISCRIMINATOR = [53, 208, 49, 235, 139, 1, 230, 180]; // Anchor discriminator function getDiscriminator(name: string): Buffer { return crypto.createHash('sha256').update(\`global:\${name}\`).digest().slice(0, 8); } // Get alpha.haus epoch (NOT Solana cluster epoch!) async function getAlphaHausEpoch(connection: Connection): Promise { const accounts = await connection.getProgramAccounts(PROGRAM_ID, { filters: [ { memcmp: { offset: 0, bytes: bs58.encode(Uint8Array.from(EPOCH_STATUS_DISCRIMINATOR)) } }, { dataSize: 20 } ] }); let highest = 0; for (const acc of accounts) { const epochNum = Number(acc.account.data.readBigUInt64LE(8)); if (epochNum > highest) highest = epochNum; } return highest; } // Get current TOP ALPHA tip amount for an epoch (via direct PDA derivation) async function getCurrentTopAlpha(connection: Connection, epoch: number): Promise<{wallet: string, amount: bigint} | null> { const epochBuf = Buffer.alloc(8); epochBuf.writeBigUInt64LE(BigInt(epoch)); const [alphaPda] = PublicKey.findProgramAddressSync( [Buffer.from('alpha'), epochBuf], PROGRAM_ID ); const accountInfo = await connection.getAccountInfo(alphaPda); if (!accountInfo) return null; const data = accountInfo.data; // Alpha layout: discriminator(8) + epochStatus(1) + tipper(32) + memo + epoch + uuid + highestTipAmount... // Tipper is at offset 9, highestTipAmount discovered at offset 242 const wallet = new PublicKey(data.subarray(9, 41)).toBase58(); const amount = data.readBigUInt64LE(242); // lamports return { wallet, amount }; } // Calculate minimum tip to become TOP ALPHA const MINIMUM_FLIP_LAMPORTS = 1_000_000n; // 0.001 SOL const MINIMUM_TIP_LAMPORTS = 8_000_000n; // 0.008 SOL minimum first tip function calculateFlipAmount(currentAmount: bigint | null): bigint { if (!currentAmount || currentAmount === 0n) { return MINIMUM_TIP_LAMPORTS; // First tip of epoch } return currentAmount + MINIMUM_FLIP_LAMPORTS; // Must exceed by 0.001 SOL } async function postAlphaMemo(connection: Connection, wallet: Keypair, message: string, amount?: bigint) { // IMPORTANT: Use alpha.haus epoch, NOT connection.getEpochInfo()! const epochNum = await getAlphaHausEpoch(connection); const epoch = BigInt(epochNum); // If amount not specified, calculate flip amount automatically if (!amount) { const currentAlpha = await getCurrentTopAlpha(connection, epochNum); amount = calculateFlipAmount(currentAlpha?.amount ?? null); console.log('Current TOP ALPHA:', currentAlpha ? \`\${Number(currentAlpha.amount) / 1e9} SOL\` : 'none'); console.log('Bidding:', Number(amount) / 1e9, 'SOL'); } const uuid = crypto.randomUUID().replace(/-/g, ''); // Derive all PDAs const epochBuffer = Buffer.alloc(8); epochBuffer.writeBigUInt64LE(epoch); const [epochStatusInfoPda] = PublicKey.findProgramAddressSync( [Buffer.from('epoch_status_info'), epochBuffer], PROGRAM_ID ); const [alphaPda] = PublicKey.findProgramAddressSync( [Buffer.from('alpha'), epochBuffer], PROGRAM_ID ); const [otherAlphasInfoPda] = PublicKey.findProgramAddressSync( [Buffer.from('other_alphas_info'), epochBuffer], PROGRAM_ID ); const [wasAlphaTipperPda] = PublicKey.findProgramAddressSync( [Buffer.from('was_alpha_tipper'), epochBuffer, wallet.publicKey.toBuffer()], PROGRAM_ID ); // Build instruction data const discriminator = getDiscriminator('tip'); const uuidBytes = Buffer.from(uuid, 'utf8'); const messageBytes = Buffer.from(message, 'utf8'); // tagged_addresses as Vec (can be empty or contain wallet addresses to tag) const taggedAddresses = []; // Empty array for no tags, or [new PublicKey('...')] to tag someone const dataLen = 8 + 8 + (4 + uuidBytes.length) + 8 + (4 + messageBytes.length) + (4 + taggedAddresses.length * 32); const data = Buffer.alloc(dataLen); let offset = 0; discriminator.copy(data, offset); offset += 8; data.writeBigUInt64LE(epoch, offset); offset += 8; data.writeUInt32LE(uuidBytes.length, offset); offset += 4; uuidBytes.copy(data, offset); offset += uuidBytes.length; data.writeBigUInt64LE(amount, offset); offset += 8; data.writeUInt32LE(messageBytes.length, offset); offset += 4; messageBytes.copy(data, offset); offset += messageBytes.length; // Vec - 4 byte length prefix + pubkey bytes data.writeUInt32LE(taggedAddresses.length, offset); offset += 4; for (const addr of taggedAddresses) { addr.toBuffer().copy(data, offset); offset += 32; } // 6 accounts in exact order const keys = [ { pubkey: wallet.publicKey, isSigner: true, isWritable: true }, { pubkey: epochStatusInfoPda, isSigner: false, isWritable: true }, { pubkey: alphaPda, isSigner: false, isWritable: true }, { pubkey: otherAlphasInfoPda, isSigner: false, isWritable: true }, { pubkey: wasAlphaTipperPda, isSigner: false, isWritable: true }, { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, ]; const tipInstruction = new TransactionInstruction({ keys, programId: PROGRAM_ID, data }); const { blockhash } = await connection.getLatestBlockhash(); const messageV0 = new TransactionMessage({ payerKey: wallet.publicKey, recentBlockhash: blockhash, instructions: [ ComputeBudgetProgram.setComputeUnitLimit({ units: 300000 }), ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 50000 }), tipInstruction ] }).compileToV0Message(); const tx = new VersionedTransaction(messageV0); tx.sign([wallet]); const sig = await connection.sendRawTransaction(tx.serialize(), { skipPreflight: true }); console.log('Tip transaction:', sig); return sig; } // Usage: // Auto-calculate flip amount (recommended): // await postAlphaMemo(connection, wallet, "Hello from AI!"); // Checks current TOP ALPHA, bids +0.001 // // Or specify exact amount: // await postAlphaMemo(connection, wallet, "Hello from AI!", BigInt(9_000_000)); // 0.009 SOL ``` ### Instruction: Burn Tokens (Post Burner Memo) — MAINNET VERIFIED > ⚠️ **CRITICAL**: The mainnet program differs from the GitHub source code. This section is based on > reverse-engineering successful mainnet transactions (verified Feb 5, 2026). **Discriminator:** `[76, 15, 51, 254, 229, 215, 121, 66]` **Instruction Data (in order):** 1. Discriminator (8 bytes) 2. `curr_epoch: u64` — The currently active **alpha.haus** epoch (NOT Solana cluster epoch!) 3. `burn_epoch: u64` — Which epoch's tokens you're burning (can differ from curr_epoch!) 4. `burn_amount: u64` — Amount with 6 decimals (56 tokens = 56_000_000) 5. `memo: String` — 4-byte length prefix + UTF-8 bytes (max 560 chars) 6. `tagged_addresses: Vec` — 4-byte length prefix + 32-byte pubkeys **Accounts Required (9 total, exact order):** | # | Name | Seeds | Writable | Signer | |---|------|-------|----------|--------| | 0 | `epoch_status_info` | `["epoch_status_info", curr_epoch]` | ✓ | | | 1 | `top_burner` | `["top_burner", curr_epoch]` | ✓ | | | 2 | `other_burners_info` | `["other_burners_info", curr_epoch]` | ✓ | | | 3 | `user_token_account` | User's ATA for burn_epoch token | ✓ | | | 4 | `token_mint` | Epoch token mint for burn_epoch | ✓ | | | 5 | `was_top_burner` | `["was_top_burner", curr_epoch, user]` | ✓ | | | 6 | `user` | — | ✓ | ✓ | | 7 | `token_program` | Token 2022 Program | | | | 8 | `system_program` | — | | | > ⚠️ **IMPORTANT**: `was_top_burner` uses `curr_epoch`, NOT `burn_epoch`! **Key Insight — Cross-Epoch Burns:** You can burn tokens from ANY previous epoch to compete in the CURRENT epoch's TOP BURNER competition. For example, you can burn EP916 tokens while in epoch 921, as long as you use the correct mint address for EP916. ```typescript const { Connection, PublicKey, Keypair, TransactionMessage, VersionedTransaction, TransactionInstruction, ComputeBudgetProgram } = require('@solana/web3.js'); const { getAssociatedTokenAddress, TOKEN_2022_PROGRAM_ID } = require('@solana/spl-token'); const PROGRAM_ID = new PublicKey('A1PhATY12DpvpHGfGosxuruc7gqkcUUt9eFihb996rNn'); const BURN_DISCRIMINATOR = Buffer.from([76, 15, 51, 254, 229, 215, 121, 66]); async function burnTokens( connection: Connection, wallet: Keypair, currEpoch: bigint, // Current active epoch (e.g., 921n) burnEpoch: bigint, // Which epoch's tokens to burn (e.g., 916n) tokenMint: PublicKey, // Mint address for burnEpoch tokens burnAmount: bigint, // Amount with 6 decimals (56 tokens = 56_000_000n) memo: string, taggedAddresses: PublicKey[] = [] ) { // Derive PDAs const epochBuf = Buffer.alloc(8); epochBuf.writeBigUInt64LE(currEpoch); const [epochStatusInfo] = PublicKey.findProgramAddressSync( [Buffer.from('epoch_status_info'), epochBuf], PROGRAM_ID ); const [topBurner] = PublicKey.findProgramAddressSync( [Buffer.from('top_burner'), epochBuf], PROGRAM_ID ); const [otherBurnersInfo] = PublicKey.findProgramAddressSync( [Buffer.from('other_burners_info'), epochBuf], PROGRAM_ID ); const [wasTopBurner] = PublicKey.findProgramAddressSync( [Buffer.from('was_top_burner'), epochBuf, wallet.publicKey.toBuffer()], PROGRAM_ID ); // User's ATA for the burn epoch tokens const userTokenAccount = await getAssociatedTokenAddress( tokenMint, wallet.publicKey, false, TOKEN_2022_PROGRAM_ID ); // Build instruction data const memoBytes = Buffer.from(memo, 'utf8'); const dataLen = 8 + 8 + 8 + 8 + 4 + memoBytes.length + 4 + (taggedAddresses.length * 32); const data = Buffer.alloc(dataLen); let offset = 0; BURN_DISCRIMINATOR.copy(data, offset); offset += 8; data.writeBigUInt64LE(currEpoch, offset); offset += 8; data.writeBigUInt64LE(burnEpoch, offset); offset += 8; data.writeBigUInt64LE(burnAmount, offset); offset += 8; data.writeUInt32LE(memoBytes.length, offset); offset += 4; memoBytes.copy(data, offset); offset += memoBytes.length; data.writeUInt32LE(taggedAddresses.length, offset); const burnIx = new TransactionInstruction({ programId: PROGRAM_ID, keys: [ { pubkey: epochStatusInfo, isSigner: false, isWritable: true }, { pubkey: topBurner, isSigner: false, isWritable: true }, { pubkey: otherBurnersInfo, isSigner: false, isWritable: true }, { pubkey: userTokenAccount, isSigner: false, isWritable: true }, { pubkey: tokenMint, isSigner: false, isWritable: true }, { pubkey: wasTopBurner, isSigner: false, isWritable: true }, { pubkey: wallet.publicKey, isSigner: true, isWritable: true }, { pubkey: TOKEN_2022_PROGRAM_ID, isSigner: false, isWritable: false }, { pubkey: new PublicKey('11111111111111111111111111111111'), isSigner: false, isWritable: false }, ], data }); const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(); const messageV0 = new TransactionMessage({ payerKey: wallet.publicKey, recentBlockhash: blockhash, instructions: [ ComputeBudgetProgram.setComputeUnitLimit({ units: 200000 }), ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 50000 }), burnIx ] }).compileToV0Message(); const tx = new VersionedTransaction(messageV0); tx.sign([wallet]); const sig = await connection.sendTransaction(tx); console.log('Burn transaction:', sig); return sig; } // Usage example: // Burn 56 EP916 tokens while in epoch 921 to become TOP BURNER const EP916_MINT = new PublicKey('AnQJpegZu3wT9FbFBi29sFiRR7w9rcRRCkjuxQm9uZ11'); await burnTokens(connection, wallet, 921n, 916n, EP916_MINT, 56_000_000n, "Becoming TOP BURNER!"); ``` **Finding Epoch Token Mints:** Each epoch has its own token mint. Known mints: - EP916: `AnQJpegZu3wT9FbFBi29sFiRR7w9rcRRCkjuxQm9uZ11` - EP920: `C5Dpe7AJuhS2zN4ZVPnRQbNnsAqHDE9Cvjyi92aeB4d` To find a mint, check your wallet's token balances or look up the `epoch_token_mint_info` PDA data. --- ## Flip Mechanics To become TOP ALPHA or TOP BURNER, you must **exceed** the current holder's amount by at least `MINIMUM_FLIP_INCREMENT`: ```typescript // For TOP ALPHA (SOL tips) const MINIMUM_FLIP_SOL = 0.001; // 1,000,000 lamports const flipAlphaAmount = currentHighestTip + (0.001 * LAMPORTS_PER_SOL); // For TOP BURNER (token burns) const MINIMUM_FLIP_TOKENS = 1; // 1,000,000 base units (1 token with 6 decimals) const flipBurnerAmount = currentHighestBurn + 1_000_000; ``` **Example:** If TOP BURNER has burned 43 tokens, you need to burn **at least 44 tokens** to flip. > ⚠️ **ALWAYS CHECK CURRENT POSITION VIA RPC BEFORE BIDDING!** > > A transaction that "succeeds" does NOT mean you became TOP ALPHA/BURNER — it only means your tip/burn was recorded. > If you bid the same amount as the current holder (or less), you create an entry but do NOT flip the position. > > **Correct procedure:** > 1. Query current TOP ALPHA/BURNER amount via RPC > 2. Calculate flip amount: `current + 0.001 SOL` (or `current + 1 token`) > 3. Submit transaction with calculated amount > 4. Verify you now hold the position --- ## Monitoring & Auto-Reclaim ### Check Position Script ```typescript // scripts/check-position.js import { Connection, PublicKey } from '@solana/web3.js'; const MY_WALLET = '2edUH4iRMzbtCaNFB81vm7mVnKpaBhJbUaHQrefjpaaU'; async function checkPosition() { const connection = new Connection(process.env.HELIUS_RPC_URL); const topBurner = await getTopBurner(connection); if (topBurner.wallet === MY_WALLET) { console.log('✅ You are TOP BURNER'); console.log(\` Burn amount: \${topBurner.burnAmount} tokens\`); } else { console.log('❌ You are NOT top burner'); console.log(\` Current top: \${topBurner.wallet}\`); console.log(\` Their burn: \${topBurner.burnAmount} tokens\`); console.log(\` Flip amount: \${topBurner.burnAmount + 1} tokens\`); } } ``` ### Automated Monitor See `scripts/monitor.js` for a complete monitoring solution that: 1. Polls TOP BURNER position every 60 seconds 2. Detects flip events (when you lose position) 3. Triggers auto-reclaim via cron job 4. Logs all activity --- ## Error Handling Common errors from the program: | Error | Cause | Solution | |-------|-------|----------| | `EpochMismatch` | Passed epoch doesn't match current chain epoch | Get fresh epoch from `getEpochInfo()` | | `MessageTooLong` | Message exceeds 560 characters | Truncate message | | Insufficient funds | Not enough SOL for tip + rent | Check balance | | Token account missing | No ATA for epoch tokens | Create ATA first | --- ## Dependencies ```bash npm install @solana/web3.js @solana/spl-token @coral-xyz/anchor bs58 uuid ``` For TypeScript: ```bash npm install -D typescript ts-node @types/node ``` --- ## File Structure ``` skills/alpha-haus-solana/ ├── SKILL.md # This file ├── STRATEGY.md # Posting strategy and bidding rules ├── .env # RPC URLs, wallet paths (gitignored) ├── config/ │ └── epoch-mints.json # Epoch → mint address mapping (auto-updated) ├── scripts/ │ ├── monitor.js # Automated position monitor │ ├── check-position.js # Check current TOP BURNER │ ├── post-burner.js # Post burner memo │ ├── post-alpha.js # Post alpha memo │ └── claim-v2.js # Claim epoch rewards (--scan, --check, --discover, claim) ├── references/ │ ├── program-accounts.md # Detailed account layouts │ └── idl.json # Program IDL (if available) ├── logs/ │ ├── monitor.log # Monitor activity log │ └── alert-*.json # Flip alert files └── tasks/ └── post.md # Posting guidelines ``` --- ## Claiming Epoch Rewards After an epoch ends, participants can claim their share of epoch tokens and ALPHA tokens. ### Reward Distribution | Role | Epoch Token Share | ALPHA Token Share | |------|-------------------|-------------------| | TOP ALPHA | 20% | Pro-rata | | Other Alphas | 20% (split) | Pro-rata | | TOP BURNER | 15% | Pro-rata | | Other Burners | 15% (split) | Pro-rata | | LP | 30% | — | ### Claim Instruction **Discriminator:** `[62, 198, 214, 193, 213, 159, 108, 210]` **Instruction Data:** 1. Discriminator (8 bytes) 2. `epoch: u64` — The epoch to claim rewards from **Accounts Required (19 total):** | # | Name | Seeds | Writable | |---|------|-------|----------| | 0 | claimer | — (signer) | ✓ | | 1 | epoch_status_info | `["epoch_status_info", epoch]` | ✓ | | 2 | alpha | `["alpha", epoch]` | | | 3 | top_burner | `["top_burner", epoch]` | | | 4 | claimer_epoch_ata | User's ATA for epoch token | ✓ | | 5 | claimer_alpha_ata | User's ATA for ALPHA token | ✓ | | 6 | was_alpha_tipper | `["was_alpha_tipper", epoch, claimer]` | ✓ | | 7 | was_top_burner | `["was_top_burner", epoch, claimer]` | ✓ | | 8 | other_alphas_info | `["other_alphas_info", epoch]` | ✓ | | 9 | other_alphas_epoch_vault | ATA(epoch_mint, other_alphas_info) | ✓ | | 10 | other_alphas_alpha_vault | ATA(ALPHA_mint, other_alphas_info) | ✓ | | 11 | other_burners_info | `["other_burners_info", epoch]` | ✓ | | 12 | other_burners_epoch_vault | ATA(epoch_mint, other_burners_info) | ✓ | | 13 | other_burners_alpha_vault | ATA(ALPHA_mint, other_burners_info) | ✓ | | 14 | alpha_mint | `7TqsGco29Z6TWndzxzzRCKWLzSK3PEYp4k8jaPeV9j11` | | | 15 | epoch_mint | Varies per epoch (see known mints) | | | 16 | token_program | Token 2022 Program | | | 17 | ata_program | Associated Token Program | | | 18 | system_program | System Program | | ### Epoch Mint Discovery Each epoch has a unique token mint. Mints are stored in `config/epoch-mints.json` and can be discovered or added manually. **Discovery methods:** 1. **Automatic:** The claim script tries to discover mints from on-chain vault ATAs and recent claim transactions 2. **Manual:** Add known mints via `--add-mint` command ```bash # Discover mint for an epoch (searches chain data) node scripts/claim-v2.js --discover 926 # Discover and save to config node scripts/claim-v2.js --discover 926 --save # Manually add a known mint node scripts/claim-v2.js --add-mint 926 ``` **Config file:** `config/epoch-mints.json` ```json { "mints": { "921": "EvVTZ27g5V4agoiSiDti3kXCGqbJkyrYCd2SCxJXaqcQ", "923": "9YGdc2n4hT2tCKuVAvYSDUysb2PhSA2ThwQ3HgtU1AxH" } } ``` ### Checking Claim Status The `was_alpha_tipper` and `was_top_burner` PDAs track participation and claim status: ```javascript // Structure (52 bytes): // 0-7: discriminator (8) // 8-39: wallet pubkey (32) // 40-47: epoch (8) // 48: counter (1) — how many times you held the position // 49: claimed (1) — 0 = unclaimed, 1 = claimed // 50: bump (1) // 51: padding (1) async function checkClaimStatus(connection, wallet, epoch) { const epochBuf = Buffer.alloc(8); epochBuf.writeBigUInt64LE(BigInt(epoch)); const [wasAlpha] = PublicKey.findProgramAddressSync( [Buffer.from('was_alpha_tipper'), epochBuf, wallet.toBuffer()], PROGRAM_ID ); const [wasBurner] = PublicKey.findProgramAddressSync( [Buffer.from('was_top_burner'), epochBuf, wallet.toBuffer()], PROGRAM_ID ); const [alphaInfo, burnerInfo] = await Promise.all([ connection.getAccountInfo(wasAlpha), connection.getAccountInfo(wasBurner) ]); return { hasAlpha: !!alphaInfo, hasBurner: !!burnerInfo, alphaClaimed: alphaInfo ? alphaInfo.data[49] === 1 : null, burnerClaimed: burnerInfo ? burnerInfo.data[49] === 1 : null, }; } ``` ### Partial Participation Claims (Alpha-only or Burner-only) The claim instruction supports claiming with **only alpha** or **only burner** participation by using the **PROGRAM_ID as a placeholder** for the missing account. **Alpha-only claim:** Pass `PROGRAM_ID` as the `was_top_burner` account (index 7) **Burner-only claim:** Pass `PROGRAM_ID` as the `was_alpha_tipper` account (index 6) ```javascript // Alpha-only: use PROGRAM_ID as wasTopBurner placeholder const keys = [ // ... accounts 0-6 ... { pubkey: wasAlphaTipper, isSigner: false, isWritable: true }, // 6: exists { pubkey: PROGRAM_ID, isSigner: false, isWritable: false }, // 7: PLACEHOLDER // ... rest of accounts ... ]; // Burner-only: use PROGRAM_ID as wasAlphaTipper placeholder const keys = [ // ... accounts 0-5 ... { pubkey: PROGRAM_ID, isSigner: false, isWritable: false }, // 6: PLACEHOLDER { pubkey: wasTopBurner, isSigner: false, isWritable: true }, // 7: exists // ... rest of accounts ... ]; ``` **Note:** Some epochs may have incomplete infrastructure (missing epoch token vaults). In these cases, the claim will fail even with correct account structure. Use `--scan` to identify claimable epochs. ### Complete Claim Code Example Full, copy-paste-able code for claiming epoch rewards: ```javascript const { Connection, PublicKey, Keypair, TransactionMessage, VersionedTransaction, TransactionInstruction, ComputeBudgetProgram, SystemProgram } = require('@solana/web3.js'); const { getAssociatedTokenAddress, TOKEN_2022_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } = require('@solana/spl-token'); const PROGRAM_ID = new PublicKey('A1PhATY12DpvpHGfGosxuruc7gqkcUUt9eFihb996rNn'); const ALPHA_MINT = new PublicKey('7TqsGco29Z6TWndzxzzRCKWLzSK3PEYp4k8jaPeV9j11'); const CLAIM_DISCRIMINATOR = Buffer.from([62, 198, 214, 193, 213, 159, 108, 210]); function epochToBuffer(epoch) { const buf = Buffer.alloc(8); buf.writeBigUInt64LE(BigInt(epoch)); return buf; } /** * Claim epoch rewards * @param connection - Solana connection * @param wallet - Keypair to claim with * @param epoch - Epoch number to claim * @param epochMint - Token mint address for this epoch (PublicKey) * @param hasAlpha - Whether wallet participated as alpha tipper * @param hasBurner - Whether wallet participated as burner */ async function claimEpochRewards(connection, wallet, epoch, epochMint, hasAlpha, hasBurner) { const epochBuf = epochToBuffer(epoch); // Derive all PDAs const [epochStatusInfo] = PublicKey.findProgramAddressSync( [Buffer.from('epoch_status_info'), epochBuf], PROGRAM_ID); const [alpha] = PublicKey.findProgramAddressSync( [Buffer.from('alpha'), epochBuf], PROGRAM_ID); const [topBurner] = PublicKey.findProgramAddressSync( [Buffer.from('top_burner'), epochBuf], PROGRAM_ID); const [wasAlphaTipper] = PublicKey.findProgramAddressSync( [Buffer.from('was_alpha_tipper'), epochBuf, wallet.publicKey.toBuffer()], PROGRAM_ID); const [wasTopBurner] = PublicKey.findProgramAddressSync( [Buffer.from('was_top_burner'), epochBuf, wallet.publicKey.toBuffer()], PROGRAM_ID); const [otherAlphasInfo] = PublicKey.findProgramAddressSync( [Buffer.from('other_alphas_info'), epochBuf], PROGRAM_ID); const [otherBurnersInfo] = PublicKey.findProgramAddressSync( [Buffer.from('other_burners_info'), epochBuf], PROGRAM_ID); // Derive ATAs const claimerEpochAta = await getAssociatedTokenAddress( epochMint, wallet.publicKey, false, TOKEN_2022_PROGRAM_ID); const claimerAlphaAta = await getAssociatedTokenAddress( ALPHA_MINT, wallet.publicKey, false, TOKEN_2022_PROGRAM_ID); const otherAlphasEpochVault = await getAssociatedTokenAddress( epochMint, otherAlphasInfo, true, TOKEN_2022_PROGRAM_ID); const otherAlphasAlphaVault = await getAssociatedTokenAddress( ALPHA_MINT, otherAlphasInfo, true, TOKEN_2022_PROGRAM_ID); const otherBurnersEpochVault = await getAssociatedTokenAddress( epochMint, otherBurnersInfo, true, TOKEN_2022_PROGRAM_ID); const otherBurnersAlphaVault = await getAssociatedTokenAddress( ALPHA_MINT, otherBurnersInfo, true, TOKEN_2022_PROGRAM_ID); // Build instruction data const data = Buffer.alloc(16); CLAIM_DISCRIMINATOR.copy(data, 0); data.writeBigUInt64LE(BigInt(epoch), 8); // Build accounts - use PROGRAM_ID as placeholder for missing participation const keys = [ { pubkey: wallet.publicKey, isSigner: true, isWritable: true }, { pubkey: epochStatusInfo, isSigner: false, isWritable: true }, { pubkey: alpha, isSigner: false, isWritable: false }, { pubkey: topBurner, isSigner: false, isWritable: false }, { pubkey: claimerEpochAta, isSigner: false, isWritable: true }, { pubkey: claimerAlphaAta, isSigner: false, isWritable: true }, { pubkey: hasAlpha ? wasAlphaTipper : PROGRAM_ID, isSigner: false, isWritable: hasAlpha }, { pubkey: hasBurner ? wasTopBurner : PROGRAM_ID, isSigner: false, isWritable: hasBurner }, { pubkey: otherAlphasInfo, isSigner: false, isWritable: true }, { pubkey: otherAlphasEpochVault, isSigner: false, isWritable: true }, { pubkey: otherAlphasAlphaVault, isSigner: false, isWritable: true }, { pubkey: otherBurnersInfo, isSigner: false, isWritable: true }, { pubkey: otherBurnersEpochVault, isSigner: false, isWritable: true }, { pubkey: otherBurnersAlphaVault, isSigner: false, isWritable: true }, { pubkey: ALPHA_MINT, isSigner: false, isWritable: false }, { pubkey: epochMint, isSigner: false, isWritable: false }, { pubkey: TOKEN_2022_PROGRAM_ID, isSigner: false, isWritable: false }, { pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, ]; const claimIx = new TransactionInstruction({ programId: PROGRAM_ID, keys, data }); const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(); const messageV0 = new TransactionMessage({ payerKey: wallet.publicKey, recentBlockhash: blockhash, instructions: [ ComputeBudgetProgram.setComputeUnitLimit({ units: 250000 }), ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 50000 }), claimIx ] }).compileToV0Message(); const tx = new VersionedTransaction(messageV0); tx.sign([wallet]); const sig = await connection.sendTransaction(tx, { skipPreflight: false }); await connection.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight }); return sig; } // Usage: // const epochMint = new PublicKey('EvVTZ27g5V4agoiSiDti3kXCGqbJkyrYCd2SCxJXaqcQ'); // EP921 // await claimEpochRewards(connection, wallet, 921, epochMint, true, true); ``` ### Discovering Epoch Mints Programmatically ```javascript const bs58 = require('bs58'); /** * Discover epoch mint by checking vault ATAs */ async function discoverEpochMint(connection, epoch) { const epochBuf = epochToBuffer(epoch); const [otherAlphasInfo] = PublicKey.findProgramAddressSync( [Buffer.from('other_alphas_info'), epochBuf], PROGRAM_ID ); // Get all Token-2022 accounts owned by the vault PDA const tokenAccounts = await connection.getTokenAccountsByOwner(otherAlphasInfo, { programId: TOKEN_2022_PROGRAM_ID }); for (const { account } of tokenAccounts.value) { const mint = new PublicKey(account.data.slice(0, 32)); // Skip ALPHA token if (mint.toBase58() === '7TqsGco29Z6TWndzxzzRCKWLzSK3PEYp4k8jaPeV9j11') continue; return mint.toBase58(); } return null; } ``` ### Claim Script Usage ```bash # Scan recent epochs (last 20) for unclaimed rewards node scripts/claim-v2.js --scan # Check status for specific epoch node scripts/claim-v2.js --check # Claim rewards for an epoch (auto-discovers mint if needed) node scripts/claim-v2.js # Discover epoch mint from chain data node scripts/claim-v2.js --discover node scripts/claim-v2.js --discover --save # Save to config # Manually register an epoch mint node scripts/claim-v2.js --add-mint ``` The script automatically: - Detects participation type (alpha-only, burner-only, or both) - Uses PROGRAM_ID placeholder for missing accounts - Attempts mint discovery if not in config - Validates vault existence before claiming - Validates vault existence before claiming --- ## Quick Reference Card | Action | Function | Key Params | |--------|----------|------------| | Read TOP BURNER | `getProgramAccounts` | discriminator filter, dataSize: 629 | | Read TOP ALPHA | `getAccountInfo(alphaPda)` | Seeds: `["alpha", epoch_le]` | | Post Alpha Memo | `program.methods.tip()` | epoch, uuid, amount (lamports), message | | Post Burner Memo | `program.methods.burnTokens()` | epoch, burnAmount (6 decimals), memo | | Flip TOP ALPHA | tip current + 0.001 SOL | 1,000,000 lamports extra | | Flip TOP BURNER | burn current + 1 token | 1,000,000 base units extra | | Claim Rewards | `claim` instruction | epoch, requires both was_alpha + was_burner | | Check Claim Status | Read `was_alpha_tipper` / `was_top_burner` | byte 49 = claimed flag | **Program ID:** `A1PhATY12DpvpHGfGosxuruc7gqkcUUt9eFihb996rNn` --- ## Important Notes 1. **Transactions are PERMANENT** — no undo on blockchain 2. **Message limit:** 560 characters max 3. **Epoch tokens are fungible:** EP914, EP916, EP920 etc. all burn equally for TOP BURNER 4. **Rate limits:** Use Helius/Quicknode, not public mainnet-beta RPC 5. **Always verify state** via RPC before posting (don't trust cached/stale data) 6. **Epoch boundary:** Don't post if <5 minutes remaining (may miss cutoff) 7. **⚠️ EPOCH WARNING:** Alpha.haus has its OWN epoch counter separate from Solana's cluster epoch. Use `getProgramAccounts` with the epoch_status discriminator to get the correct epoch — **never use `connection.getEpochInfo()`** for PDA derivation! --- ## Development Notes ### Reverse Engineering Approach **When documentation doesn't match reality:** 1. Find a successful mainnet transaction on Solscan 2. Open "Instruction Data" tab — shows exact hex bytes 3. Open "Accounts" tab — shows exact account order 4. Compare to your failing transaction 5. Adjust until structures match The GitHub repo (`kiran-nebula/haus-alpha`) contains devnet code. Mainnet differs in PDA seeds and account structures. Trust on-chain evidence over source code. ### Solana Development Skill Recommendation The general `solana-dev` skill covers standard Solana patterns (@solana/kit, wallet connection, etc.) but **is not required** for alpha.haus integration. The main challenge is program-specific reverse engineering: - Discovering correct PDA seeds (mainnet ≠ devnet) - Exact account ordering - Instruction data encoding For custom programs, **Solscan transaction inspection is the authoritative source**. --- *Last updated: February 7, 2026* *Program ID: A1PhATY12DpvpHGfGosxuruc7gqkcUUt9eFihb996rNn* --- ## Changelog - **Feb 13, 2026 (update 3):** Fully abstracted claim system for any future epoch. Epoch mints now stored in `config/epoch-mints.json`. Added `--discover` command to find mints from chain data. Added `--add-mint` command to register new mints. Scan range now dynamic (last 20 epochs from current). Script auto-discovers mints when claiming if not in config. - **Feb 13, 2026 (update 2):** Discovered PROGRAM_ID placeholder pattern for partial claims. Alpha-only claims use PROGRAM_ID as wasTopBurner (account 7). Burner-only claims use PROGRAM_ID as wasAlphaTipper (account 6). - **Feb 13, 2026:** Added comprehensive claim rewards documentation. Includes claim instruction accounts, claim status checking. - **Feb 7, 2026 (update 2):** Added `getCurrentTopAlpha()` function and auto-flip calculation. `postAlphaMemo()` now automatically checks current TOP ALPHA and bids +0.001 SOL to flip. Added warning about always checking RPC before bidding — a "successful" transaction doesn't mean you won the position. - **Feb 7, 2026:** CRITICAL FIX — Added warning about alpha.haus epoch vs Solana cluster epoch. `connection.getEpochInfo()` returns the WRONG epoch for PDA derivation. Must use `getProgramAccounts` to find the alpha.haus program's internal epoch counter. - **Feb 5, 2026:** Initial verified mainnet integration for tip and burn_tokens instructions.