192 lines
8.9 KiB
TypeScript
192 lines
8.9 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
import { Wallet } from 'ethers';
|
|
import { createWalletContext } from '../../setup/wallet-provider';
|
|
import { getStackConfig, validateStackHealthy } from '../../setup/stack';
|
|
import {
|
|
createReport,
|
|
connectWallet,
|
|
mintEth,
|
|
buyKrk,
|
|
takeScreenshot,
|
|
logObservation,
|
|
logCopyFeedback,
|
|
logTokenomicsQuestion,
|
|
recordPageVisit,
|
|
recordAction,
|
|
writeReport,
|
|
attemptStake,
|
|
resetChainState,
|
|
} from './helpers';
|
|
|
|
// Marcus uses Anvil account #1
|
|
const ACCOUNT_PRIVATE_KEY = '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d';
|
|
const ACCOUNT_ADDRESS = new Wallet(ACCOUNT_PRIVATE_KEY).address.toLowerCase();
|
|
|
|
const STACK_CONFIG = getStackConfig();
|
|
const STACK_RPC_URL = STACK_CONFIG.rpcUrl;
|
|
const STACK_WEBAPP_URL = STACK_CONFIG.webAppUrl;
|
|
|
|
test.describe('Marcus "Flash" Chen - Degen/MEV Hunter', () => {
|
|
test.beforeAll(async () => {
|
|
await resetChainState(STACK_RPC_URL);
|
|
await validateStackHealthy(STACK_CONFIG);
|
|
});
|
|
|
|
test('Marcus explores Kraiken with a critical eye', async ({ browser }) => {
|
|
const report = createReport('Marcus Flash Chen');
|
|
const personaName = 'Marcus';
|
|
|
|
console.log(`[${personaName}] Starting test - Degen/MEV hunter looking for edge cases...`);
|
|
|
|
const context = await createWalletContext(browser, {
|
|
privateKey: ACCOUNT_PRIVATE_KEY,
|
|
rpcUrl: STACK_RPC_URL,
|
|
});
|
|
|
|
const page = await context.newPage();
|
|
page.on('console', msg => console.log(`[BROWSER] ${msg.type()}: ${msg.text()}`));
|
|
page.on('pageerror', error => console.log(`[BROWSER ERROR] ${error.message}`));
|
|
|
|
try {
|
|
// --- Landing Page ---
|
|
let pageStart = Date.now();
|
|
await page.goto(`${STACK_WEBAPP_URL}/app/`, { waitUntil: 'domcontentloaded' });
|
|
await page.waitForTimeout(2_000);
|
|
|
|
await takeScreenshot(page, personaName, 'landing-page', report);
|
|
logObservation(personaName, 'Lands on app, immediately skeptical - what\'s the catch?', report);
|
|
logCopyFeedback(personaName, 'Landing page needs "Audited by X" badge prominently displayed', report);
|
|
logTokenomicsQuestion(personaName, 'What prevents someone from flash-loaning to manipulate VWAP?', report);
|
|
|
|
recordPageVisit('Landing', page.url(), pageStart, report);
|
|
|
|
// --- Connect Wallet Immediately ---
|
|
pageStart = Date.now();
|
|
await connectWallet(page);
|
|
await takeScreenshot(page, personaName, 'wallet-connected', report);
|
|
recordAction('Connect wallet', true, undefined, report);
|
|
logObservation(personaName, 'Connected wallet - now looking for contract addresses to verify on Basescan', report);
|
|
|
|
// --- Check for Documentation/Audit Links ---
|
|
pageStart = Date.now();
|
|
logObservation(personaName, 'Scrolling through UI looking for audit report link...', report);
|
|
|
|
// Marcus would look for footer, about page, docs
|
|
const hasAuditLink = await page.getByText(/audit/i).isVisible().catch(() => false);
|
|
const hasDocsLink = await page.getByText(/docs|documentation/i).isVisible().catch(() => false);
|
|
|
|
if (!hasAuditLink) {
|
|
logCopyFeedback(personaName, 'CRITICAL: No visible audit link. Immediate red flag for degens.', report);
|
|
}
|
|
if (!hasDocsLink) {
|
|
logObservation(personaName, 'No docs link visible - would need to find contracts manually', report);
|
|
}
|
|
|
|
await takeScreenshot(page, personaName, 'looking-for-docs', report);
|
|
|
|
// --- Mint ETH (Cheats) ---
|
|
pageStart = Date.now();
|
|
await page.goto(`${STACK_WEBAPP_URL}/app/#/cheats`);
|
|
await page.waitForTimeout(1_000);
|
|
recordPageVisit('Cheats', page.url(), pageStart, report);
|
|
|
|
await takeScreenshot(page, personaName, 'cheats-page', report);
|
|
logObservation(personaName, 'Found cheats page - good for testing edge cases quickly', report);
|
|
|
|
await mintEth(page, STACK_RPC_URL, ACCOUNT_ADDRESS, '50');
|
|
recordAction('Mint 50 ETH', true, undefined, report);
|
|
|
|
// --- Test Small Swap First (Paranoid) ---
|
|
pageStart = Date.now();
|
|
logObservation(personaName, 'Testing small swap first to check slippage behavior', report);
|
|
|
|
await buyKrk(page, '0.01');
|
|
recordAction('Buy KRK with 0.01 ETH (test)', true, undefined, report);
|
|
await takeScreenshot(page, personaName, 'small-swap-complete', report);
|
|
logTokenomicsQuestion(personaName, 'What\'s the slippage on this tiny swap? Is three-position liquidity working?', report);
|
|
|
|
await page.waitForTimeout(2_000);
|
|
|
|
// --- Test Larger Swap ---
|
|
logObservation(personaName, 'Now testing larger swap to probe liquidity depth', report);
|
|
|
|
await buyKrk(page, '5');
|
|
recordAction('Buy KRK with 5 ETH', true, undefined, report);
|
|
await takeScreenshot(page, personaName, 'large-swap-complete', report);
|
|
logTokenomicsQuestion(personaName, 'Did I hit the discovery edge? What\'s the actual buy depth?', report);
|
|
|
|
await page.waitForTimeout(5_000);
|
|
|
|
// Reload page to ensure balance is fresh
|
|
await page.reload();
|
|
await page.waitForTimeout(3_000);
|
|
|
|
// --- Navigate to Stake Page ---
|
|
pageStart = Date.now();
|
|
await page.goto(`${STACK_WEBAPP_URL}/app/#/stake`);
|
|
await page.waitForTimeout(4_000);
|
|
recordPageVisit('Stake', page.url(), pageStart, report);
|
|
|
|
await takeScreenshot(page, personaName, 'stake-page-initial', report);
|
|
logObservation(personaName, 'Examining stake interface - looking for snatching mechanics explanation', report);
|
|
logCopyFeedback(personaName, 'Tax rate selector needs tooltip: "Higher tax = harder to snatch, lower yield"', report);
|
|
logTokenomicsQuestion(personaName, 'What\'s the minimum profitable tax spread for snatching? Need a calculator.', report);
|
|
|
|
// --- Attempt Low Tax Stake (Bait) ---
|
|
logObservation(personaName, 'Staking at 2% tax intentionally - testing if someone can snatch me', report);
|
|
|
|
try {
|
|
await attemptStake(page, '100', '5', personaName, report);
|
|
await takeScreenshot(page, personaName, 'low-tax-stake-success', report);
|
|
logObservation(personaName, 'Stake worked at 2% - now waiting to see if I get snatched...', report);
|
|
} catch (error: any) {
|
|
logObservation(personaName, `Stake failed: ${error.message}. UI needs better error messages.`, report);
|
|
await takeScreenshot(page, personaName, 'low-tax-stake-failed', report);
|
|
}
|
|
|
|
await page.waitForTimeout(3_000);
|
|
|
|
// --- Try to Snatch Another Position (if visible) ---
|
|
logObservation(personaName, 'Scrolling through active positions looking for snatch targets...', report);
|
|
|
|
await page.goto(`${STACK_WEBAPP_URL}/app/#/stake`);
|
|
await page.waitForTimeout(2_000);
|
|
await takeScreenshot(page, personaName, 'looking-for-snatch-targets', report);
|
|
|
|
const activePositions = page.locator('.active-positions-list .collapse-active');
|
|
const positionCount = await activePositions.count();
|
|
|
|
logObservation(personaName, `Found ${positionCount} active positions. Checking tax rates for snatch opportunities.`, report);
|
|
|
|
if (positionCount > 0) {
|
|
logTokenomicsQuestion(personaName, 'What\'s the gas cost vs profit on snatching? Need ROI calculator.', report);
|
|
} else {
|
|
logObservation(personaName, 'No other positions visible yet - can\'t test snatching mechanics', report);
|
|
}
|
|
|
|
// --- Check Statistics ---
|
|
pageStart = Date.now();
|
|
await page.goto(`${STACK_WEBAPP_URL}/app/#/stake`);
|
|
await page.waitForTimeout(1_000);
|
|
|
|
const statsVisible = await page.getByText('Statistics').isVisible().catch(() => false);
|
|
if (statsVisible) {
|
|
await takeScreenshot(page, personaName, 'statistics-section', report);
|
|
logObservation(personaName, 'Checking average tax rate and claimed slots - looking for meta trends', report);
|
|
logTokenomicsQuestion(personaName, 'What\'s the Nash equilibrium tax rate? Is there a dominant strategy?', report);
|
|
}
|
|
|
|
// --- Final Thoughts ---
|
|
await page.waitForTimeout(2_000);
|
|
await takeScreenshot(page, personaName, 'final-dashboard', report);
|
|
|
|
report.overallSentiment = 'Intrigued but cautious. Mechanics are novel and create genuine PvP opportunity. Would need to see audit, verify contracts on Basescan, and test snatching profitability in production. Missing: clear contract addresses, audit badge, slippage calculator, snatching ROI tool. Three-position liquidity is interesting - need to verify it actually works under manipulation attempts. Would allocate small bag ($2-5k) to test in production, but not going all-in until proven safe.';
|
|
|
|
logObservation(personaName, report.overallSentiment, report);
|
|
|
|
} finally {
|
|
writeReport('marcus-flash-chen', report);
|
|
await context.close();
|
|
}
|
|
});
|
|
});
|