72.62.247.119, NS=Hostinger)72.62.247.119 (ssh root@72.62.247.119 / ssh malaysia)/root/polymarket-screener/ (на Malaysia). ⚠️ Локальная копия /home/app/polymarket-screener/ УДАЛЕНА 21 Jun (бэкап: ~/openclaw-backups/polymarket-screener-main-2026-06-21.tar.gz).polymarket-screener (Rust API, порт 3240) + copy-trader-mu (демон копи-трейдинга, LIVE)/root/polymarket-screener/data/screener.db (на Malaysia — авторитетная)⚠️ ПЕРЕЕЗД НА MALAYSIA — ЗАВЕРШЁН 2026-06-21 (читай ПЕРЕД старыми заметками ниже)
Проект целиком переехал с main-сервера (Ванкувер, 1 ядро задыхался) на Malaysia VPS (2 vCPU / 8 ГБ, 100 ГБ NVMe). План:
docs/MIGRATION_TO_MALAYSIA_PLAN.md(в репо/бэкапе).
- Всё работает на Malaysia: API
polymarket-screener(порт 3240, nginxpoly-dev.szhub.space→127.0.0.1:3240, фронт отдаёт сам Rust-бинарь) + демонcopy-trader-mu(LIVE,SUBS_URL=http://127.0.0.1:3240/api/copy/active— localhost co-location, больше НЕ ходит через континент).- SSL: certbot на Malaysia, серт до 19 Sep 2026, авто-продление работает (renewal-конфиг создан, dry-run OK). Basic-auth
/api/analytics/=szhub_adm(htpasswd/etc/nginx/.htpasswd-poly).- БД-авторитет = Malaysia (двойная-БД больше не риск): 145 copy_subscriptions, позиции/киты впереди main.
- 🆕 [29 Jun]
copy_positionsЖИВЁТ В ОТДЕЛЬНОМ ФАЙЛЕdata/money.db(commit2d58e28): money-path изолирован от тяжёлых whale/gamma-txn (раньшеpositions_upsert: database is locked19-41с). Дизайн = ATTACH (не отдельный пул — есть cross-DB JOINcopy_positions cp LEFT JOIN screener_markets m): на КАЖДОМ коннектеATTACH 'data/money.db' AS money+busy_timeout 30s+money.journal_mode=WAL(всё вdb.rs). Все SQL обращаются кmoney.copy_positions(copytrade.rs 7 сайтов, routes.rs 9 сайтов). На первом коннекте бинарь создаёт таблицу (28 кол + 2 idx) + seed-if-empty изmain.copy_positions. ⚠️screener.dbтеперь содержит ТОЛЬКО историческийmain.copy_positions(живой бэкап, заморожен) — реальные данные копий вmoney.db. При работе с позициями через python подключатьdata/money.db, НЕ screener.db. 🔲 NEXT: DROP main.copy_positions позже.- main декомишн: PM2-запись удалена, nginx-сайт poly-dev отключён, renewal
.disabled, папка удалена (+4.1 ГБ, бэкап 766МБ сохранён).- 🔧 ДЕПЛОЙ ТЕПЕРЬ: rsync кода
→ root@72.62.247.119:/root/polymarket-screener/, сборка/рестарт PM2 на Malaysia (НЕ на main). 8 ГБ RAM + swap →cargo build --release -j 2без OOM (старое правило «-j 1 foreground из-за 1.5Gi RAM» относилось к main — на Malaysia не нужно).- Демон рестарт по ПРАВИЛУ: direct inline-env
pm2 start copy-loop-mu.mjs --name copy-trader-mu+ полный env +pm2 save(НЕ ecosystem .cjs, НЕ--update-envесли меняешь env — теряет SERVICE_TOKEN).- 🪤 ENOENT
.env.privy= ТОРГОВЛЯ ВСТАЁТ (фикс 1 Jul ч.4): после «Варианта В» демон крутит repo-код, аcopy-exec-mu.mjs:29резолвит.env.privyотносительно import.meta.url (скрипта), НЕ cwd → ищетnode-executor/.env.privy. Держится СИМЛИНКОМ:/root/polymarket-screener/node-executor/.env.privy → /root/poly-exec/.env.privy(в .gitignore). После любого рестарта/переустановки node-executor проверятьls -la node-executor/.env.privy— пропал →[exec] FAIL ENOENTу всех юзеров, exec не собирается. Пересоздать симлинк +pm2 restart copy-trader-mu. Признак жизни в логе:[exec] ready … exec=ok. Долгий фикс (при Rick):copy-loop-mu.mjs:199'./.env.privy'→'../.env.privy'.- ⚠️ Юзер cmqe (
0x8b95fb94, неактивен с 15 Jun) ВЫКЛЮЧЕН намеренно:[exec] FAIL policy violation(нет кэша creds→ClobAuth DENY). Включить =update-policy.mjs APPLY=1(POLICY_IDxq797h4uovkjp5k3tybo3sjb), но он re-open withdraw vector для этого DW (пометка Rick 16 Jun). НЕ включать без явной команды «включай cmqe». Изолирован — на активных юзеров/демон не влияет. ✅ Спам заглушён (1 Jul ч.73fb4e63):ensureExecutorтеперь на "policy violation" ставитu.execDisabled, логирует[exec] DISABLEDОДИН раз и молча скипает (было — FAIL каждый трейд китов, заливал лог). Сброс = рестарт демона. Обобщается на любого юзера без политики.- ℹ️ «order manager not ready» на manual-close = ответ Polymarket CLOB для рынка в резолюции (UMA
proposed, токен не делистнут → SELL нельзя). НЕ баг. Позиция сама закроется+заредимится черезredeem-all.sh(крон */30) когда UMA финализирует. Форсить не надо.- ✅ Как ЗАКРЫВАЮТСЯ позиции (штатно, 1 Jul ч.6): главный путь — LEADER-EXIT (кит вышел/рынок резолвнулся у кита →
sellAll→ резолв отдаёт «invalid token id» →leader-exit-resolved, realized=−cost). История: leader-exit=375, onchain-absent=276, worthless-aged(24ч предохранитель)=1. Плюсы И минусы закрываются сами. ⚠️ Phase B требует живого executor'а (if(ex)for...) — при ENOENT-простое закрытия ВСТАЮТ (детектятся, но не исполняются); чинится восстановлением executor'а (симлинк).- 🪤 «Висящие с резолвом $0» = НЕ баг, это ON-CHAIN пыль (1 Jul ч.6): проигранный токен PM остаётся в кошельке НАВСЕГДА $0 (сам PM прячет; redeem-крон скипает worthless — сжигать $0=газ впустую). data-api его отдаёт → UI-таб
openпоказывал (запросsizeThreshold=0.1=по ШЕЙРАМ, не value). Пыль слоты НЕ занимает (слоты=ЛЕДЖЕР copy_positions open/pending; on-chain холдинги ≠ слоты). Фикс = UI-фильтр74cae0a(TradePage.tsx: табopen→visible=positions.filter(currentValue>=0.01), список/summary/счётчик из visible). ⚠️ Коммит757701a(демон закрывал лузеров на redeemable) был ОТКАЧЕН (28f0d31) — мисдиагноз, штатный leader-exit справляется. Ручная чистка старой пыли если надо: флипstatus='closing'прямым UPDATE money.db (демонpullCloseIntents→manual-resolved); прямой UPDATE→'closed' НЕ работает (демон владеет in-memory, перезатирает).- 🗑️
screener.db.copy_positionsдропнута (1 Jul ч.5): стейл-снапшот с миграции 29 Jun (6943 строки), весь код читаетmoney.copy_positions. Бэкап/root/screener-copy_positions-stale-20260701.sql. При рестарте API вернётся ПУСТОЙ (db.rsschema-init её пересоздаёт) — безвредно, не читается.- 🩹 REDEEM neg-risk фикс длины amounts (1 Jul ч.7
3daf41f):NegRiskAdapter.redeemPositions(cid, amounts[])реверит (batch would revert) еслиamounts.length < outcomeSlotCount.redeem-dw.mjsстроил длинуmaxIdx+1= 1 когда выигрышный исход = index 0 → выигрыши НЕ выводились (напр. $4 «Mexico win»). Фикс: длинаamountsпо on-chaingetOutcomeSlotCount(cid)(SLOT_ABI), паддинг 0. Проверено: live redeem+wrap CONFIRMED. Глобально по всем DW (redeem-all.sh) → тестеры-победители neg-risk выводят авто. Бэкапpoly-exec/redeem-dw.mjs.bak-negrisk-slotcount-20260701.- 🚀 Подготовка к тестерам (1 Jul ч.7): роадмап
docs/ROADMAP_EXECUTION.md(23 фичи, ✅4/🟡7/❌12, ядро дифференциации закрыто). Этап0 аудит + Этап1 money-грабли = ✅ (2 фикса выше). NEXT = Этап2: онбординг с чистого кошелька (активация делегата→депозит USDC→авто-конверт pUSD→подписка→первая копия), отловить где спотыкается новый юзер. Мин.деп ~$2 (ниже → «not enough balance», slot released — graceful). #2 TG-алерты здоровья (health*/15сейчасconsole only) ждут ОТДЕЛЬНЫЙ токен от Rick (НЕ Бендера).- ✅ Этап2 онбординг ЗАКРЫТ (1 Jul ч.8, фронт-only): воронка
1e94600—client/src/components/OnboardingChecklist.tsxв PrivyAccount приwalletReady: 3 шага ✅активирован→⬜пополни$20+→⬜выбери китов[navigate('/')]→«всё готово». Подписка на LeadersPage/(/whalesвыпилен, хукuseCopySubscriptions). стабильностьc23de29—registerWalletтеперьres.ok+ретрай3×+варнингacct.regWarn(был fire-and-forget→демон не знал DW→копий нет);cooldown25с +acct.errRateLimit(защита от Privy rate-limit при повторномaddSigners). чистка6f9af63— удалён мёртвыйportfolio/CustodyCard.tsx. Билд node20 nvm, Rust отдаёт бандл без рестарта, тестеру обновить PWA. ГОТОВНОСТЬ: можно звать пару тестеров. Осталось TG-алерты (ждут отдельный токен).- ❗Ниже по файлу «главный сервер»/«на main»/«Ванкувер»/
/home/app/polymarket-screener— это ИСТОРИЯ до переезда. Текущая прод-точка = Malaysia. Откат: развернуть бэкап + A-запись назад на76.13.138.220(но main-БД устарела → откат неполноценный).- TODO перед глобал-продом: переименовать домен с
poly-dev(решение Rick: имя оставить до прода); ротировать.env.privy; рассмотреть Cloudflare CDN (159мс Ванкувер↔Малайзия).
🌊 ПОТОК (Flow) + РУЧНЫЕ СДЕЛКИ С ДЕПОЗИТА — 30 Jun 2026 (commits на redesign-nav: 55183bc/e4ec2a7/fe771ed + демон)
- Редизайн «Сигналы»→«Поток» (Rick: «сигналы» = скам-оттенок). 5-й пункт меню «Поток» (2-й, после Лидеров), роут
/flow,FlowPageс саб-табами 🤝 Консенсус · 🆕 Инсайдер · 🔥 Жар (последние две — заглушки «скоро»). Карточка консенсуса: дельта «вход {a}¢ → сейчас {c}¢» + тег дороже/дешевле/≈, бейдж свежести 🆕, сортировка по силе (киты×согласие), фильтры (период 1/7/30д, 🐋≥2/3/5, согласие). Consensus API (/api/signals/whales/consensus) теперь отдаётcurrentPrice(LEFT JOIN screener_markets, доминантная сторона) +lastTradeAt.- 🔑 РУЧНЫЕ СДЕЛКИ ИДУТ С ТОГО ЖЕ ДЕПОЗИТА, ЧТО И КОПИ (подход A = «копия без лидера»): кнопка «Войти→» на карточке → модалка
EnterTradeModal(сумма+пресеты, БЕЗ wallet-подписи) →POST /api/copy/manual-orderпод Privy-сессией {market_id, token_id, outcome, amount_usd, limit_price} → таблицаmoney.manual_orders(status='requested') → демонexecuteManualOrders()(interval 8с, guard manualBusy) забирает:GET /api/copy/manual-orders(service-token, JOIN user_wallets→dw/eoa) → claim 'executing' (POST /api/copy/manual-order/{id}/status) →ensureExecutor→executor.buy({tokenID, price=limit_price, size=amount/effPrice})(deposit-wallet/pUSD, sigType3, тот же путь что копи) → reserve+saveLedger вmoney.copy_positions(direction='manual', leader='manual', обычный lifecycle reconcile/redeem; mirror-exit не триггерит — лидера нет) → 'done'/'failed'. Bypass copy-budget/maxOpen (явное действие юзера). 1 in-flight на (user,token) против дабл-тапа. Закрытие ручной позиции — существующий manual-close (POST /api/copy/positions/{token_id}/close).- 🪤 СБОРКА КЛИЕНТА СЛОМАНА на Node 18 (с 24 Jun): vite 8.0.14 (rolldown) требует Node 20+ (
CustomEvent, статич. импортstyleTextиз node:util — НЕ полифиллится). На Malaysia системный node=18 (на нём живой money-демон — НЕ апгрейдить системно!). РЕШЕНИЕ: изолированный Node 20 через nvm:export NVM_DIR=$HOME/.nvm; . $NVM_DIR/nvm.sh; nvm use 20; cd client && npm run build. Если падаетrolldown-binding.linux-x64-gnu.node not found→npm install @rolldown/binding-linux-x64-gnu@1.0.2 --no-save --legacy-peer-deps. Деплой клиента: scp файлов →nvm use 20 && npm run build→pm2 restart polymarket-screener. App.css на Malaysia имеет pending-редизайн Rick → НЕ scpّить целиком, CSS дописывать append-only прямо на сервере.- ✅ ВЕРИФ LIVE (30 Jun): живой тест прошёл —
✅ MANUAL BUY 4sh, позиция open, 4 шара on-chain в dw. Фича рабочая.- 🪤 neg-risk allowance (ОБЯЗАТЕЛЬНО для турниров/спорта): neg-risk рынки settle через
0xe2222…(neg-risk биржа V2) +0xd91E…(NegRiskAdapter) +0xC5d5…(neg-risk CTF). pUSD должен быть approved для ВСЕХ + CTF setApprovalForAll.dwApprovals.ts/approve-dw.mjsраньше одобряли только EXCHANGE_V20xE111→ neg-risk манул И КОПИИ молча падали «not enough allowance». Фикс для dw Rick прогнан (approve-dw.mjslist-driven, gasless relayer). 🔲 FOLLOW-UP: добавить эти спендеры в онбординг-аппрувы ВСЕХ юзеров (clientdwApprovals.ts+ per-user approve) — иначе neg-risk у других падает.- 🏷️ direction='manual' (fix
1ac0ac2):positions_upsertпринимал только 'fade' (manual→'copy'). Теперь.filter(|d| *d=="fade"||*d=="manual"). UITradePageприentry==='manual'рисует чип «✋ Ручная сделка» (i18npf.manualTrade), не фейкового кита. Маркер ручной позиции =leader_address='manual'+direction='manual'.
🌐 i18n (en/ru/es) — 2 Jul 2026 (испанский добавлен, коммиты
280e63e/a4ae8e5/18f16fc)
- Словарь:
client/src/i18n/translations.ts—Lang='en'|'ru'|'es', объектtranslationsс блоками en/ru/es (по 397 ключей, ПАРИТЕТ обязателен),LANGUAGES[]= селектор в Settings.t()фолбэчит на en при отсутствии ключа, потом на сам ключ. Плейсхолдеры{name}через 2-й аргt('key',{name:val}).- Селектор/детект:
LanguageContext.tsxdetectLang()— localStoragesz_lang(en/ru/es) →navigator.language(ru/es/en) → en.- Добавить язык = добавить код в
Lang, entry вLANGUAGES, блок-близнец вtranslations(все ключи!). Проверка паритета — парсер блоков по\n <lang>: {с балансом скобок; сверятьset(en)^set(xx)==[]и 0 дублей.- 🪤 Клички китов только ru/en:
whaleCodename/whaleCodenamesUnique(utils/whales.ts) принимаютlangIn:'ru'|'en'|'es', внутри нормализуютes→en. Не забыть при новом языке.- 🪤 Хардкод-протечки: тернарники
lang==='ru'?ru:enв JSX = НЕ переводятся на новые языки (падают в en). При добавлении языка грепниgrep -rn "lang === 'ru'" srcи вынеси в словарь. На 2 Jul вычищены (были в TradePage/CopyTradingPage/LeadersPage/TradeHistory/CopyConfigModal/WhaleProfile). Если после выносаlangбольше не используется в файле — убрать изuseT()destructure (иначе noUnusedLocals роняет build).- Сборка/деплой i18n:
npm run build(node20 nvm; =tsc -b && vite build, ловит TS) →pm2 restart polymarket-screener(Rust вмораживает index.html при старте). 🪤client/distв .gitignore → коммитить ТОЛЬКОclient/src(Rust на Malaysia сам пересобирает/отдаёт из dist после build).
🎚️ ЛИМИТЫ КОПИЙ — переработаны 2 Jul 2026 (
8d455cc+005df49)
- Схема теперь per-whale, БЕЗ глобальных счётчиков-капов. Реальная защита = баланс депозита (executor физически не купит больше pUSD, чем есть на DW).
maxOpenPerLeader(позиций на кита) — задаётся в модалке «Копировать», editable, дефолт 2. Единственный count-кап.maxPerEvent(позиций на событие/игру) — per-whale, editable, дефолт 3. Жадному киту (берёт по 10 на событие) ставить 1 в его модалке.- УБРАНО: глобальный
maxOpen(былmin(maxExposure/maxPerTrade, MAX_OPEN)=8 — резал ВЕСЬ аккаунт), поле «Max exposure $40» из модалки, env-clamp у maxPerEvent. envMAX_OPEN/MAX_PER_EVENTбольше не рулят (код игнорит).- deriveCfg (
copy-loop-mu.mjs):maxOpenPerLeader = Math.max(1, Number(c.maxOpenPerLeader)||2),maxPerEvent = Math.max(1, Number(c.maxPerEvent)||3). Total-open чек удалён.- Рестарт демона после правки кода:
pm2 restart copy-trader-muPLAIN (НЕ--update-env→ сохраняет SERVICE_TOKEN + инлайн-env; env не меняли, нужен лишь перечит кода) +pm2 save.- 🗺️ Юзеры → email (Rick НЕ читает did:privy):
users(address,email,country) +user_wallets(deposit_wallet) вscreener.db. Известные: serg5585@gmail.com=cmq45iuz(dw 0x50a8061e, основной, торгует), sergiizapolskyi@gmail.com=cmqel7uq(dw 0x8b95fb94=«cmqe», executor OFF by design=withdraw-vector), borodichs111@gmail.com=cmqgoxfo(GE).- Остаточные тормоза копий (НЕ капы): priceMax-банд 0.60 (режет дорогих фаворитов, by design эдж) + тонкие филлы лайв-рынков (лимит-ордер по цене лидера, лаг ~12с → цена уехала → висит → reconcile отменяет). Ошибка
reading 'price'= SDK на неликвидных микро-рынках (не баг кода).- ⚠️ ПОПАПЫ AskUserQuestion НЕ видны в Telegram — Rick'у вопросы задавать ТОЛЬКО текстом в ответе.
docs/COPYTRADING_PLAN.mdПродукт развернулся в копитрейдинг. Навигация V3 ниже = АРХИВ (будет переделана).
getSafeAddress(signer)==safe, один EOA) делегацию НЕ
дают — только тип 3 (EIP-1271, Safe решает). ⚠️ Issue #70 SDK ставит POLY_ADDRESS=EOA
→ обходим своим кодом подписи (POLY_ADDRESS=funder). Модель A (кастодия) отвергнута (FINTRAC/уголовка).Polymarket/privy-safe-builder-example
(+turnkey/magic). Выбран Privy (Turnkey-дашборд не открывался у Rick).
M0✅ Privy app SZHub: App ID cmq444h0i00580cih1ur8q1pr (публичный), Secret в .env.privy (gitignored, ⚠️ротировать),
email-метод вкл, allowed origin poly-dev.szhub.space. ⚠️дубль-app удалить. M1a✅ (e06f613) @privy-io/react-auth@3.29.2
(install --legacy-peer-deps; доставить @solana-program/system иначе rolldown падает), PrivyProvider в App.tsx.
M1b-1✅ LIVE (2401b46) components/PrivyAccount.tsx email-вход, заменил SIWE в MorePage — Rick вошёл по email.
M1b-2 DONE — онбординг Safe (2a-2d, live): Builder-креды получены (019ea3a9-…+secret+passphrase→.env.privy),
taker fee 0.5% pending 10 Jun. 2a (cc34cc8) POST /api/polymarket/sign builder-HMAC (build_l2_hmac) + main.rs
load_dotenv('.env.privy'). 2b (1aa5a5b) usePrivySigner (Privy→ethers v5) + SDK (relayer/signing/clob/ethers@5);
build-фиксы: Privy v3 embeddedWallets.ethereum.createOnLogin, PWA лимит 5MiB, externalize @solana/kit в vite.
2c (67ca28b) useRelayClient+useSafeDeployment (deriveSafe+газлесс deploy)+кнопка «Настроить торговый кошелёк».
2d (3391cbc) useTokenApprovals+utils/approvals (USDC.e+outcome→CTF/Exchange/NegRisk). Флоу email→EOA→Safe→approvals в проде.
⚠️ CLOB V2 коллатерал=pUSD (пример апрувит USDC.e)→wrap в M3.
🏆 M2 DONE — ДЕЛЕГИРОВАНИЕ+СЕРВЕРНАЯ ПОДПИСЬ РАБОТАЮТ LIVE (8 Jun). 🔑 РАЗГАДКА: delegateWallet=ЛЕГАСИ (виснет),
актуальный = useSigners().addSigners({address, signers:[{signerId, policyIds?}]}). signerId=authorization-ключ
(дашборд Wallet infra→Authorization→New key; тумблера delegated-actions НЕТ, TEE=доступно). Authorization key: Signer ID
kc34moyfw73mo5whwfjqpewy→VITE_PRIVY_SIGNER_ID(.env.production); private key wallet-auth:MIGH…→.env.privy PRIVY_AUTHORIZATION_KEY.
M2b-2 (3970be0): сервер подписал за кошелёк Rick 0xE2D892…633d3F(delegated:true) БЕЗ юзера —
new PrivyClient(appId,secret,{walletApi:{authorizationPrivateKey}}).walletApi.ethereum.signMessage({address|walletId,chainType,message}).
⚠️ addressдепрекейт→walletId в проде.
M3 прогресс (8 Jun): сервер делает за юзера ВСЁ (auth+подпись+сабмит), упёрлись в 1 CLOB-гейт. Node-executor (server-auth за Rick EOA 0xE2D892…): M3a order-sign.mjs (signTypedData order→recover EOA; депы viem+builder-relayer-client@0.0.10); M3c-auth executor-auth.mjs (ClobAuth→create-key=CLOB-креды). Privy-Safe Rick=0x7570210eACa5F3783577997fF74e8FEbe9B9E1f4 (deriveSafe+factory 0xaacFeEa…3541b, задеплоен, +2 pUSD). M3c-order order-submit.mjs: CLOB «maker address not allowed, deposit wallet flow». 🔑 ДИАГНОЗ: V1-биржа 0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E getSafeAddress(EOA)=ТОЧНО наш Safe; V2 0xE111…996B (домен ордера) НЕ имеет getSafeAddress. ГИПОТЕЗА ФИКСА: подписывать sigType2-ордер против домена V1 0x4bFb. Альт: CLOB-регистрация Safe / pUSD-approval. 🎯 РАЗГАДКА «maker not allowed» (8 Jun, ресёрч issues#51-74+docs): «Deposit wallet» ≠ Gnosis Safe! Мы задеплоили SAFE, а CLOB хочет deposit-wallet. Deposit wallet = per-user ERC-1967 proxy от фабрики 0x00000000000Fb5C9ADea0298D729A0CB3823Cc07 (Beacon новые/UUPS легаси); создание relayer /submit {type:"WALLET-CREATE",from:EOA,to:factory}→ждать STATE_CONFIRMED (реестр); адрес deriveDepositWalletAddress(); ордера sigType3 POLY_1271, maker==signer==deposit-wallet, ERC-7739-обёртка (clob-client SDK сам). Ирония: наш vault-POLY_1271 был правильным паттерном, нужен их РАСПОЗНАННЫЙ deposit-wallet (#3 уперся в нерегистрацию). SDK builder-relayer-client@0.0.10 имеет deriveDepositWallet/deriveBeaconDepositWallet(owner,factory,beacon); адреса в dist/config/index.js getContractConfig(137). NEXT=РАЗВОРОТ на deposit-wallets (большой чанк): деривить deposit-wallet→деплой relayer(STATE_CONFIRMED)→фунд pUSD→ордер sigType3 через clob-client SDK+server-auth signer. Меняет M1b(deposit-wallet вместо Safe useSafeDeployment)+M3c; M2 делегация wallet-agnostic остаётся.**
M3-pivot ПРОГРЕСС (8 Jun): deposit wallet ЗАДЕПЛОЕН+ПРОФИНАНСИРОВАН СЕРВЕРОМ. DepositWalletContracts(getContractConfig(137)): Factory 0x0000…Cc07, Impl 0x58CA52eb… (UUPS). Rick dw=0x50A8061e9448EB1e5d5e7aF07BE4E4F63C6F24Ff (deriveDepositWallet(EOA,factory,impl)). 🔑 АДАПТЕР server-auth→Polymarket SDK (node-executor/deploy-dw.mjs): RelayClient ждёт viem WalletClient → toAccount({address:EOA,signMessage,signTypedData}) зовущий privy.walletApi.ethereum.sign* → createWalletClient({account,chain:polygon,transport:http(drpc)}). 🎯 dw задеплоен (1ace4f3): relay.deployDepositWallet()→STATE_CONFIRMED, server-signed БЕЗ юзера; релеер relayer-v2.polymarket.com НЕ геоблокнут. Профинансирован 2 pUSD (tx e8dfd00…; ⚠️ещё 2 pUSD в Safe 0x7570… вернуть). NEXT финал: pUSD-approval dw→exchange (relay.executeDepositWalletBatch) + ордер sigType3 POLY_1271 maker==signer==dw ERC-7739 через clob-client SDK+server-auth→CLOB.
ГРАБЛИ: чёрный экран#1=externalize @solana/kit→стаб alias @solana-program/system (375d069); чёрный#2=стейл-бандл→Cache-Control:no-cache на index.html в Rust SPA-middleware (8a8be89). Headless /snap/bin/chromium+puppeteer-core(node-executor)=диагностика консоли. ⚠️SiweAuthProvider ещё в App (убрать). Ниже — старый vault-контекст (research/dead-end):Vault.sol (~80 строк, research/spike-b/) разделяет:
isValidSignature пускает делегата, withdraw/approveExchange=onlyOwner. Инвариант
1271: signer==maker==vault. ⚠️ аудит контракта перед mainnet.spike.mjs, 8/8 checks, #1/#2 закрыты:
delegate-подпись→MAGIC, проходит exchange-гейт). Chunk 2 ⬜ Amoy on-chain (нужен faucet)POLY_ADDRESS=funder). #3 открыт.91d8418):
🐋Лидеры(/)·📋Мои копии(/copy)·💼Портфель(/portfolio,+/trade)·🔍Рынки(/screener)·⚙️Ещё.
Старая лента Сигналов→/feed. Ш2 LeadersPage(/) лидерборд поверх /api/signals/top-traders
(БЕЗ бэка), карточки+сорт+CTA→профиль (9be291e). Ш3 профиль: equity-спарклайн (resolved
trades, inline-SVG, честно) + CTA [Копировать]→/copy + viewMode public/owner extension (c06a626).
Ш5 «Мои копии» demo (a2e4878): хук useCopySubscriptions(localStorage,window-sync)+CopyConfigModal
(аллокация/лимиты/категории/цена/зеркал/просадка)+CopyTradingPage→карточки подписок; профиль CTA→модалка→/copy.
Ш6 💼 CustodyCard на TradePage (67088c3): баланс-плейсхолдер+статус делегирования+disabled депозит/вывод,
рабочий портфель НЕ тронут. Весь фолловер-UI собран в demo. ⚠️ Rust серверит client/dist; деплой=npm run build+pm2 restart.users(PK=address)+
nonce/подпись/сессия+FK в vaults/copy_subscriptions+middleware. NEXT=Ш4 Custody (SIWE→vault→enroll делегата→движок), нужен Rick (faucet/деньги).fa4efe7: users/vaults/copy_subscriptions/copy_positions).
4b-1 SIWE-бэкенд (4189f77: src/auth.rs nonce→ecrecover k256+tiny-keccak→JWT httpOnly cookie→upsert users;
депы k256/tiny-keccak/jsonwebtoken/rand; AppState auth_nonces+jwt_secret; ⚠️JWT_SECRET random без env→сессии reset).
4b-2 SIWE-клиент (7de96cc: SiweAuthProvider wagmi signMessage, MorePage «Аккаунт»). copy-CRUD (15ffe67+0bab796:
src/copytrade.rs /api/copy/subscriptions session-gated; useCopySubscriptions backend когда залогинен, иначе localStorage).
🎯 4c ON-CHAIN MAINNET (52ce539): Vault 0xecf49480f7b93a8e788cd0b671ffff7a263ef3e4 задеплоен на Polygon
(deployer=weather-bot EOA 0x1aec730E…, Rick разрешил, бот retired; delegate 0x830BcD…). PASS: isValidSignature(delegate)→MAGIC,
withdraw делегатом→revert. #1/#2 ОНЧЕЙН. Харнесс research/spike-b/deploy-mainnet.mjs, RPC polygon.drpc.org, solc 0.8.26.
4d delegate-подпись (232cf41): src/ordersign.rs EIP-712 order digest+k256 подпись, 2 теста PASS (digest=viem golden 0x99b047dd…, recover→delegate), депа primitive-types, dead_code до 4e.
#3 прогресс (7 Jun): vault профинансирован + auth разгадан. КОЛЛАТЕРАЛ=pUSD 0xC011a7E…E82DFB (6dec, CLOB V2; из USDC.e через Onramp 0x93070a8…, wrap_usdc_to_pusd в weather executor.py). Биржа V2=0xE111180000d2663C0091e4f400237545B87B996B (getCollateral подтвердил). Деньги были в weather Safe 0x3643… (21.58 pUSD). Перевёл 3 pUSD Safe→vault 0xecf49480…ef3e4 (Safe execTransaction, tx 0xdd0da48…) + approveExchange MAX (tx 0xc522dcf…). ⚠️drpc round-robin→balanceOf врёт 0, чек на 2 RPC. #3 AUTH: L1 идёт на ДЕЛЕГАТА (EOA), нужен /auth/create-api-key (не derive); vault/1271 только в ОРДЕРЕ (maker=signer=vault,sigType=3,подпись делегата→isValidSignature). ClobAuth: domain{name:'ClobAuthDomain',version:'1',chainId:137}, msg='This message attests that I control the given wallet'. NEXT#3 (итеративно): добавить create-api-key прокси в скринер + обойти submit_order signer==owner для 1271 + выставить ордер (owner=delegate,order.signer=vault). Возврат: vault.withdraw(pUSD,owner). ⚠️Vault НЕАУДИРОВАН. Сборка ~/.cargo/bin/cargo.clob.polymarket.com=live mainnet; clob-staging=503(лежит); Amoy-CLOB нет→#3 на mainnet $1.5c856de pushed)Баг: разные адреса хешатся в одно «Прилагательное+Зверь» комбо → дубли в списках («Чёткий Дракон» ×2 в «Мои копии»). Генератор ~2500 комбо vs 4000+ китов → коллизии неизбежны (birthday paradox). Деплоенная версия дедуп НЕ делала; начатый в раб.дереве фикс лепил hex-хвост адреса («…A3F») — уродливо + лик адреса (файл сам это запрещает). Решение: whaleCodenamesUnique() (client/src/utils/whales.ts) при коллизии перебирает свободные комбо детерминированно от адреса (beast inner / adj outer, старт от натурального имени) → чистые разные имена («Чёткий Дракон»/«Чёткий Грифон»), без хвоста/лика. Стабильно: адреса сортируются, lexи-меньший держит базовое имя. Hand-picked WHALE_NICKNAMES не перекатываются (фикс. идентичности), только резервируются. Применено во ВСЕХ списках: CopyTradingPage, LeadersPage, UserAnalyticsPage. Одиночные карточки (WhaleProfile/PriceChart) не трогал (1 кит = нет коллизий). Деплой клиента = npm run build (tsc+vite) + pm2 restart polymarket-screener (index.html вмораживается через Vec::leak).
Прошёл copy-loop.mjs+copy-exec.mjs на баги (money-path). 5 найдено+починено, задеплоено. Коммиты 4f06964 (Chunk1) + 862ff77 (Chunk2), запушены.
reconcileOpenPositions грузил pos, держал его через МЕДЛЕННЫЕ on-chain await readContract по каждой absent-позиции, потом savePos(pos). На каждом await event loop пускал WS-трейд → handleTrade BUY делал свой loadPos/savePos с новым токеном → reconcile дописывал устаревший pos поверх → запись позиции исчезала (ордер on-chain есть, в ledger нет → SELL-ветка pos[t.asset] не находит → закрыть нельзя; слоты врут). Фикс: reconcile разбит на phase1 (snapshot + on-chain чтения, БЕЗ лока) → phase2 (lock + reload свежего ledger + применить патчи только к still-open). Патч = {markPnl} или {close:reason}, realizedPnl считается на свежем p.markPnl под локом.ws.on('message', async…) НЕ ожидается библиотекой → два трейда в разных фреймах идут параллельно, оба проходят MAX_OPEN/maxOpenPerLeader/maxPerEvent/«already holding» ДО того как любой сохранил → превышение капов / двойная покупка. Фикс: withPos() — промис-цепочка-мьютекс вокруг всех мутаций ledger (BUY проверки+покупка+save, SELL close, reconcile apply). let posLock=Promise.resolve(); const withPos=(fn)=>{const run=posLock.then(fn,fn);posLock=run.catch(()=>{});return run;};. Только in-process — ОК, т.к. единственный живой писатель positions.json = сам демон (нет redeem-крона/2го процесса; resync-ledger.mjs ручной). Smoke-тест: сериализует + переживает throw.savePos был голый writeFileSync → краш в момент записи = битый JSON → loadPos catch→{} → ВСЕ записи «забыты» → re-buy storm + сироты-токены (SELL их не видит). Фикс: запись в .tmp + renameSync (атомарно на одной ФС). Заодно защищает ручной resync-ledger от torn-read.AbortSignal.timeout(15000) (как в pollSubs/tick) — иначе зависший Data API стекает interval-колбэки.openedAt=t.timestamp предполагал секунды (на живом ledger подтвердил — секунды ✓), но если RTDS отдаст ms — age-логика reconcile (GRACE/MAX_OPEN_AGE) молча сломается. toSec=(ts)=>{const n=Number(ts)||0;return n>2e10?Math.floor(n/1000):n;} на openedAt/closedAt.copy-exec.mjs (исполнение корректно); copytrade.rs (IDOR закрыт authed_user крипто-проверкой, запросы параметризованы, конфиг клампится в deriveCfg против env-потолков — UI может только ниже); интеграция (FOLLOWER allow-list задан=DID Rick'а → активен; service-token совпадает, логи 14 from DB без 401).copy-loop.mjs (copy-exec не менял) → node --check на Малайзии → pm2 restart copy-trader --update-env. Бэкап copy-loop.mjs.bak-<ts> перед заменой. Демон поднялся: detection LIVE | mode=WS | leaders=14 | maxNotional=$3, ledger цел (o2 open сохранились).Триггер: Rick спросил «сколько открыто позиций?» — я сказал 4 (демон-ledger), а Портфель показал 8 (Data API). Разные источники правды: моя цифра = positions.json status=open (что демон активно ведёт); Портфель = Data API /positions (реальные on-chain холды кошелька, включая дуст-лузеров и сирот). Копнув разницу — нашли реальные баги.
pub.readContract balanceOf=0. Но drpc round-robin'ит по нодам, отстающая отдаёт ложный 0 (известная грабля — в памяти «чек на 2 RPC»). Иронично: мы только что закрыли reconcile от флака Data API проверкой on-chain, а сам on-chain чек оказался уязвим к флаку RPC. Симптом: Spread: Argentina ($1.98) закрыт resolved-onchain-absent, хотя 9.00 шейров на кошельке. Фикс: второй независимый клиент pub2 (RPC2 env, дефолт polygon-bor-rpc.publicnode.com); при onchain===0n подтвердить нулём на pub2 ПЕРЕД close; ненулевой/ошибка → keep open + retry + лог false-zero guarded. Проверка: оба RPC прочитали 9.00sh на токене Argentina → guard сработал бы. Коммит 0904f02, деплой rsync+restart.Argentina($1.98)+Haiti($0.97) флипнул status:closed→open в positions.json (атомарной записью, delete closedReason/closedAt/realizedPnl). Лидер обоих = 0x204f72f3 (лудик, на паузе) → демон SELL не ловит, доедут до резолва, закроются через reconcile (теперь с guard). open 4→6. Урок: сегодняшний guard предотвращает НОВЫЕ false-close, старых сирот лечить вручную (флип в ledger). Haiti был закрыт ещё и ручным cleanup-скриптом manual-dust-paused-leader (слишком грубо зацепил позу с ценностью) — операционная, не баг демона./activity = микс TRADE(BUY) + REDEEM. У REDEEM нет side/price → рисовалась пустым бейджем + 0.00 шейров @ 0¢ = выглядело мусором. Юзер: «ничего не понятно». Расшифровка: REDEEM = погашение резолвнутого рынка, правая $ = выплата ($5=выигрыш, $0=проигрыш). Фикс (TradeHistory.tsx): для type==='REDEEM' бейдж Погашение·выигрыш(зелёный, amount>0)/Погашение·проигрыш(серый, $0) вместо side/outcome/size/price. i18n-ключи th.redeem/win/loss (EN+RU), CSS .th-redeem-win/loss. Дубли в ленте (3× одинаковых «Hurricanes BUY @48¢» в одну секунду) = НЕ тройная покупка, а 1 ордер исполнился об 3 мейкера → Data API даёт строку на набивку (опц. схлопывать, пока оставил). Коммит fd04b37.b417558, нашёл при сборке): npm run build (tsc -b) был КРАСНЫЙ ещё до меня — git-история показала, файлы не мои. (1) WhalesPage.tsx Leaderboard юзал tr() без useT() (t затенён traders.map(t=>…)) → ReferenceError, страница Киты падала. Фикс: const {t:tr}=useT(). (2) WalletBrowserBanner.tsx:2 импорт ../../i18n на уровень выше (соседние импорты ../) → module-not-found. Фикс: ../i18n. Коммит fff3cb5. ⚠️ ГЛАВНЫЙ УРОК (повтор сессии 3): vite build/rolldown НЕ делает type-check → битьё уходит в коммит зелёным. ВСЕГДА npm run build (=tsc -b && vite build) перед деплоем клиента, не голый vite build. Деплой клиента: пересборка → pm2 restart polymarket-screener ОБЯЗАТЕЛЕН (index.html вмораживается в 'static память при старте, Vec::leak — старые хеши ассетов иначе). Юзеру жать «Обновить приложение» (PWA SW кеш).reconcileOpenPositions видит balanceOf>0 → не закрывает. Симптом: «сделок давно не было» при живом демоне (слоты забиты мёртвым дустом).MAX_OPEN_AGE_S (env, дефолт 24ч) в copy-loop. В ветке absent-из-Data-API+past-grace: onchain===0→close (старое); иначе now-openedAt>MAX_OPEN_AGE_S→close resolved-worthless-aged. Безопасно для долгих холдов — живые позы держат value → остаются в Data API /positions → первая ветка (refresh markPnl), до max-age не доходят. Триггерится только absent+aged+onchain>0.status:'open'→'closed' в positions.json по фильтру (напр. leader+open). Безопасно если лидер на паузе. Гонка с демоном (пишет каждые 2мин) пренебрежимо мала за 1 атомарный write.pm2 list demon online? (2) tail логов — какие скипы: leader already N open(per-leader cap, ок), incomplete/over-cap(цена>кап/$0.60 при $3), already holding token, max N open(глобально); (3) open позиции node -e positions.json (не забиты ли дустом); (4) budget DW pUSD (хватает ли на копию ≥$1).clob-client-v2 на Малайзии (ради обхода геоблока) → любая серверная фича UI-пути (билдер-код/фи/лимиты/логи/атрибуция) к копи-ордерам НЕ применяется автоматически — дублировать в copy-exec.mjs/copy-loop.mjs. Проверять этот стык при каждой новой серверной фиче.clob-client-v2 ClobClient принимает builderConfig:{builderCode} → createOrder сам цепляет к каждому ордеру. В copy-exec.mjs конструктор: builderConfig:{builderCode:'0x3c829d5150b70f3ba347670d4b1eda96be3c255b3f64895a7eeef5caea7952d5'} (SZHub bytes32, верифиц. vs builder-fees API; НЕ путать с naive 0x535a4875…). Без него копи-объём не идёт в Builder Analytics и не зарабатывает фи. Деплой = rsync на Малайзию + рестарт copy-trader.0x3643… (ручные тесты) + deposit-wallet 0x50A8061e… (копи-бот) = 2, оба Rick. taker fee ~0 до ~10 июня; свой билдер-фи = возврат себе (нейтрально), доход с ДРУГИХ юзеров.SettingsPage.clearSW): unregister SW БЕЗ чистки Cache Storage = бесполезно (старый SW контролит страницу до выгрузки → reload отдаёт старый бандл). Правильно: caches.keys()→caches.delete() (чистит workbox-precache) ПОТОМ unregister ПОТОМ hard-reload. Кнопка «Обновить приложение» в Settings→Данные./activity имена полей ≠ нашей схеме. Фид отдаёт transactionHash/usdcSize/timestamp(unix), а НЕ id/amount/createdAt/realizedPnl. Кто кладёт сырьё без маппинга — получает $0/пустоту. Маппинг: id=txHash-asset (у REDEEM asset пустой + неск. в 1 tx → ключ conditionId), amount=usdcSize, createdAt=ISO из unix timestamp. Бэкенд-хелпер cache_activity_rows() (routes.rs); фронт — в хуке usePortfolioActivity./portfolio/summary агрегирует из кеша trade_history; кеш наполняет cache_activity_rows. Summary САМ фетчит+кеширует историю (пагинация до 2000), не зависит от History-вкладки.conditionId — BUY копит (shares,cost$); SELL = выручка − avg×шт; REDEEM = usdcSize − остаток_cost (payout=$0 у проигравших = полный убыток, такие строки есть). Daily по дате события, winRate по дням с ненулевым realized. Открытые позы НЕ в realized (unrealized, на Positions). Average-cost, не FIFO.npm run build (Vite/rolldown) НЕ делает type-check → необъявленная переменная/пропущенный импорт собирается зелёным, падает в РАНТАЙМЕ (ReferenceError → ErrorBoundary рисует ошибку на вкладке). Реальный кейс: PrivyAccount.tsx юзал useT() без import { useT } from '../i18n/LanguageContext' → вкладка «Ещё» в ошибке. Хочешь ловить до деплоя — добавить tsc --noEmit в build-скрипт.pos-redeem-btn = кнопка Delete (переиспользует класс), НЕ трогать.MAX_NOTIONAL=$3 (копирует входы до цены 0.60; при $2 резалось всё дороже $0.40 — CLOB min 5sh×цена>кап), MAX_OPEN_PER_LEADER=2, MAX_OPEN=8. Менять = inline-env рестарт на Малайзии + синк ecosystem.copy-trader.cjs (прод+MY) + pm2 save. Скрипт закрыть позы кита: kill-ludik.mjs (sellAll по лучшему биду; дуст <5sh не продать).copy-trader-mu [16 Jun ч.2]: MAX_OPEN=15, MAX_OPEN_PER_LEADER=2 (экспозиция $45). Reference node-executor/ecosystem.copy-trader-mu.cjs, но запуск = direct inline-env (.cjs как config не парсится). 🪤 namespaced-оверрайд MAX_OPEN:10 в ~/.pm2/module_conf.json на Малайзии = СТАРЫЙ copy-trader, НЕ -mu (для mu оверрайда нет). Рестарт: вытянуть полный env из pm2 jlist (токен!) → pm2 delete+direct start inline-env → pm2 save; либо pm2 restart copy-trader-mu --update-env если env не меняешь.5c2a900]: sell на резолвнутом рынке reject'ит invalid token id → раньше прямая SELL-ветка детекции (≈l.477) держала позу open вечно (слот мёрз). Теперь обе ветки (reconcile Phase B + детекция SELL) ловят regex invalid token id|token not found|market resolved/closed → close leader-exit-resolved+realize mark-PnL. Покрытие резолвов в copy_positions: resolved-onchain-absent+reconciled-onchain-absent+leader-exit-resolved (16 Jun: 135/197=69%).priceMin/priceMax (дефолт 5/95) режет покупки кита вне коридора. Симптом «кит подписан, но копий нет»: в логе демона skip (price 0.99 outside band 0.05-0.95) — кит скупает тяжёлых фаворитов по $0.96–0.99, band 0.95 их отсекает. Это НЕ баг, это правильно: фаворит-фарм (купить по 0.99 ради 0.01) = низкий эдж + риск разорения на одной осечке. НЕ поднимать band до 0.99 ради них./trades?user=): считать avg buy price + % buys ≥0.90 + долю «up or down/bitcoin/eth/sol/hyperliquid». Фаворит-фармер/скальпер = avg≥0.85 ИЛИ много крипто-up/down (HFT 5-мин, скопировать нельзя — скорость+maker-эдж) → НЕ копировать. Направленный (копируемый) = avg 0.25–0.85, ≥55% сделок в середине книги, медленные ликвидные рынки (спорт/esports/теннис, холд часами), WR 49–65% (эдж сайзингом, НЕ 95%+ фарм).whale_wallets⨝whale_trades → total_pnl>$5k, last_seen<4д, avg buy price 0.25–0.85, mid-fraction≥55%, crypto<30%, выборка≥8 трейдов. Потом ВЕРИФИЦИРОВАТЬ топ через Data API (живое поведение = БД-стате).f396 f883 6db5 c1a9 eed5; добавлены 6 новых c44f432b(+$80k) 2c335066(+$72k) 84cfffc3(+$66k) 1eaf5d5f(+$41k) 38337de2(+$24k) 84cb17a5(+$18k). На паузе (7, status='paused' НЕ delete): лудик 0x204f72f3 + 6 фаворит-фармеров 0d2e f3ce 4699 6982 e7bd 5409.kill-ludik.mjs на Малайзии (makeExecutor().sellAll({tokenID,price}), price=лучший бид). Дуст <1 sh (или <CLOB min 5) продать нельзя → резолв сам. Демон пометит closed при реконсайле (ончейн balanceOf=0).UPDATE copy_subscriptions SET status='paused' WHERE leader_address=? — роут active() отдаёт только status='active', демон через 20с (pollSubs) дропает. Строка цела, реверсивно.sqlite3 CLI и better-sqlite3 на главном сервере НЕТ — работать с data/screener.db через python3 -c "import sqlite3".5 вкладок: [🔥 Возможности] [🐋 Киты] [🔍 Скринер] [📊 Портфель] [⚙️ Ещё]
/ Возможности (SignalsFeed.tsx, таб переименован с «Сигналы» 31 May) — ГЛАВНАЯ. Карточная лента из /api/signals/feed: 🐋 КИТ (whale edge), 🎯 ФЕЙД (longshot fade), ⚡ МОМЕНТУМ (volume surge), 📈 СПАЙК (anomaly). Фильтры category/type/min_strength. Buy CTA → /market/:id?side=./whales Киты (WhalesPage.tsx) — SubTabs: 🐋 Сделки (WhaleFeed, досье+направление) / 🤝 Консенсус (useWhaleConsensus) / 🔔 Алерты (useWhaleAlerts, подписки+pending) / 🏆 Топ-кошельки (→ /whale/:address). Профиль кита: авто-бэкфилл истории, кнопка «🔔 Алерт», PnL в Recent Trades./screener Скринер (ScreenerPage.tsx) — тогл «🗂 События»(дефолт, группировка по event_id)/«📋 Все рынки» (table desktop/cards mobile). Сорт-бар, фильтры (объём/ликв/цена/завершение), live-KPI./trade Портфель (TradePage.tsx) — Positions / P&L / History/more Ещё (MorePage.tsx) — Alerts, Leaderboard, Settings, Copy2092387) — старый /home (Hot/Ending/Spikes) снесён; фича «Ending soon» убрана (бэкенд-эндпоинт /api/screener/ending-soon остался, фронта нет)./analytics/szhub2026, за nginx Basic Auth)client/src/App.tsx — routes, WalletProvider wrapperclient/src/pages/ — SignalsFeed (главная), WhalesPage, SignalsPage, ScreenerPage, MarketDetailPage, TradePage, SearchPage, AlertsPage, MorePage, AnalyticsPage, LeaderboardPage, WhaleProfilePage (HomePage УДАЛЁН)client/src/hooks/useSignalsFeed.ts — лента сигналов (WhaleSignal|MomentumSignal|SpikeSignal discriminated union)client/src/components/signals/SignalCard.tsx — карточка сигнала (бейдж + WR китов + тир 🧠 Умные деньги)client/src/hooks/usePolymarketAuth.ts — EIP-712 CLOB authclient/src/hooks/useAlerts.ts — alerts CRUD + pollingclient/src/hooks/useAnalytics.ts — builder fee analyticsclient/src/hooks/useLeaderboard.ts — builder leaderboard rankingsclient/src/hooks/useAnomalies.ts — volume anomaly detection (useEndingSoon УДАЛЁН)client/src/hooks/usePortfolioHistory.ts — trade activity + P&L statsclient/src/components/layout/Sidebar.tsx — desktop sidebar nav (≥1024px)client/src/components/layout/BottomNav.tsx — mobile bottom nav (<1024px)client/src/components/shared/SubTabs.tsx — reusable pill-tab switcherclient/src/components/home/AnomalyCard.tsx — volume spike card (still used by SignalsPage; EndingSoonCard УДАЛЁН)server-rust/src/edge.rs — edge scorer cron → edge_scores (WHALE/MOMENTUM/NEUTRAL signals)server-rust/src/whales.rs — whale scan cron: trades + wallets agg + market resolution (CLOB) + win_rate calcclient/src/components/portfolio/PnlDashboard.tsx — P&L KPIs, daily bars, equity curveclient/src/components/portfolio/TradeHistory.tsx — paginated trade listclient/src/components/trade/OrderForm.tsx — trading form (useBalance for presets)server-rust/src/routes.rs — all API handlersserver-rust/src/gamma.rs — Gamma API sync cron (every 2 min) + alert checking + anomaly detectionserver-rust/schema/schema.sql — DB schema (7 tables)GET /api/screener/markets — filtered/sorted/paginated market listGET /api/screener/market/{id} — single market detailGET /api/screener/market/{id}/orderbook — orderbookGET /api/screener/market/{id}/history — price historyGET /api/screener/stats — KPI stats (live: total_markets/total_volume_24h/ending_today/top_volume)GET /api/screener/events?category=&search=&sort=&order=&limit=&offset= — markets grouped by event_idGET /api/screener/categories — category listGET /api/screener/ending-soon?days=&min_liquidity=&limit= — markets resolving soonPOST /api/auth/derive-key — CLOB API key derivation (server-side cred storage)POST /api/trade/order — submit signed order (builder_code "SZHub" 0.5%)GET /api/alerts — list alertsPOST /api/alerts — create price alertDELETE /api/alerts/{id} — delete alertGET /api/signals/feed?limit=&category=&type=&min_strength= — карточная лента (WHALE/MOMENTUM/SPIKE). WHALE-строки несут whaleWr (AVG win_rate уникальных китов рынка, null если неизвестно), whaleCount (# сделок кластера) + whaleVolumeUsd ($-объём). detectedAt отдаётся в RFC3339 UTC (с Z)GET /api/signals/whales — лента крупных сделок (>$5k, 7д)GET /api/signals/top-traders — лидерборд китов по объёму (winRate, top_category)GET /api/signals/whale/{address} — профиль китаGET /api/signals/anomalies?min_score=&hours=&limit= — volume spike alertsGET /api/analytics/volume?period= — builder daily volume (Data API proxy)GET /api/analytics/trades?cursor=&after=&before= — builder trades (CLOB proxy)GET /api/analytics/rank?period= — builder leaderboard (Data API proxy)GET /api/portfolio/activity?address=&limit=&offset= — trade history (Data API proxy + cache)GET /api/portfolio/summary?address= — P&L summary from cached tradesedge_scores. Сигнал: WHALE (whale-кластер ≥2 сделок, ≥$20k, edge >0.15 от цены → BUY_YES/BUY_NO) · MOMENTUM (NEUTRAL + объём ≥3× от 7-дн среднего + цена 10–90¢) · NEUTRAL. Колонки: edge_score, signal, confidence, momentum_factor, whale_yes_pct.whale_trades → агрегация whale_wallets → резолвы (resolve_whale_markets: CLOB /markets/{conditionId}, tokens[].winner=true → screener_markets.resolved/winning_outcome, throttle MAX_RESOLVE_PER_SCAN=120/скан) → win_rate (update_win_rates: доля BUY-сделок где outcome==winning_outcome среди резолвнутых BUY; null если 0).82f49f9): Portfolio тянет позиции прямо с Data API (по DW) — лидера нет. Роут GET /api/copy/position-leaders (user-authed, Privy DID) → карта conditionId→leader из copy_positions (open). TradePage: всегда показывает кита-входа (мерж с leaderHoldings, value=0 если нет live-холда) + 🎯-бейдж pf.entryLeader при whales>1. Старый leaderHoldings (live-холд) флакал → одиночные карточки были пустые.014a9cb): колонка resolve_checked_at + WHERE-backoff (свежезакрытые <7д eligible /20мин, старые/пустые /сутки) + ORDER never-checked→least-recently-checked→end_date DESC + штамп КАЖДОЙ проверки (resolved или нет). Зачем: end_date ASC долбил древние НЕзакрытые war/гео long-shot'ы (end_date в прошлом, но рынок ОТКРЫТ на PM месяцами) → ~50/120 резолвов. После: 115/120 (×2.3). 🛠️ Билд ТОЛЬКО foreground cargo build --release -j 1 (RAM сервера 1.5Gi/без свопа → фоновые сборки гибнут на resume + OOM; ~2.5мин). Деплой главного = pm2 restart polymarket-screener (миграция ALTER в db.rs на старте).?condition_ids= НЕ батчится и теряет резолвнутые → юзаем CLOB. sqlite3 CLI на сервере НЕТ → python3. win_rate считается ТОЛЬКО для китов с резолв-BUY (иначе NULL, чип не виден).c65be9e): реальный id в client/.env.production (публичный, коммитим). Было 'demo' → мобильный WC не работал. Rust лекает index.html → рестарт скринера после фронт-билда для свежих deep-link'ов.57bdf1b): ошибка «Active chainId is 0x1 but received 0x89» (кошелёк на Ethereum, ClobAuth chainId 137) → useSwitchChain().switchChainAsync({chainId:137}) перед подписью в usePolymarketAuth.deriveApiKey + OrderForm.handleSubmit. ⚠️ wagmi v2 = useSwitchChain НЕ useSwitchChainAsync.0x1aec73…22d1 ($4 USDC+114 POL). weather-bot был ОНЛАЙН (DRY_RUN=false!) — остановлен pm2 stop+save. Прокси Safe = $3 коллатерал + ~$23 в дневных погодных позициях (ждать резолв+redeem, не продавать).ca33576): лёгкий кастомный (без либы). client/src/i18n/translations.ts (словарь {en,ru}, плоские ключи, фолбэк en→key) + LanguageContext.tsx (LanguageProvider в main.tsx, useT()→{lang,setLang,t}). Дефолт = localStorage sz_lang иначе navigator.language. Переключатель в SettingsPage. t(key,{n}) подставляет {x}. Добавить язык = блок в translations.ts + строка LANGUAGES.useT() инкрементально.trades.map(t=>…) затеняет i18n t → деструктуризуй const {t: tr}=useT().5fe1359+1d7511a+bb89c66): TradePage/PnlDashboard/TradeHistory двуязычны; карточка позиции += полоска вероятности(curPrice)+⏳countdown(endDate); сводка 2×2 += «К погашению» (Σ redeemable).594209b): stats теперь ВЖИВУЮ из active=1 (был разрыв 24.9k stale vs 34.7k список). «Avg Spread»→«Заканчиваются за 24ч», «Top Mover»→«🔥 Топ по объёму». formatPrice суб-цент→<1¢. Поля stats: ending_today/top_volume (убраны total_events/avg_spread/top_mover).20451a0): видимый селектор сортировки на мобиле (Объём/Ликвидность/Спред/Заканчивается/Новые ↓↑). Бэк += updated_at.5c64b68): полоска implied-вероятности + ликв/спред + ⏳ countdown.0089374+c1413b2): GET /api/screener/events группирует по event_id (35.5k→3949 событий). Агрегат vol/liq, COUNT исходов, топ-3 фаворита (yes_price, 2-й запрос IN). Фронт: тогл «🗂 События»(дефолт)/«📋 Все рынки», EventsList. «World Cup Winner» 60→1 карточка $105M.4d7d87f): min_liquidity + ending (today/week) в markets-эндпоинт + панель.data.markets+data.meta. ⚠️ пустой end_date всплывает в сорте «Заканчивается».8b8c6d0 краш таба — сменил форму stats (top_mover→top_volume), PWA отдавал старый кэш → top_volume.question undefined → ErrorBoundary; фикс = KPIBar крашеустойчив (коэрсить все поля). 390093b KPI overflow на мобиле: repeat(2,1fr)→repeat(2,minmax(0,1fr)) (1fr=minmax(auto,1fr), большие числа задавали min-content). Урок: меняешь форму API → коэрси на фронте + minmax(0,1fr) для grid с крупными значениями.b307e6e): offset + «Стр X из Y», бэкфилл на стабильном fetchRef./trades = ГЛОБАЛЬНЫЙ поток (500 последних × все юзеры, ≥$5k, scan 5мин). Поэтому ~4-6 на кита. Фильтр ?user=<addr> даёт ИСТОРИЮ конкретного кита (сотни).6d7c70c+1a3d004): POST /api/signals/whale/{address}/backfill (fetch_user_trades 5стр→process_whale_trades→update_win_rates). useWhaleProfile зовёт на mount, бейдж «↻ обновляю историю». Whale #58: 6→363, WR из шума в реальный.whale_alerts(whale_address UNIQUE,last_trade_ts,pending_count) + CRUD (POST/GET /api/signals/whale-alerts,DELETE+POST /{addr}/seen). check_whale_alerts в whale-кроне подтягивает историю подписанных + UPDATE pending_count(+новые)/last_trade_ts. Фронт: кнопка «🔔 Алерт» toggle на профиле (заменила копирование), под-вкладка «🔔 Алерты» с «+N новых». Подписка сидит last_trade_ts на текущую → только БУДУЩИЕ.a43f3ee): трейды профиля JOIN screener_markets.resolved/winning_outcome. Realized только резолв-BUY: win=size·(1−price), loss=−amount. 🟢+$X·+Y%/🔴−$X·−100%/⏳в игре. ⚠️ Unrealized НЕ считаем (исходы=команды, нет тек.цены). Покрытие ~22%.Option<bool> не парсит "1"→"true". borrow: собирать адреса в Vec push'ем, не collect-tail. Активная БД=data/screener.db.bee08c1): /api/signals/feed?horizon=fast|mid|long (≤2д / 2д–2нед / >2нед), SQL на sm.end_date в обоих запросах. UI — циклич. тогл ⏱.4c1c759): фид китов отдаёт whaleWinRate(0..1)+whaleTopCategory. Строка: чип 🐋 $vol · N сделок · WR% + 🧠 Smart при WR≥60%. ⚠️ WR на малых выборках = шум.52e67bc): /api/signals/whales?include_resolved (дефолт false) прячет цены ≤5¢/≥95¢ в общей ленте (не в market-view). Тогл «Показать решённые». ⚠️ Option<bool> НЕ парсит "1" — слать "true".6248b4f, фронт): BUY Yes/SELL No→🟢 «Ставка→Yes»; BUY No/SELL Yes→🔴 «Ставка→No». Левый акцент-бар, рус купил/продал, цена «вход».068ac91+7c86886): /api/signals/whales/consensus группирует whale_trades по рынку → netSide, netFlowUsd, whales(distinct), agreement(0.5–1.0), avgPrice(SUM(amount)/SUM(size) дом.стороны), HAVING whales>=2. Фронт: саб-таб «🤝 Консенсус» (useWhaleConsensus), карточки «🐋 N китов → Yes · $X · согласие %». Самая ценная фича.entry_quality = spread_quality*0.6 + liq_quality*0.4, где spread_quality = 1-|yes+no-1|/0.15 (тесный спред = хорошо; инвертировано от старой модели). Новые колонки edge_scores.entry_quality, source (идемпотентный ALTER).source), приоритет whale > longshot > momentum: WHALE (кластер) · LONGSHOT · MOMENTUM. source различает их в фиде (whale/longshot делят BUY_YES/BUY_NO).strength=(0.20-cheap)/0.18 (дешевле лонгшот = сильнее). Дешёвый YES→BUY_NO, дешёвый NO→BUY_YES. Confidence ≤ MED. Floor 2¢. Sort +1.0 (ниже китов).SignalCard две шкалы-бара + бейдж 🎯 ФЕЙД + «Ставка: NO · вероятность 90%». Фильтр-чип «🎯 Фейд». Таб нав «Сигналы»→«Возможности».screener_markets (edge + spike) → endDate (ISO) для ВСЕХ карточек, без миграции. format.ts timeUntil() рус «45 мин/12 ч/3 д/2 мес/завершён» (tz-less=UTC). Чип .sc-ends (янтарь).volume_anomalies va обязателен (join с screener_markets иначе ambiguous question/category).sqlite_utc_to_iso() в routes.rs отдаёт ...TZ для всех detectedAt. Фронт: единый timeAgo() в utils/format.ts (русский «только что/5 мин/2 ч/3 д»), сам нормализует tz-less как UTC. 4 локальных дубля удалены (SignalCard/WhaleFeed/WhaleProfile/AnomalyCard).edge_scores.whale_count + whale_volume_usd (идемпотентный ALTER в edge.rs + schema.sql). Карточка: «$33.4K · 4 сделки» вместо «100% объёма китов» (фолбэк на старый текст при count=0).label="Кошелёк" + CSS nowrap — не переносится на 2 строки.mask-image fade у правого края .category-pills — намёк на горизонтальный скролл.0x3c829d5150b70f3ba347670d4b1eda96be3c255b3f64895a7eeef5caea7952d50x535a4875… replaced (30 May)0x3643914646900cA7A5df15B8f5d1Cc5E32728c1aWas V1 = BROKEN → builder earned $0. Migrated to CLOB V2 (live 28 Apr 2026).
Full verified spec + Step A hash-proof: docs/CLOB_V2_MIGRATION.md.
Commits: A order.ts 7905e4f, B OrderForm f78b05a, C routes.rs bc063c4, Step A fixture fed8cbe.
Re-verified: viem (order.ts) == py-clob-client-v2, digest+sig byte-identical (scripts/stepA_v2_ref.py).
⚠️ Live test still pending — needs funded EOA in MetaMask (see Wallet note).
0xE111180000d2663C0091e4f400237545B87B996B, NegRisk V2 0xe2222d279d744050d28e00520010520000310F59.builder INSIDE signed struct. signatureType=0 (browser EOA). Side BUY=0/SELL=1.{order:{…,signature}, owner, orderType, deferExec:false, postOnly:false}.py-clob-client-v2 v1.0.1, live in weather-bot venv.Источник истины: docs/REDESIGN_PLAN.md (24f94e3). Главный экран / = карточная
лента 🔥 Сигналы (WHALE/SPIKE/MOMENTUM/NEW MONEY/WIDE SPREAD + Buy-кнопка), отдельная
вкладка 🐋 Киты (лента + лидерборд + профили), скринер-таблица → 3й план. Навигация 5
вкладок: Сигналы/Киты/Скринер/Портфель/Ещё. Deps #1 (edge.rs) и #3 (CLOB V2 trade) ✅.
Уже есть кирпичи: EdgeScanner+WhaleFeed на /signals, volume_anomalies, /whale/:address.
Метод: 1 микрошаг → commit → спросить дальше.
Фазы: 1 каркас навигации (4 шага) · 2 лента Сигналов (6) · 3 вкладка Киты (3) · 4 полировка (3).
✅ Фаза 1 DONE (31 May, 3e8e992/11c7c80/0e744ca): роуты-заглушки (HomePage→/home), BottomNav + Sidebar 5 вкладок синхронно.
✅ Фаза 2 DONE (31 May, 787eb26→3e2acba): backend GET /api/signals/feed (edge_scores type=WHALE + volume_anomalies type=SPIKE; WHALE-first sort key +2.0; normalized strength WHALE=edge_score/SPIKE=min(score/10,1); фильтры category/type/min_strength post-fetch по пулу 300). useSignalsFeed hook (SignalCard=WhaleSignal|SpikeSignal). SignalCard.tsx (бейдж+картинка+метрика). SignalsFeed страница (grid/skeleton/empty + filter-bar). Buy CTA → /market/:id?side= (OrderForm initialSide).
⚠️ directional WHALE в БД=1, спайков ~50 (данные, не код). ⚠️ PWA SW кэширует — fix: закрыть PWA/Clear site data.
✅ Фаза 3 DONE (31 May, 4775c42): WhalesPage заменила заглушку — SubTabs 🐋 Сделки (WhaleFeed) + 🏆 Топ-кошельки (useTopTraders → /whale/:address). 3.1/3.3 уже существовали (/api/signals/whales+/top-traders+/whale/{address}), переиспользованы. NB: LeaderboardPage=билдеры SZHub, не киты.
СЛЕДУЮЩИЙ ШАГ = Фаза 4 (полировка): 4.1 WR+спец-категория китов в карточках Сигналов, 4.2 типы NEW MONEY/MOMENTUM в edge.rs+фид, 4.3 судьба старого HomePage (/home) — слить в Скринер или удалить (нужно разрешение Rick).
weather-bot = custodial sigType2 (funds in proxy 0x3643…); screener = non-custodial sigType0 (browser EOA-direct). Can't live-test screener with bot wallet (funds in proxy, type-0 order won't see them) → need a funded EOA in MetaMask.
screener_markets — all Gamma marketsscreener_stats — aggregated KPIsalerts — user price alertsvolume_daily — daily volume snapshots per marketvolume_anomalies — detected volume spikes (score >= 3x)trade_history — cached trade activity from Data API0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E0xC5d563A36AE78145C45a50134d48A1215220f80a0xE111180000d2663C0091e4f400237545B87B996B0xe2222d279d744050d28e00520010520000310F590x4D97DCd97eC945f40cF65F87097ACe5EA04760450xd91E80cF2E7be2e162c6513ceD06f1dD0dA352960x2791Bca1f2de4661ED88A30C99A7a9449Aa84174client/src/App.css (~3100 lines) — all styles consolidatedindex.css (theme colors, --green, --red, --blue, --bg-hover)1dcc3ba): formatVolume/formatPrice коэрсят non-finite → $0/<1¢. Одна битая числовая строка не должна обелять весь апп через ErrorBoundary — при добавлении новых .toFixed() на API-данные гардить (?? 0 / Number.isFinite).73bae2a): убран молчаливый сброс. usePolymarketAuth ставит видимую подсказку вместо setError(null); TopBar показывает жёлтый баннер под шапкой (тултип на тач невидим); OrderForm рендерит ошибку в ветке «не подписан».3cd3e7f): utils/walletBrowser.ts + WalletBrowserBanner.tsx. На мобиле без инъектированного провайдера (window.ethereum) показывается баннер с deep-link'ами MetaMask/Coinbase/Trust → юзер открывает dApp ВНУТРИ браузера кошелька → инъектированный EIP-1193 провайдер → подпись нативная, релей не участвует. Как у самого Polymarket. RainbowKit getDefaultConfig сам подхватывает injected-коннектор (EIP-6963), доп. кода в пути подписи нет.https://metamask.app.link/dapp/<host+path>, Coinbase https://go.cb-w.com/dapp?cb_url=<encoded>, Trust https://link.trustwallet.com/open_url?url=<encoded>. Единого стандарта нет.73bae2a): в TradePage.tsx const t = setTimeout затенял i18n t() → красный билд мастера. Переименован в timer.curl https://polymarket.com/api/geoblock → blocked:true). Торговые вызовы CLOB (derive-key + order) идут через SOCKS5-прокси TRADE_PROXY (тот же, что у weather-bot, гео BR). Отдельный http_trade reqwest-клиент (фича socks в Cargo.toml). Схема обязательно socks5h (remote DNS) — socks5 (локальный DNS) фейлит коннект. main.rs нормализует socks5://→socks5h://.process.env.TRADE_PROXY || '', секрет НЕ в git). ⚠️ pm2 restart --update-env БЕЗ переменной в шелле СБРАСЫВАЕТ прокси → всегда: TRADE_PROXY="$(grep ^TRADE_PROXY /home/app/polymarket-weather-bot/.env|cut -d= -f2-)" pm2 restart polymarket-screener --update-env && pm2 save.maker=VITE_POLY_FUNDER (Safe 0x3643914646900cA7A5df15B8f5d1Cc5E32728c1a), signer=EOA (0x1aec730…, владелец Safe), signatureType=2. Конфиг в client/.env.production (публичные адреса). Backend owner-check ослаблен до signer==owner. Single-wallet (жёстко на Safe weather-bot'а); мульти-юзер → derive proxy из EOA (sigType1) позже.int(random()*now_ms) как JSON-number (НЕ 256-бит, НЕ строка). Суммы округляются до 2 знаков (roundDown2 для shares, roundNearest2 для USDC — флор давал $0.99<min$1 из-за FP). Market FOK: слиппедж +5¢ (MARKET_SLIPPAGE) иначе FOK killed. min order = $1.VITE_POLY_FUNDER (Safe), не EOA — иначе пусто. P&L: Data API percentPnl УЖЕ в процентах (НЕ умножать ×100); cashPnl в центах со знаком.cargo NOT on sandbox PATH → export PATH="$HOME/.cargo/bin:$PATH" before cargo build# Frontend
cd client && npm run build
# Backend (на сервере с cargo)
cd server-rust && cargo build --release
# PM2
pm2 restart polymarket-screener
97daf19): clob-client-v2 + Privy server-auth viem-account + funderAddress=DW + SignatureTypeV2.POLY_1271 → createOrder({tokenID,price,size,side,feeRateBps},{tickSize,negRisk}) строит валидный sigType3 ERC-7739 ордер ОФЛАЙН за юзера (maker==signer==deposit-wallet 0x50A8061e…F24Ff).clob.createOrDeriveApiKey() на deposit-wallet вернул валидные CLOB-креды → EIP-1271 L1-auth работает; стена Issue#70 на зарегистр. deposit-wallet мертва.@polymarket/clob-client-v2 имеет ВЛОЖЕННЫЙ node_modules/.../axios → корневой axios.defaults.httpsAgent=SocksProxyAgent НЕ применяется к нему (пост шёл мимо прокси → 403 geoblock). Чтобы прокся применилась — патчить вложенный axios ИЛИ постить через Rust-бэкенд (он уже роутит order-post через TRADE_PROXY).~/.pm2/dump.pm2 (не в файлах): grep -ao 'socks5h\?://[^"]*' ~/.pm2/dump.pm2. Сейчас отвечал ConnectionRefused — проверять живость перед использованием.72.62.247.119, Hostinger MY, SSH-ключ ~/.ssh/id_ed25519.bak) — Малайзии нет в блок-листе Polymarket. DE-сервер + BR-прокси геоблочены; локальный SSH -D SOCKS-туннель в sandbox НЕ работает (listen рубится, exit 144).~/poly-exec (rsync node-executor/*.mjs+package*, scp ../.env.privy→./.env.privy chmod600, npm i --legacy-peer-deps). Запуск: node --experimental-global-webcrypto post-my.mjs (Node 18.20 без флага падает на globalThis.crypto.subtle unavailable в L2-HMAC; либо обновить до Node≥20).{success:true,status:"live",orderID:"0x96a74969…dc963"}.GET /trades?user=<leader> (публичный, не геоблочен). Поле asset=ERC1155 tokenID (для ордера); side/size/price/conditionId/timestamp/transactionHash. Лаг данных ~5мин (для 5-мин BTC рынков копия опаздывает).copy-loop.mjs: детекция сделок лидера + дедуп (state JSON) + решение копии (size=leaderSize×COPY_FRACTION, пол=CLOB min 5 shares, жёсткий MAX_NOTIONAL→skip). Прайминг: первый проход = базлайн, бэклог НЕ копируется. DRY по умолчанию; LIVE=1 постит BUY через executor (SELL отложен — нужен учёт позиций).copy-exec.mjs: execution-примитив makeExecutor()→postOrder({tokenID,side,price,size}) (Privy server-auth+CLOB+L1, sigType3). CLI self-test через ORDER_JSON. Live-пруф Малайзия: orderID 0xb40087…95ad.~/poly-exec): LIVE=1 LEADER=0x.. COPY_FRACTION=0.1 MIN_SHARES=5 MAX_NOTIONAL=2 STATE=cl-state.json node --experimental-global-webcrypto copy-loop.mjs.wss://ws-live-data.polymarket.com, подписка {action:"subscribe",subscriptions:[{topic:"activity",type:"trades"}]}, ping PING каждые 5с. Сообщение (payload): proxyWallet, asset(=ERC1155 tokenID), side, size, price, conditionId, outcome, title, transactionHash. Нет server-side user-фильтра → фильтруем по адресу лидера client-side (906 сделок/20с — ок).POLL=1 = Data API /trades?user= fallback (~5мин лаг). Мультилидер: LEADER=0x..,0x... Reconnect backoff, in-memory дедуп.OrderFilled логи CTF Exchange Polygon (~2с; декод makerAssetId→tokenID, side BUY=0/SELL=1, осторожно NegRisk taker=0xC5d563A36AE78145C45a50134d48A1215220f80a).LIVE=1 LEADER=0x.. node --experimental-global-webcrypto copy-loop.mjs. ws установлен в ~/poly-exec.makeExecutor()→{buy, sellAll, tokenBalance, EOA, DW}. sellAll({tokenID,price}) читает ERC1155-баланс DW (CTF 0x4D97DCd97eC945f40cF65F87097ACe5EA0476045, 6dec) и продаёт ВСЁ держание.handleTrade(t): BUY→открыть+positions.json; лидер SELL по держимому→sellAll+closed; SELL по недержимому игнор. Ledger пишется только в LIVE.approve-dw-ctf.mjs (relayer-batch, DE) setApprovalForAll→ExchangeV2(0xE111…996B)+NegRisk(0xC5d563A36AE78145C45a50134d48A1215220f80a). DONE/CONFIRMED для DW 0x50A8….ecosystem.copy-trader.js (Малайзия). Старт: задать LEADER→pm2 start ecosystem.copy-trader.js; смена лидера→edit+pm2 restart copy-trader --update-env; pm2 logs copy-trader.privy.rs verify_token() — Privy access-token ES256 против JWKS (кэш), iss=privy.io+aud=app_id→DID. copytrade::authed_user = SIWE ИЛИ Privy Bearer (X-User-Address убран). Фронт useCopySubscriptions шлёт Authorization: Bearer getAccessToken(). user_address = Privy DID.~/poly-exec, pm2 copy-trader. ЗАПУСК (прямой, НЕ через ecosystem — poly-exec type:module ломает pm2-config-детект):
export LIVE=1 SUBS_URL='https://poly-dev.szhub.space/api/copy/active' SERVICE_TOKEN='<COPY_SERVICE_TOKEN>' FOLLOWER='did:privy:cmq45iuzf00mx0cl2xtt1vxf1'
pm2 start copy-loop.mjs --name copy-trader --interpreter node --interpreter-args '--experimental-global-webcrypto'~/.pm2/logs/copy-trader-out.log (stdout буферится — для DRY-теста писать в файл). Stop/restart: pm2 stop/restart copy-trader..env.privy (gitignored). Тест-вставка подписки: data/screener.db copy_subscriptions (user_address=DID).0x50A8061e… баланс ~$10.49 pUSD + ~$10 в позициях (P&L +$4.35). Cron редим+wrap 15мин на DE (редим доказан ончейн; wrap ждёт первого выигрыша). Всё закоммичено/запушено. NEXT: 1) почистить MEMORY.md (76KB→<24KB, старьё в activity/) 2) навести порядок на серваке (логи, временные скрипты node-executor, dev-процессы) 3) опц. карточка «Всего» в портфеле + поднять MAX_OPEN.id_ed25519(app@szhub) НЕ в authorized_keys сервера, старый остался. Рабочий = id_ed25519.bak(you@example.com). Fix: скопировал в ~/.ssh/id_malaysia + ~/.ssh/config Host-блок (Host malaysia 72.62.247.119). Теперь ssh root@72.62.247.119 и ssh malaysia работают. Это НАШ прод (futures-screener id0 + copy-trader id7), не чужой.MAX_OPEN=5. Кит грузил 1 игру (Spurs/Knicks) мультиногами (ML+спреды+O/U) → 5 слотов забиты, дедуп был только по asset (один рынок), не по событию.0d6d46f): группировка по eventSlug (есть в трейде, напр. nba-sas-nyk-2026-06-08 — объединяет все ноги игры). cfg.maxPerEvent (env MAX_PER_EVENT=1): копируем ПЕРВУЮ увиденную ногу события у кита, остальные ноги той же игры — skip. eventSlug сохраняется в записи позиции.copy-loop.mjs): computeAndPostPnL() каждые 2 мин джойнит DW /positions (Data API cashPnl) × positions.json (token→leader), POST на PNL_URL (=SUBS_URL заменой /active→/pnl). Realized копится: reconcile стампит markPnl=live cashPnl каждый цикл, при закрытии (resolve-absent ИЛИ лидер SELL) последний mark банкуется в realizedPnl. Эмитит на лидера: {realized, unrealized, pnl(=сумма), value, openCount, closedCount}.copytrade.rs + main.rs AppState copy_pnl: RwLock<Value> + route): POST /api/copy/pnl (service-token гард, в память) + GET /api/copy/pnl (открытый read для UI). Хранит JSON как есть → новые поля проходят без пересборки.CopyTradingPage.tsx): фетч /api/copy/pnl поллинг 30с, строка на КАЖДОЙ карточке: PnL: +$X · реализ. +$Y · N откр · M закр (зелёным/красным), без копий → PnL: — · ещё нет копий.9d447db(exec) c42e28b(rust) 8d1583f(ui) c28b494(B1 realized) 98651d0(B2 always-show). Всё запушено.2ac426b перед правками.client/src/i18n/LanguageContext.tsx (useT()→{t,lang,setLang}, t(key,params) с {var} интерполяцией, fallback en→key, setLang пишет localStorage sz_lang+<html lang>), словарь translations.ts (плоские dotted-ключи, блоки en/ru). Добавить язык = блок в translations + строка в LANGUAGES.t() (фазами, ~25 файлов): навигация (BottomNav/Sidebar/TopBar), Лидеры, Мои копии+CopyConfigModal, Рынки/Скринер (ScreenerPage/MarketsTable/KPIBar/EventsList), Сигналы (SignalsFeed/SignalCard), Киты (WhaleFeed/WhaleProfile/WhalesPage/WhaleProfilePage), Портфель (CustodyCard), PrivyAccount, WalletBrowserBanner, SettingsPage admin, TradePage, OrderForm, Siwe/PolymarketAuthProvider (ошибки подписи). Коммиты: 4e01179 ff71074 fce4a32 2285935 3fcd243 b417558.navigator.language ru→ru иначе en), ручной выбор перебивает+сохраняется. Rick сначала просил EN-дефолт (коммит 292a914), потом откатили обратно на автодетект — финал = автодетект.utils/format.ts) возвращали хардкод-русский («5 мин») → main page был RU даже в EN. Fix: функции читают активный язык из localStorage и берут перевод из словаря (_t() хелпер, re-render на тогле подхватывает). Коммит fb186d7. Ключи time.justNow/min/hr/day/mo/ended.t затеняет функцию перевода t. В .map((t)=>...) или ({trade:t}) любой t('key') падает (t — объект, не функция). Fix: алиас const { t: tr } = useT() в файлах LeadersPage, SignalsFeed, WhaleFeed, WhaleProfile, WhalesPage(Leaderboard). Проверять .map((t/(t,/trade: t перед обвязкой.t. Fix: хранить labelKey вместо label, резолвить t(x.labelKey) в рендере. Для смешанных ($-значения языконезависимы + «Любой») — опциональный labelKey? + {opt.labelKey ? t(opt.labelKey) : opt.label}.import после сигнатуры функции (якорь импорта = строка функции) → PARSE_ERROR «import only at top level». Fix: импорты вставлять после существующего import, а если файл без импортов (CustodyCard — только JSDoc) — префиксом в самый верх.xOne/xMany (EN: trade/trades; RU: сделка/сделок — для 2-4 чуть неточно, но RU вторичен).965eac3): 📋 на углу аватара кита если он в подписках (зелёный active / серый paused), via useCopySubscriptions.3c5008b): sortbar → горизонтальный скролл (flex:0 0 auto; overflow-x:auto, скрытый скроллбар), «Профит»→«P&L» (бэк уже сортировал по total_pnl).client/src висит ЧУЖАЯ недоделка (admins.ts, useIsAdmin.ts, useAnalytics/AnalyticsPage M) — admin-only analytics, НЕ трогать без задачи.0x204f72f3 и выгребает все слоты (MAX_OPEN глобальный, пер-кит лимита НЕ было). 394 копии других зарезано max open reached. Диагностика: ssh malaysia → `(open by leader) +pm2 logs copy-trader` grep skip.f6a30ac): MAX_OPEN_PER_LEADER (env, дефолт 1). Фильтр в BUY-ветке copy-loop.mjs (по образцу MAX_PER_EVENT): leaderOpen = open && p.leader===leader; if >= cfg.maxOpenPerLeader skip. Прокинут в deriveCfg (UI может только понизить). MAX_OPEN 5→8→10 (Rick: ночью набирать до 10).c4f3577): Rick прислал 4 native USDC (Trust Wallet «Основной кошелек 2», адрес НЕ Privy EOA) на DW. Onramp pUSD реверит на нативном (batch would revert) — принимает только USDC.e. Цепочка gasless через relayer executeDepositWalletBatch (DW без MATIC):swap-usdc.mjs: native USDC (0x3c499…) → USDC.e (0x2791…) через Uniswap V3 fee=100 pool (0xD36e…a418), SwapRouter02 0x68b3465833…, exactInputSingle, slippage 0.5%.wrap-asset.mjs: универсальный wrap любого asset → pUSD через Onramp 0x93070a847… (обобщение step2 redeem-dw). Запускать WRAP_ASSET=0x2791…./home/app/polymarket-screener/node-executor), НЕ Малайзия — нужен relayer (не геоблок) + builder-sign localhost:3240. DW $9.71→$13.71.af0e084): Portfolio (Data API ончейн) = 8 поз, демон (positions.json open) = 5. Причина: reconcileOpenPositions() закрывал слот если позиция пропала из Data API /positions >grace(600с) — но Data API флакает и временно теряет held-позиции → 3 still-held копии забанены resolved-onchain-absent, потом вернулись. Демон недосчитывал экспозицию против MAX_OPEN. Fix: перед закрытием читать CTF.balanceOf(DEPOSIT_WALLET, BigInt(tokenID)) ERC1155 (CTF 0x4D97DCd97…, 6dec) — источник правды; закрывать только при ончейн-нуле; RPC-err→continue (keep+retry). CTF.balanceOf отдаёт реальные холды даже для sports O/U/spreads (не только moneyline).77511ca): resync-ledger.mjs — свип всех closed записей, у кого CTF.balanceOf>0 → reopen (del closedReason/realizedPnl). Вернул 3 призрака, свип подтвердил других нет → 8 open = 8 ончейн. Env: POSITIONS=, DEPOSIT_WALLET=, DRY=1./activity. Мелочи: CustodyCard.tsx — (помеченная заглушка), wallet.ts WC projectId='demo' fallback (в TODO)..cjs ecosystem НЕ парсится pm2 start как config-файл (package.json type:module → pm2 копирует в .js → ESM-падение module.exports). Имя процесса становится ecosystem.copy-trader, не copy-trader. Fix: запускать демон напрямую pm2 delete copy-trader; LIVE=1 SUBS_URL=… SERVICE_TOKEN=… FOLLOWER=… MAX_OPEN=10 MAX_OPEN_PER_LEADER=1 … pm2 start copy-loop.mjs --name copy-trader --interpreter node --node-args="--experimental-global-webcrypto" --update-env; pm2 save. SERVICE_TOKEN на Малайзии = pm2 env <id> | grep SERVICE_TOKEN.executeDepositWalletBatch прошёл (не только CTF/Onramp/Exchange). Полезно для swap-then-wrap.wss://ws-live-data.polymarket.com отдаёт 429 на WS-handshake с датацентр-IP (Cloudflare throttling). Демон в mode=WS бесконечно реконнектил ([ws] error: 429 → reconnecting), детект мёртв, 0 копий при живом бюджете+китах. Подтверждение что не наш бан: мгновенный 429 со ВСЕХ IP — Малайзия v4+v6 И главный сервер Ванкувер (curl-handshake + реальный ws-клиент). Endpoint/подписка правильные (ресёрч: endpoint жив, 426 на plain GET = норма). Data API REST (/trades?user=) работает. Вывод: WS с server-IP структурно недоступен → POLL надёжнее.tick() опрашивал [...LEADERS][0] (env LEADER), но демон DB-driven (LEADER='', 12 китов в activeLeaders) → флип POLL=1 опросил бы undefined. Фикс (1649a8d, запушен): переписал tick() — опрашивает ВСЕ activeLeaders через Data API Promise.allSettled, per-leader курсоры (state.lastTs{}/state.primed{}), приминг каждого кита при первом виде (baseline current trades как seen, историю НЕ копирует). 12 китов /8с = 90 req/мин < лимит 100. Канонический исходник /home/app/polymarket-screener/node-executor/copy-loop.mjs → rsync на Малайзию.POLL_MS 15с→8с (5с отверг: 12×12=144 req/мин > 100/мин публичный кап Data API → словили бы 429 и на REST; 8с=90/мин безопасный пол). MAX_OPEN 8→10, MAX_OPEN_PER_LEADER 1→2.MAX_OPEN=10 инлайном НЕ применялся — оставался 8. Причина: pm2 set copy-trader:MAX_OPEN 8 зашит в ~/.pm2/module_conf.json, pm2 инжектит его как env (и как JSON-blob env copy-trader={"MAX_OPEN":"8"}) ПОВЕРХ старта. POLL_MS/MAX_OPEN_PER_LEADER такого оверрайда не имели → брались инлайн. Fix: pm2 set copy-trader:MAX_OPEN 10 + рестарт. Если переменная «не слушается» инлайн-env → чекать pm2 conf/module_conf.json.closedReason ставится верно (resolved-onchain-absent и пр.), realizedPnl считается (XLG win +$4.14, лузеры в минус). Redeem-крон НА ГЛАВНОМ сервере (не Малайзия): */15 * * * * cd …/node-executor && node redeem-dw.mjs >> /home/app/redeem-cron.log. Работает — заклеймил $6 (Valorant XLG win) + $5 (Málaga) → USDC.e → wrap pUSD, лузеров чистит. DW: pUSD ~$19, USDC.e $0 (ничего не застряло). Realized total −$16.02 за 38 закрытых (10W/12L/16 zero — наследие старого reconcile-бага, учёт шумный, деньги целы).2c3b207, fuss-free): раньше крон редемил ТОЛЬКО один захардкоженный DW (0x50A8… дефолт) — тестер 0x8b95fb…+новый 0xa1c2… авто-redeem не получали, новые DW игнорились. Фикс: redeem-dw.mjs без env DEPOSIT_WALLET тянет ВСЕ deposit_wallet из data/screener.db (user_wallets) через python3 и цикл processWallet(DW)+try/catch на кошелёк. Env-режим (single-DW) сохранён для DRY-тестов. DRY: «deposit wallets to process: 3». Detach политики НЕ прошёл (privy-detach.mjs APPLY=1 → «No valid authorization signatures» — updateWallet смены сигнеров требует owner-подпись кошелька, наш AUTHORIZATION_KEY валиден только для подписи ордеров/сообщений). Рабочий путь = whitelist DW в app-owned политике (update-policy.mjs, app-secret auth). DW тестера уже в allow-list; 2 других кошелька «open» (без политики) → подписывают свободно. 📌 TODO ПОЛИТИКИ: secure-redeem не сделан, делегат может всё (вкл. вывод) — ок для своих/беты, НЕ при масштабе. Каждый НОВЫЙ policied-DW добавлять в allow-list (update-policy.mjs), иначе redeem застрянет. Scoped-решение → MEMORY «secure auto-redeem».c94c063+1cf8768+9036631): admin-страница UserAnalyticsPage (poly-dev /analytics/...). (1) Полный RU/EN i18n (ua.* ключи в translations.ts) — было захардкожено EN. (2) Бэкенд analytics_users (routes.rs) теперь отдаёт на юзера depositBalance (он-чейн pUSD 0xC011a7E1…+USDC.e 0x2791… баланс DW через ручной eth_call balanceOf к polygon.drpc.org, кэш 60с в AppState dw_balance_cache, фанаут JoinSet), positionsValue (снапшот демона поле value), unrealizedPnl. Totals += totalDeposits/totalPositionsValue. (3) Колонки таблицы юзеров (Rick: ВСЁ видимыми колонками, не тултип): Юзер·Страна·Регистрация·Актив·Подписки·Открыто·Закрыто·На депозите·В позициях·Всего·Оборот·Реализ.·Нереализ.·Общий PnL. KPI: убран Notional, добавлены На депозите+В позициях. ⚠️ ГРАБЛЯ Rust: вся rusqlite-работа в analytics_users обёрнута в блок-выражение, чтобы conn (не Send) дропнулся ДО он-чейн await — просто drop(conn) НЕ помогает, нужен именно scope, иначе handler future не Send. ⚠️ после pm2 restart in-memory copy_pnl пуст → positionsValue=0 пока демон не перешлёт снапшот (~цикл, ~25с). Деплой: cargo -j1 + npm build(tsc-b) + pm2 restart.3ed735d): баг — строки таблицы юзеров брали realized/счётчики из in-memory снапшота демона (держит только последний кусок истории → закрытых 4 вместо 42, realized занижен), а unrealized = positionsValue(снапшот) − cost(БД) по РАЗНЫМ наборам позиций → неверная величина и даже знак (убыток на прибыли). Фикс: каждое поле из авторитета — realizedPnl+closedPositions из полного SQL-леджера; positionsValue+unrealizedPnl+openPositions+depositBalance из живого он-чейна через helper dw_account() (eth_call pUSD+USDC.e + Data API /positions?user=DW: Σ currentValue / Σ(currentValue−initialValue) / count size>0). Снапшот pnl_agg убран из analytics_users совсем. Кэш dw_balance_cache теперь (ts,cash,posValue,unrealized,openCount). БОНУС: positionsValue больше НЕ обнуляется после pm2 restart (он-чейн, не очищаемый in-memory снапшот). Проверено borodich: −9.35→−3.58, закрытых 4→42.docs/MANUAL_CLOSE_PLAN.md): intent-flag архитектура (сервер НЕ подписывает — только демон Малайзии). Ч1 afa7362: эндпоинт POST /api/copy/positions/{token_id}/close (user-auth/ownership/идемпотент, open|pending→'closing'), миграция close_requested_at, защита 'closing' от клоббера в positions_upsert (CASE). Ч2 233e2d6: демон reconcileUser ветка 'closing'→balanceOf→SELL/cancelManual/manualFlat, причины manual-user/manual-resolved/manual-cancel-pending, ретрай. Ч3 99ab72c: TradePage кнопка Закрыть/Получить+модал(оценка size×curPrice+варнинг)+closingIds+тост, asset в Position; резолв→auto-redeem крон выплачивает. 🐞 БАГ+ФИКС 3cf70c9: loadLedger() только на старте → 'closing' после бута не виден → вис вечно; фикс pullCloseIntents() в reconcileAll фетчит ?status=closing. ⚠️ латентность закрытия = reconcile-цикл ~2 мин (RECONCILE_MS=120000). Проверено вживую: Trump +$1.50, Australia −$0.05 (closed/manual-user). 100¢-позиции: продажа теряет спред vs додержать до резолва ($1.00) — закрывать ради слота/капитала, не профита.0x23222038/0x46992d0e/0x9e807759), коридор 60→95 у 4 прибыльных-заглушённых (0x0346af/0x9b491485/0x1eaf5d5f/0x4e8d5fd8). (4) Метод отбора прибыльных китов: per-whale ROI на РЕЗОЛВНУТЫХ ДЕШЁВЫХ входах ≤0.6 (НЕ общий backtest_pf — тот по всей истории вкл.дорогие искажает), фильтр WR≥50%/≥40 сделок/last_seen свежий → отсев лотерейщиков. Подписал на 8 новых (7 new + реактив 0x6db568e6): 44c1dfe4/35bbbad2/6db568e6/df17f4a8/9f41b736/29b52d98/4f1af091/e2349595(295% — ФЛАГ проверить). Коридор 60¢. Активных 29 (⚠️ MAX_OPEN~15 на демоне < 29 → конкуренция за слоты, норма). Запрос на новых китов в UI: фильтры Ср.цена входа≤55¢ + PF≥1.3.bdeb778, ФРОНТ-ОНЛИ): жалоба тестера «видно только 17 китов». Корень: бэк top-traders отдавал LIMIT 40, фронт убирал копируемых (~30) + .slice(0,30) → ~17. Гейты НЕ виноваты (319 кандидатов). Фикс: useTopTraders(sort, 40→300) (парс limit в routes.rs:3247 без клампа, пул ~337), убран жёсткий slice (cap 100). Фильтры (клиентские, мгновенные, в LeadersPage.tsx, компонент FilterSlider ползунок+числовое поле): Винрейт≥% (по ЧЕСТНОМУ winRatePos, не legacy winRate — врёт), PF≥ (backtestPf), Закрытых поз≥ (closedPositions), Ср.объём сделки≥$ (avgTradeSize, 0-20k), Активных дней≥ (activeDays, 0-30), Realized PnL≥$ (realizedPnlPos, 0-100k), Ср.цена входа≤¢ (МАКСИМУМ-фильтр, avgBuy, 0-95¢), ⭐Только качественные (qualityFlag). Счётчик «Найдено N» + Сбросить. Персист в localStorage['sz_leaders_filters'] (все фильтры+sort, lazy-init useState + useEffect-save). Карточка теперь кажет honest WR (wrOf=winRatePos??winRate). ⚠️ нужные поля (winRatePos/closedPositions/realizedPnlPos) добавлены в Trader интерфейс useTopTraders.ts (бэк их слал, TS не объявлял). Диапазоны слайдеров выверены по распределению БД. avgBuy фильтр = МАКС (0=off, value=потолок¢), не мин как остальные. Build tsc-b OK + pm2 restart + health 200.whale_trades JOIN screener_markets(resolved=1, winning_outcome) по корзинам цены. Winrate растёт монотонно с ценой (25%@0.1 → 89%@0.95), но ROI обратный — эдж в ДЕШЁВЫХ входах: <0.50 ROI +6.8…+134%, 0.50-0.70 ~−1.3% (мёртвая зона), 0.70-0.80 +2.9%, 0.90-0.98 катастрофа −13%. Сводно: <0.60 winrate 46% ROI +9.5%; ≥0.60 winrate 75% ROI −2.4%. Favorite-longshot: дорогой фаворит выигрывает часто, но редкий проигрыш сжирает кучу побед (отриц.перекос) → минус. Подтвердило жалобу Rick «брали дороже 0.6 — пошли минуса, winrate подрос». Применения: (1) дефолт priceMax 0.95→0.60 СДЕЛАН (fbe9a5f, DEFAULT_CONFIG в useCopySubscriptions.ts; новые подписки, существующие Rick правит сам в UI); (2) фильтр ср.цены входа кита — СДЕЛАН; (3) кит, льющий в 0.9+, не должен получать ⭐. Оговорки: по входам без учёта exit/mirror, матчинг исхода по строке (winrate чуть занижен равномерно), фи не учтены (добивают дорогие зоны). Стата Rick (serg5585, 327 закрытых): PnL −$9.44, PF 0.96, профит держат 2 кита (0x0346af +$39.54 / 0x224a89 +$9.17, оба qf=0!), главный активный слив 0x2c3350 −$16.50 (qf=1, малая выборка). ВЫВОД: per-leader backtest PF на малой выборке (2-25 копий) НЕ предсказывает результат, правит дисперсия; ⭐-гейт местами анти-коррелирует. Детали → brain.4500038, deployed) + Today-виджет (ba91bde) + ресёрч тиров: заменил оценку notional×0.5% (завышала ~2.6× в сумме, до ~8× на юзера — множила ДОФИ-эпоху, когда builderFee=$0 до ~10-11 июня) на РЕАЛЬНЫЙ builderFee из CLOB /builder/trades, группировка по maker (=on-chain DW юзера). ⚠️ owner в сделке = UUID api-ключа, НЕ адрес — матчить ТОЛЬКО по maker. Бэк: хелпер fetch_builder_fees_by_maker (пагинация ≤50стр, кэш 120с AppState.builder_fees_cache=(ts,map,grand_total)), analytics_users вешает builderFees+builderTrades/юзер + totalBuilderFees. Фронт: builderFeesOf=u.builderFees, колонка→«Builder Фи», тултип «реальные», UserRow+builderFees/builderTrades, EMPTY_TOTALS+totalBuilderFees. ГРАБЛЯ: HashMap НЕ импортирован вверху routes.rs → std::collections::HashMap фулл-квалифай. Live: Rick $5.01/sergii $1.79/borodichs $1.92 (оценка врала $15.58); итого $8.93 vs $23. Today-виджет (Builder Analytics, фронт-онли): карточка «Today (since 00:00 UTC)» сделки/объём/фи с суточного сброса 00:00 UTC + живой countdown (компонент TodayActivity, setInterval 1с, из загруженных trades). Деплой: cargo -j1(2m53s)+npm build+pm2 restart polymarket-screener.b004ba5, ФРОНТ-ОНЛИ) [УСТАРЕЛО → заменено 19 Jun Variant A выше]: показывает сколько builder-комиссий принёс юзер = notional × 0.5% taker. Считается на КЛИЕНТЕ (notional уже в UserRow) → БЕЗ пересборки Rust/демона. Файлы: UserAnalyticsPage.tsx — const BUILDER_FEE_RATE = 0.005 + builderFeesOf(u)=u.notional*RATE + строка в USER_COLS (builderFees) + USER_HEADERS ({col:'builderFees',label:'ua.colBuilderFees'} после notional) + <td title={t('ua.builderFeesTip')}> после ячейки оборота; translations.ts — RU «Фи (ua.builderFeesTip. Место: между «Оборот» и «Реализ.». Лейбл+тултип = ПРИБЛИЗИТЕЛЬНО: считает только ВХОД (cost), без выходов/отмен/maker → занижает ~вдвое (taker берётся на каждом филле). Build tsc-b OK, pm2 restart polymarket-screener, health 200. 📌 TODO «по-нормальному»: точные builderFee из CLOB /builder/trades агрегировать по deposit-кошельку юзера, либо демон пишет copy_positions.builder_fee при матчинге.MIRROR_EXIT_LIVE=1 — когда кит ВЫХОДИТ, демон зеркалит SELL (разгрузка слотов + забор прибыли). Раньше дефолт off=«safe observe» → копии висели до резолва, слоты застревали (видели exit_seen 270+). Логика close (copy-loop-mu.mjs ~280-300): sellAll по exitPrice−EXIT_SLIPPAGE (clamp 0.01-0.99), банк ТОЛЬКО на филле→mirror-leader-exit, резолв/неторгуемое (invalid token id)→leader-exit-resolved+free slot, отказ→откат+ретрай. 🔧 РЕСТАРТ ДЕМОНА (правило): старт из .cjs НЕЛЬЗЯ (type:module→pm2 копирует в .js→ESM-падение); только direct pm2 delete + pm2 start copy-loop-mu.mjs --name copy-trader-mu --interpreter node --node-args="--experimental-global-webcrypto" с ПОЛНЫМ инлайн-env + pm2 save. SERVICE_TOKEN брать из /proc/$PID/environ живого процесса (не печатать). Текущий env [17 Jun]: LIVE=1 POLL=1 SUBS_URL=…/api/copy/active COPY_FRACTION=0.1 MIN_SHARES=5 MAX_NOTIONAL=5 MAX_OPEN=20 MAX_OPEN_PER_LEADER=2 STATE=/root/poly-exec/copy-state-mu.json MIRROR_EXIT_LIVE=1 (FOLLOWER пустой=ALL). ⚠️ env-значения брать из ЖИВОГО /proc/PID/environ перед рестартом, не из этой строки (могли поменяться). История: MAX_OPEN 8→15→20, MAX_NOTIONAL 3→5. ⚠️ ecosystem.copy-trader-mu.cjs УСТАРЕЛ (8/1/тестер) — не источник правды, живой конфиг в pm2 dump.97f94e8+ae39c74): баг — useLeaderHoldings.ts слал ~20 параллельных запросов к data-api из браузера → Polymarket рейт-лимитил часть → упавшие молча пропускались → ~2 карточки БЕЗ объёма (каждый раз разные). Фикс: новый GET /api/copy/leader-holdings (copytrade.rs leader_holdings_get, user-authed) — tokio JoinSet фанаут по активным китам юзера со СВОЕГО IP (не режется как браузер) + 1 ретрай + кэш 30с/юзер (поле copy_holdings_cache в AppState/main.rs). Отдаёт conditionId→[{address,value,price,size}] (value>0, sort desc, дедуп per-leader keep largest). Хук дёргает 1 эндпоинт вместо 20, тип возврата тот же → TradePage только передаёт getAccessToken. Деплой: cargo -j1(2m32s)+npm build(tsc-b чист)+pm2 restart polymarket-screener. ⚠️ data-api positions ДОСТУПЕН с главного сервера без прокси (как redeem-cron) — фанаут на state.http.positions.json на Малайзии; stop→атомарная правка→restart; бэкап *.bak.costfix-*): ⚠️ демон активно пишет ledger → out-of-band правка на живом = race (нет межпроцессного лока, withPos только in-process) → pm2 stop перед правкой, pm2 restart после (сохраняет env из stopped-состояния). (1) Germany/Curaçao O/U — ложное закрытие: в ledger closed, но on-chain держится ($0.28) → reopened open cost 0.27. (2) Бэкфилл cost для 3 legacy cost=None (restored-сироты, открыты до записи cost): Argentina 1.89, Haiti 0.90, Knicks-84cfff 1.90 — взял из Data API size×avgPrice = initialValue. Все 7 open теперь с cost (P&L посчитается на резолве). Germany после рестарта осталась open (присутствует в Data API → reconcile не закрыл)./home/app/polymarket-screener/data/screener.db (НЕ server-rust/data/screener.db — там whale-таблиц нет). whale_trades(13256)+whale_wallets(1710, cols: total_pnl, win_rate 0–1, top_category, last_seen ISO). Критерии направленного кита: PnL>$5k, WR≥55%, avgBuy 0.25–0.85 (из BUY-сделок, не фаворит-фарм у ~1), крипто<30% (keyword-regex по market_question/slug), active 7д, ≥20 сделок → 12 прошло, 11 уже копируем (валидация: ростер = топ-11; #1 0xf883 $3.46M PnL/80%WR/$19.7M vol). Добавил 2 новых в copy_subscriptions (owner=FOLLOWER DID did:privy:cmq45iuzf00mx0cl2xtt1vxf1, конфиг как у активных allocValue:2,maxPerTrade:5,priceMin/Max:5/95,status:active): 0x2fa0ad4b0d86e392716bd302dd6e4446b88bbe6d ($33.5k/78%WR/avgBuy0.48, ⚠️молчит с 1июн) + 0xde9f7f4e77a1595623ceb58e469f776257ccd43c ($8.7k/57%WR, свежий 9июн). Демон подхватил за ~20с (active leaders updated: 15). SUBS_URL фильтрует по FOLLOWER DID. sqlite3 CLI нет → python3.Цель: «всегда актуальные данные» + читаемость метрик. Все правки в server-rust/src/routes.rs (portfolio_summary, portfolio_activity, helper redeem_pnl_by_market) + клиент client/src/components/portfolio/{PnlDashboard,TradeHistory}.tsx, hooks/usePortfolioHistory.ts, i18n/translations.ts, App.css. Деплой: cd server-rust && cargo build --release (~2мин, бинарь напрямую target/release/polymarket-screener) + cd client && npm run build (tsc -b — ОБЯЗАТЕЛЕН, rolldown НЕ тайпчекает) + pm2 restart polymarket-screener. Тест-кошелёк DW 0x50A8061e…F24Ff, проверка curl localhost:3240/api/portfolio/summary?address=… и …/activity?….
696f5c2): реализованный реплей был стейл (без открытых позиций и зарезолвленных-незаредимленных лузеров). portfolio_summary теперь тянет live mark-to-market из Data API /value?user= (форма [{"value":N}]) на каждый запрос. ⚠️ фетч ДО state.pool.get() — rusqlite Connection !Send, держать через .await нельзя (хендлер станет non-Send). totalPnl = totalSold + totalRedeemed + currentValue − totalBought. /activity рефетчится live. Поля: currentValue, totalPnl, totalRedeemed.724c2f0): на карточке нельзя показывать «Открыто $16» (валовый holdings value) рядом с «Реализованный −$13» — не бьётся с Net −$11. Показывай unrealized = totalPnl − totalRealizedPnl → realized+unrealized=net. i18n pnl.unrealized.c614072): НЕ рисовать кумулятив горизонтальными барами по датам — выглядит как противоречие «Дневному P&L» (один день +зелёный дневной vs −красный кумулятив). Сделано SVG-полилинией: viewBox 0 0 100 40, preserveAspectRatio="none", на line/polyline vectorEffect="non-scaling-stroke" (иначе stroke растягивается), zero-baseline, area-полигон, подпись pnl.cumulative ±$. CSS .pnl-spark*.b60ff67): day-based винрейт обманчив. В replay: каждое закрытие (SELL/REDEEM) с |realized|>0.005 → win(realized>0)/loss. winRate=win/(win+loss), поля closedTrades/winningTrades/losingTrades (winDays/totalDays убраны). i18n pnl.winLossTrades.20c285a): карточка «Комиссии» всегда $0 (комса серверная, юзеру не списывается) → заменена на PF. Replay копит gross_win/gross_loss → avgWin, avgLoss, profitFactor=gross_win/gross_loss (JSON null=∞ когда лоссов нет; зелёный при ≥1). i18n pnl.profitFactor/avgWin/avgLoss, удалён мёртвый pnl.feesPaid.6de696f): Data API /activity для проигрышного погашения: usdcSize=0, size=0, price=0, realizedPnl=null — суммы убытка в строке НЕТ → UI показывал «$0». Helper redeem_pnl_by_market(conn, address) реплеит trade_history (avg-cost, ключ = market_id = conditionId) → realized = payout − cost_basis; portfolio_activity после кеша страницы вписывает realizedPnl в каждую REDEEM-строку по conditionId. Клиент TradeHistory: для REDEEM с realizedPnl<0 показывает −$X классом th-pnl-neg вместо formatVolume(amount)=$0; бейдж .th-redeem-loss перекрашен серый→var(--loss). ⚠️ точность зависит от полноты trade_history — portfolio_summary фетчит все 10×200 страниц при открытии Портфеля/Дохода, так что обычно полно.cd ../client из server-rust = sandbox «Directory boundary violation» (всё в /home/app) → сборки отдельными Bash с абсолютным cd. (2) pnl-kpi-sub уже был в App.css — не дублировать.Ключ: DW 0x50A8061e… = копи-счёт, его P&L = табель PolyCopy. Диагностика «почему минус»: наш winrate 27% vs стейтед-WR китов 72%. Утечки (по логам демона Малайзия + positions.json):
0x2c335066 = −$16.50/6 сделок = 63% убытков. Источник правды per-leader P&L = строка [pnl] в логе демона (pm2 logs copy-trader | grep '\[pnl\]') ИЛИ replay positions.json по realizedPnl где status=closed.resolved-onchain-absent). SELL-ветка handleTrade верна, но ловит выход только если держим токен в момент продажи кита. Sell% китов бимодально: buy-and-hold (0%) vs активные управляющие (25-46%, whale_trades GROUP BY side).Фиксы в node-executor/copy-loop.mjs (канон → rsync Малайзия /root/poly-exec/ → pm2 restart copy-trader; env аддитивно pm2 set copy-trader:VAR val):
LEADER_KILL_USD($8): в BUY-reserve-блоке (withPos) суммирует realized closed-копий кита, ≤−$X → skip. Существующие open досиживают./positions?user=<leader> для китов наших open-поз; кит не держит наш токен → leaderExit. 2-цикловой счётчик leaderExitSeen в леджере (env LEADER_EXIT_CONFIRM) против Data API флака. Phase B unlocked: sellAll @ curPrice*(1−EXIT_SLIPPAGE 0.02), банк при филле / cancel+keep иначе. Гейт MIRROR_EXIT_LIVE (default off=observe+log). Деплой money-path: ВСЕГДА observe-фаза сначала (детект без продаж) → проверить ложные → флаг LIVE.leaderExitSeen переживает рестарт (в леджере) → после флага LIVE позиции уже на 2 → закрылись на 3-м цикле сразу.Ростер (state в copy_subscriptions, main DB data/screener.db): демон читает GET /api/copy/active (WHERE status='active', фильтр FOLLOWER DID), подхват ~30с, лог [poll] N leader(s). Пауза = UPDATE … status='paused'. Профиль кита для отбора: whale_wallets(pnl/wr/last_seen) + whale_trades(avgBuy из BUY-цен, sell%, crypto% regex по market_question/slug). Критерии: PnL>$15k, WR≥58%, avgBuy 0.35–0.78 (главный — фаворит-фарм >0.80 блоу-апит), крипто<30%, ≤4д, ≥30 сделок. sqlite3 CLI нет → python3.
Leaders UI (LeadersPage.tsx): идентиконы (addrHue/addrInitials из адреса) вместо общего 🐋; метка «🟢/🔴 на реале» из GET /api/copy/pnl (демон POSTʼит per-leader на /api/copy/pnl через PNL_URL). ⚠️ снапшот state.copy_pnl IN-MEMORY → обнуляется при рестарте screener, демон перепостит на своём цикле (секунды). i18n leaders.onReal/realPnlTip.
SELL realized в History (cfd2d48): realized_pnl_by_key (обобщён из redeem): REDEEM по conditionId, SELL по id={txHash}-{asset} (несколько продаж/рынок). portfolio_activity вписывает в SELL+REDEEM. Клиент TradeHistory: SELL → realized знаком+цветом, центы для <$1.
Единый утиль client/src/utils/whales.ts (Leaders+MyCopies+WhaleProfile одинаково):
addrHue/addrInitials (идентикон), whaleCodename(label,addr) → Whale #N (хеш), categoryGlyph(cat) (⚽🗳️₿🛢️📈🤖🎬🌡️🐢), whaleStyle(avgBuy,sellRate) (⭐≥0.80 фаворит-фарм / ⚡≥0.25 sell активный / 🎯 0.35-0.80 направленный), whaleHorizon(totalTrades,firstSeen,lastSeen) (⏱️≥4 сделок/день / 📆≥1 / 🐢<1).Whale #N + стиль + статy, НЕ адрес/псевдоним. Реальный identity (address/pseudonym/profile_image) — в whale_wallets (admin-only, миграция db.rs ADD COLUMN, sync ловит из трейдов). Адрес убран из URL: роут /whale (без param), адрес через router state; нет state→Navigate to "/". Все call-sites через navigate('/whale',{state:{address}}). WhaleFeed onNavigate тип → NavigateFunction.top-traders + whale_profile отдают avgBuy(AVG BUY price)/sellRate(SELL share) подзапросами по whale_trades; whale_profile ещё resolvedTrades/totalPnl. /api/copy/pnl (демон POSTʼит) → метка «🟢 на реале» на Leaders + сортировка MyCopies./api/copy/pnl. Стиль lead-sortbar.scan_whales (cron 5 мин, whales.rs) берёт ОБЩУЮ ленту последних сделок → у неоткрытых китов история неполная; полную тянет backfill_whale ТОЛЬКО на открытии профиля (POST /whale/{addr}/backfill). update_win_rates (winrate+pnl всех из resolved-рынков, глоб. UPDATE) гонится каждый скан, но зависит от throttled resolve_whale_markets. Фикс: refresh_tracked_whales(pool,client) в run_whale_scan после scan_whales — бэкфилит копи-ростер (copy_subscriptions, всегда) + ротацию топ-40 по volume (ORDER updated_at ASC), 15/цикл, sleep 400мс. Скан-цикл вырастает ~3с→26с = норма (бэкфилл идёт).m.resolved=1); у молодого кита resolved мало (напр. 4 из 84) → 0% если те в минус. UI показывает «Win Rate (N res.)» чтоб не пугало. Грабля: unused import после рефактора (TS6133) → всегда npm run build.docs/BETA_LAUNCH_PLAN.md)Цель (Rick): ОДИН прод-грейд продукт → внутр. тестирование на РЕАЛЬНОЙ системе → прод ~неделя. НЕ урезанная бета. Прод-гейт = 6 критериев: эдж / security / секреты / мультиюзер / ops / legal. План = 5 фаз + Фаза L (legal).
Фаза 0 — гейт эджа (идёт, фоном): замер profit factor с stop-loss+mirror-exit. Артефакты data/edge-measurement/:
baseline-ledger-2026-06-11.json, README.md, measure.mjs (тянет ledger с Малайзии, окно по openedAt>=1781231224,
считает PF/WR/per-leader). Baseline all-time PF 0.73/WR47%. Гейт: PF>1.0 на ≥30 → продолжаем. ~14-16 Jun → node measure.mjs.
🔑 Правило: НЕ менять торговую логику демона / НЕ применять политику до гейта (иначе замер недостоверен).
Фаза 1 — security = ВАРИАНТ A (строгий, выбрал Rick). Спека docs/PRIVY_POLICY_SPEC.md. Аудит: server-ключ
подписывает 5 путей, вывод средств ТОЛЬКО через deposit-wallet Batch (executeDepositWalletBatch). Эмпирически
(живой Privy API): ❌ политика не заходит в массив Call[] (валидатор давится типом), ✅ матч domain.verifyingContract,
✅ default-DENY. Финал A: политика на SERVER-ДЕЛЕГАТА (addSigners({signerId:kc34moyfw73mo5whwfjqpewy,policyIds})):
1 правило ALLOW eth_signTypedData_v4 WHERE verifyingContract in [exchangeV2 0xE111…996B, negRiskV2 0xe2222…0F59]
Фаза 2 — мультиюзер онбординг (2.1 a-d ✅ задеплоено): клиент деплоил Safe, а копитрейд в DEPOSIT-WALLET → разрыв.
client/src/hooks/useDepositWallet.ts — derive/deploy/approve/withdraw DW (relay.deriveDepositWalletAddress/
deployDepositWallet/executeDepositWalletBatch; ⚠️ методы НЕ в .d.ts → каст as unknown as DwRelay).client/src/utils/dwApprovals.ts — точный DW-набор (ground truth on-chain Rick): pUSD→ExchangeV2(max),
CTF setApprovalForAll→ExchangeV2+NegRisk(0xC5d563). negRiskV2 НЕ нужен. createDwApprovalCalls/checkDwApprovals/
readPusdBalance/createWithdrawCall.PrivyAccount.tsx — онбординг Safe→DW (деривация read-only на mount, идемпотентно для существующих).user_wallets(user_address PK, deposit_wallet); POST /api/copy/wallet (copytrade.rs
register_wallet, authed); active() LEFT JOIN → отдаёт depositWallet демону. Клиент репортит DW после онбординга.useSafeDeployment/useTokenApprovals orphaned (не удалены).Фаза 2 — фундинг (2.5 a-b ✅ задеплоено): Rick выбрал вариант A (крипто). Депозит = показ DW-адреса+копи+«pUSD/Polygon».
Вывод = withdrawDepositWallet transfer-батч подписью ВЛАДЕЛЬЦА EOA (не делегата). i18n dep./wd.. В PrivyAccount.
4.1 risk-дисклеймер ✅ задеплоено: CopyConfigModal гейт первой активации (localStorage polycopy_risk_accepted_v1),
4 пункта (риск/лаг/«не выводим»[A]/контроль юзера)+чекбокс. i18n risk.*. Editing подписки пропускает.
NB: модалка УЖЕ даёт юзеру mirrorExits+drawdownStop (плюс к не-дискреционной структуре).
E2E journey: шаги 1-4,6,7 ✅ работают; шаг 5 (демон мультиюзер 2.2-2.4) + шаг 8 (политика 1.3) — ПОСЛЕ гейта эджа. Legal (docs/LEGAL_RISK.md, отложен но в уме): R1 Polymarket аудитит копитрейд-апы (отзыв builder-кода); R2 adviser-регистрация Канада NI 31-103 (наша дискреция → строить не-дискреционно); R3 гео-обход через Малайзию; R4 не-кастодиал↔security заодно. Нужен реальный юрист перед деньгами. Билдер-фи→$10k: доход=notional×rate/10000 (ОБЪЁМ, не число сделок). Сейчас ~$1/день. $10k@1%=$1M notional. Рычаги: юзеры×размер позиции, ручные сделки скринера, taker 0.5→1%, удержание трафика. → BETA_LAUNCH_PLAN Chunk 4.3.
node data/edge-measurement/measure.mjs (тянет positions.json с Малайзии по SSH, окно openedAt >= MEASUREMENT_START, PF/WR/realized + per-leader breakdown). 13 Jun: 13/30, PF 0.83.data/screener.db (НЕ server-rust/data): таблицы whale_wallets (total_pnl, avg_trade_size, top_category, last_seen — win_rate БЕСПОЛЕЗЕН, хранится долей→0/1) + whale_trades (side/price/amount/market_question → считать avgBuy, %crypto, %fav>0.78 самому). Подписки: copy_subscriptions (user_address/leader_address/config/status).UPDATE copy_subscriptions SET status='paused'/'active' (python3, sqlite3/better-sqlite3 на серваке нет). Демон читает /api/copy/active (фильтр status='active', нужен x-service-token). Открытые позы снятого кита НЕ закрываются — доезжают через mirror-exit (by design).data/edge-measurement/README.md.ended badge (TradePage.tsx): завершён только при pos.redeemable===true, иначе почасовой timeUntil(endDate); окно расчёта → t('time.hr',{n:'<1'}). (commit ee58b7f)archived (useCopySubscriptions.ts union + CopyTradingPage + LeadersPage + translations): «Стоп»→setStatus(addr,'archived') (не remove/delete), Мои копии фильтруют archived, Лидеры бейдж 🟠🕘 leaders.badgeArchived. Бэк НЕ трогали (/api/copy/active уже фильтрует active). (commit ba8f0de)GET /api/screener/events/{event_id}/outcomes (routes.rs, тот же запрос исходов БЕЗ обрезки entry.len()<3, фильтр event_id=?) → попап вместо ссылки на polymarket.com. .ev-more →hooks/useLeaderHoldings.ts фетчит позиции активных лидеров (из useCopySubscriptions, status=active) через Data API (CORS открыт из браузера), индекс по conditionId (несколько лидеров на рынке→крупнейший холд). TradePage карта: 🐋 {whaleCodename(null,addr)} держит {formatVolume(value)}. Анонимно. ⚠️ Вариант C (точный «кит→размер входа в момент копии») = ПОСЛЕ гейта: демон пушит positions(conditionId→leader+leaderSize) в POST /api/copy/pnl (правка торгового демона).WHALE_NICKNAMES map (lowercase addr→кличка) в начале whaleCodename, приоритет над label. Прилагательное+существительное по стилю. Surface везде (Лидеры/Мои копии/карты поз — все юзают whaleCodename). Легко переделать = правка мапы.data/ в .gitignore → edge-measurement + screener.db НЕ в git (изменения = состояние, не код).urllib (403 Forbidden) → curl -H "User-Agent: Mozilla/5.0". Из браузера CORS открыт (fetch напрямую ок).cargo не в PATH сэндбокса → $HOME/.cargo/bin/cargo. Rust release build ~1m50s.whaleCodename(label,addr): для нашего ростера label = fmtAddr (труншутый адрес) → показывался адрес; мапа кличек перебивает.WHALE_NICKNAMES: Record<addr,{ru,en}> в whales.ts; whaleCodename(label,addr,lang) (5 вызовов передают lang из useT). Проверка i18n-паритета: распарсить EN-блок vs RU-блок translations.ts, дифф ключей (было 266=266).useLeaderHoldings отдаёт МАССИВ китов на conditionId (дедуп по адресу=крупнейшая нога, сортировка desc). Карта: «🐋 N в этой сделке» (pf.whalesIn) + список. Консенсус-сигнал.GET /api/screener/market/{id}/whale-entries (JOIN whale_trades×copy_subscriptions active, GROUP BY addr+side, size-weighted price + MIN ts; ключ screener_markets.id=whale_trades.market_id=conditionId). PriceChart (LWC v5): createSeriesMarkers(series,markers) (НЕ series.setMarkers!), markersRef сброс при ребилде. BUY ▲зел belowBar / SELL ▼крас aboveBar, «🐋 кличка цена». ts ISO без Z → +'Z' для UTC, сортировать по time asc.docs/FRIEND_BETA_PLAN.md)🔑 Правило весь сессион: живой node-executor/copy-loop.mjs НЕ трогать (byte-identical, эдж-гейт PF 0.83<1.0); весь мультиюзер = отдельный инстанс copy-trader-mu (copy-loop-mu.mjs); все money-path скрипты DRY по умолчанию; политика A НЕ применяется до гейта. 10 коммитов 0a62a24..8566a8f (pushed).
A. Мультиюзер-демон copy-loop-mu.mjs (форк single-user 1:1, per-user users=Map<addr,{dw,eoa,budget,executor,ledger:Map}>, write-through в copy_positions через API):
pub2, mark refresh). Phase1 на снапшоте/Phase2 патч CURRENT-записи status-guarded.copy_pnl: Value→HashMap<String,Value> keyed by user; pnl_post ключует по userAddress (или ""); pnl_get ?user= с fallback на "" (Rick-глобальный жив). Клиент CopyTradingPage+LeadersPage → ?user=<DID> (user.id из usePrivy).B. Self-serve депозит (USDC→авто-pUSD):
client/.../components/DepositPanel.tsx: warning(Polygon+USDC), QR (qrcode lib + src/qrcode.d.ts), readDepositBalances(dw) в dwApprovals.ts (native USDC+USDC.e+pUSD), live-статус поллинг 20с, мин $20. i18n dep.* → USDC.node-executor/deposit-watch.mjs: луп DW из /api/copy/active (FOLLOWER allowlist), native→USDC.e (Uniswap V3 fee100)→pUSD (Onramp) per-user relay gasless. Главный сервер cron рядом с redeem-dw. Решения Rick: только крипто, авто без кнопки.C. Per-user портфель (0943853): был хардкод TradePage portfolioAddress=DW Rick → утечка. Фикс: authed GET /api/copy/wallet→DW юзера (рядом с POST на том же роуте); TradePage резолвит per-user. Изоляция подписок уже ок (authed_user→DID, list/upsert/delete скоупят).
E. Privy политика A node-executor/privy-policy.mjs (НЕ исполнен): orders-only (спека §4b — ALLOW только CLOB-домены exchangeV2/negRiskExchangeV2, прочее enclave DENY). create/show/apply, apply за APPLY=1. ⚠️ применять ПОСЛЕ гейта (ломает авто-redeem/wrap→юзер-present).
F1. Ops node-executor/health-check-mu.mjs: Telegram-алерт демон-stale / низкий баланс. Cron 15мин.
Грабли: git commit -F heredoc с /api/… → sandbox принимал за cd-таргет → писать в .git/COMMIT_MSG_TMP. qrcode был транзитивно (объявить явно). cargo в $HOME/.cargo/bin. Удаление кода → TS6133 unused → npm run build (tsc -b).
TODO: G1 (дисклеймер гейтит активацию?) + сухой прогон mu LIVE на тест-DW.
G1 (c4bbcc5): аудит risk-дисклеймера. Гейт уже был (CopyConfigModal: risk-экран перед новой подпиской, чекбокс обязателен, confirm disabled={!accepted}, текст risk.p1-p4 en/ru). Дыра мультиюзера: RISK_KEY localStorage был ГЛОБАЛЬНЫЙ → второй тестер на том же браузере наследовал принятие. Фикс: ключ per-DID polycopy_risk_accepted_v1:<did> (usePrivy user.id).
🔴 ИНЦИДЕНТ (Rick: «где стата, зачем сломал»): Портфель показал всё $0. Причина — деплой-рассинхрон Блока C: новый клиент (TradePage дёргает GET /api/copy/wallet) задеплоился, а Rust-бэкенд НЕТ (крутился старый, где роут только POST → GET 405) → depositWallet undefined → portfolioAddress пустой → $0. Деньги/позиции целы on-chain, сломалось только отображение. Диагностика: curl localhost:3240/api/copy/wallet → 405 (не 401) = старый бинарь; user_wallets в data/screener.db (НЕ server-rust/data — там нет таблицы) есть DID+DW Rick; cwd репо-корень, DB_PATH дефолт data/screener.db. Фикс: cargo build (incremental 1.8s = бинарь на диске уже был новый) + pm2 restart polymarket-screener → GET 401 (роут жив). 🔑 УРОК: Блок где меняется и клиент И бэкенд = деплоить ВМЕСТЕ (cargo build + npm build + pm2 restart). Клиент в проде обновляется отдельно — нельзя коммитить серверную правку и не задеплоить бинарь.
Страница Кошелёк (план: Портфель=позиции/P&L, Кошелёк=деньги-в/вывод+настройки; чип=только баланс; не плодить 6-ю вкладку):
f1966a9): новая /wallet (WalletPage.tsx) оборачивает существующий PrivyAccount (НЕ тронут — там уже депозит/вывод/setup/delegate). MorePage: убран inline PrivyAccount → пункт меню «👛 Wallet» сверху. i18n nav.wallet.73f9f53): чип TopBar 👤 email→👛 $XX.XX (свободный pUSD, поллинг 30с через GET /api/copy/wallet→readPusdBalance), navigate /more→/wallet. email ушёл в title.31ed960): сводка на Кошельке — Доступно (кэш pUSD) / В позициях (Data API /value?user=dw) / Всего. Поллинг 30с, скрыта пока не резолвится / у юзера без DW. i18n wallet.available/inPositions/total.PrivyAccount и бэкенд не тронуты. Каждый чанк: npm build (tsc -b) + pm2 restart (деплой клиента = restart, index.html вмораживается при старте). Грабля: >/dev/null в Bash триггерит sandbox cd-детект → не использовать редирект в pm2-командах.Контекст: Rick гонял по скриншотам страницу «User Analytics» (/analytics/users, admin-only по isAdminEmail, серверный хендлер analytics_users в routes.rs, клиент UserAnalyticsPage.tsx + хук useUserAnalytics.ts). Серия мелких правок по UX.
Деплой-факт сессии: бинарь Rust часто УЖЕ собран (mtime новее исходников) — cargo «Finished» за ~1с = пересборки нет, это норм. Клиентские правки = npm run build + pm2 restart (index.html вмораживается). Серверные = cargo build + restart. Снапшот copy_pnl in-memory → после рестарта пустой ~20-30с пока демон не запушит.
f40bb3c email как ID: колонка User = email сверху + адрес мелким снизу (fallback на адрес если email нет). Email Privy-юзера ловится на сервере: клиент PrivyAccount.tsx registerWallet шлёт email: user.email.address → POST /api/copy/wallet → users.email (COALESCE, валидация @). Флаг страны: белый 🏳️ fallback заменён на приглушённый — (флаги работают через regional indicators, но у старых SIWE-юзеров country=null). Privy-юзеры (DID-auth, минуют SIWE) теперь апсёртятся в users при register_wallet + штамп last_login + geo по IP (ip-api.com, 1 раз в фоне) — иначе не попадали в аналитику.45067f2 обогащение лидеров: таблица «Most-Copied Leaders» была голая (имя+подписчики). Запрос джойнит whale_wallets: pseudonym (клички типа Faint-Jug, есть у 1545/3342), win_rate, total_pnl, total_volume, total_trades, top_category, last_seen. UI: имя = pseudonym || label("Whale #N") || адрес; +5 колонок Win%/PnL/Volume/Trades/Category. fmtUsd теперь форматит минусы, +fmtPct.de48647 сортировка по клику (client-only): обе таблицы. Дженерик sortRows (null/empty всегда вниз) + переиспользуемый SortTh (▲/▼/↕, числовые→desc первый клик, текст→asc). Колонки заданы массивами LEADER_HEADERS/USER_HEADERS + accessor-мапы LEADER_COLS/USER_COLS. useState/useMemo ДО early-return if(!isAdmin) (правила хуков). Дефолт: киты по subs↓, юзеры по joined↓.92b7bb9 статы юзера из снапшота демона (server-only): copy_positions ПУСТАЯ (мульти-юзер демон не запущен) → Open/Closed/Notional/Realized брались как 0. Фикс: аналитика читает live-снапшот state.copy_pnl (его пушит живой single-user copy-loop через POST /api/copy/pnl, поля на кита: openCount/closedCount/realized/value). Суммирую по китам → per-user. Легаси single-user пушит под ключом "" → пока total_users==1 привязываю "" к единственному юзеру; мульти-юзер демон пушит per-address (новые стартуют с нуля). Totals KPI = сумма строк. 🔑 ГРАБЛЯ Send: rusqlite conn !Send — НЕЛЬЗЯ держать через .await. Снапшот читать state.copy_pnl.read().await в самом НАЧАЛЕ функции, ДО state.pool.get(), агрегировать в owned HashMap, потом синхронная работа с conn. Иначе Axum: «future cannot be sent between threads».did:privy:cmq45iuzf00mx0cl2xtt1vxf1, EOA 0xe2d8…3d3f, DW 0x50a8…24ff, 11 подписок) в users → email=serg5585@gmail.com (→авто-админ, уже в admins.ts) + country=CA. Был не виден (регистрировался до фикса DID-апсёрта). Удалены 2 пустых SIWE-ряда (0x4c91…dac7, 0x47a9…c5df — не везербот: его EOA 0x1aec…22d1/proxy 0x3643…728c1a в скринер не заходили). sqlite3 CLI на сервере НЕТ → правки через python3 stdlib sqlite3 (busy_timeout=15000, tmp+commit).JWT_SECRET (ops): был не задан → сессии сбрасывались на рестарте. pm2 set polymarket-screener:JWT_SECRET <openssl rand -hex 32> + restart + pm2 save. Хранится в ~/.pm2/module_conf.json (не git), как TG_BOT_TOKEN/TRADE_PROXY.Главное: прошли ВЕСЬ путь нового юзера на реальном 2-м аккаунте (email sergiizapolskyi, DID cmqel7uqa022w0cjj4wbvbw9b, DW 0x8b95…d3cb, EOA 0xc4b7…4141): регистрация→активация кошелька→депозит $10 USDC→авто-конверт pUSD→подписка→LIVE-копия исполнилась (~$2.80 «Spain», потом ещё). Изоляция данных подтверждена.
🐛 5 багов пойманы+починены (все коммиты pushed master):
5d83f2c Activate wallet падал «deriveDepositWalletAddress is not a function» — клиент был на @polymarket/builder-relayer-client@0.0.6 (только Safe-методы), а deposit-wallet методы есть с 0.0.10 (node-executor уже на ней). Бамп клиента→0.0.10 (npm i --legacy-peer-deps из-за Solana peer-конфликта Privy). Старые юзеры не падали (их DW делался серверным deploy-dw.mjs).1aea516 Утечка приватности: новый юзер видел PnL Rick'а на My Copies/аналитике — pnl_get+analytics_users падали на глобальный "" снапшот (его пушит живой single-user демон). Фикс: "" отдаётся ТОЛЬКО LEGACY_PNL_USER (env=DID Rick cmq45…, задан pm2 set), остальные — строго своё. Анонимы (без ?user=) ещё видят "" для Leaders social-proof.7d273e9 deposit-watch «No wallet account found» — подписывал lowercase-адресом, Privy ждёт checksummed (copy-exec так и делает). Фикс: резолвить canonical-адрес через Privy getUser перед relayer-batch.10c2390 copy-exec «2 Privy wallets ambiguous» — на Малайзии старый copy-exec.mjs (last-wins, без opts.eoa). Дал mu-демону ИЗОЛИРОВАННЫЙ copy-exec-mu.mjs (текущая версия с opts.eoa/USER_EOA), живой copy-loop.mjs+его старый copy-exec НЕ тронуты.79e555d кнопка «Accept & start» дисклеймера выглядела активной без галочки (была disabled функционально, но без .ccm-save:disabled стиля) + f5d9d18 дефолты копи Fixed $2/$5/$40 + 86f0f2e кошелёк свернул депозит/вывод в кнопки.⚙️ Автономка (всё крутится без меня, reboot-safe):
copy-trader-mu (Malaysia, id меняется): LIVE + MIRROR_EXIT_LIVE=1 (копия закрывается при выходе кита). Запуск НАПРЯМУЮ (не ecosystem — .cjs грабля): pm2 start copy-loop-mu.mjs --name copy-trader-mu --interpreter node --node-args="--experimental-global-webcrypto" + инлайн env (SERVICE_TOKEN, SUBS_URL, FOLLOWER=tester DID, LIVE=1, MIRROR_EXIT_LIVE=1, POLL=1, MAX_NOTIONAL=3, MAX_OPEN=8, STATE=/root/poly-exec/copy-state-mu.json). pm2 save. Allow-list FOLLOWER обязателен — нельзя FOLLOWER=ALL пока живой copy-trader обслуживает основной аккаунт Rick (cmq45/DW 0x50a8) → двойное копирование.run-deposit-watch.sh, flock, SERVICE_TOKEN из .env.privy COPY_SERVICE_TOKEN). Главный, не Малайзия — нужен localhost:3240/api/polymarket/sign + релеер. Разовый скрипт (native→USDC.e Uniswap fee=100→pUSD Onramp).run-health-check.sh, STATE=/root/poly-exec/copy-state-mu.json, STALE_MIN=90 LOW_USD=2). Разовый. TG-алерты ждут creds в /root/poly-exec/.env.health (TG_BOT_TOKEN+TG_CHAT_ID пусты → пока console/file). pm2-startup: Малайзия pm2-root.service + главный pm2-app.service — оба enabled.🎯 EDGE GATE PASSED (data/edge-measurement/measure.mjs): 81 закрытых копий, PF 1.30, realized +$21.17, winrate 30% (выигрыши крупнее проигрышей). Старт был 0.83. ✅ → разблокированы: п.1 USER_EOA живому, п.2 Privy-политика A, миграция на mu. Инсайт: PF держат 2-3 кита (0x0346af+$17, 0x224a89+$12), а 0x84cfff -$3.96/26 копий — кандидат на вычистку.
🚨 НОВОЕ ПРАВИЛО (CLAUDE.md): НИКОГДА не использовать токен бота Бендера для алертов/задач. Для проектов — отдельный бот от Rick.
Скоуп-апдейт friend-beta: друг может регаться (путь чист). Его копии стартуют только после добавления его DID в FOLLOWER (1 команда). Депозит держать малым ($5-10) пока политика A не навешена.
Чанк 1 ✅ (USER_EOA, живой демон безопасен): old copy-exec.mjs last-wins выбор кошелька → с 2 Privy-кошельками при рестарте мог подписать НЕ тем ключом. Фикс: задеплоил текущий copy-exec.mjs (поддерживает USER_EOA/opts.eoa) + задал USER_EOA=0xe2d8…3d3f (Rick EOA) живому copy-trader, рестарт. Проверка: budget=$35.85 (Rick, у тестера было бы $4.65) = правильный кошелёк. 🚨 ГРАБЛЯ повтор: pm2 start ecosystem.copy-trader.cjs снова запустил .cjs как СКРИПТ (имя ecosystem.copy-trader, no-op) → живой демон ~30с лежал. Фикс: ВСЕГДА pm2 start copy-loop.mjs --name copy-trader --interpreter node --node-args="--experimental-global-webcrypto" + ПОЛНЫЙ инлайн-env (LIVE/SUBS_URL/SERVICE_TOKEN/FOLLOWER/USER_EOA/DEPOSIT_WALLET/STATE/POSITIONS/капы). На рестарте --update-env НЕ хватит — SERVICE_TOKEN слетит в ''. pm2 save.
Чанк 2 — Privy-политика A (orders-only), вся подготовка + анализ готовы, остался apply:
29814f7/7028541: L2 CLOB креды кэшируются per-EOA (clob-creds-<eoa>.json, mode 0o600, gitignored) в copy-exec.mjs+copy-exec-mu.mjs. Прежде createOrDeriveApiKey() подписывал ClobAuth на КАЖДОЙ init → под политикой = DENY на рестарте. Теперь derive 1 раз → кэш → reuse. Оба демона перезапущены, креды файлы созданы (tester+Rick EOA).xq797h4uovkjp5k3tybo3sjb (1 rule: ALLOW eth_signTypedData_v4 где domain.verifyingContract ∈ [ExchangeV2 0xE111…, NegRiskExchangeV2 0xe2222…], всё остальное default-DENY). НЕ привязана.additionalSigners[].overridePolicyIds, авторизует OWNER кошелька — серверный updateWallet нашим signer-ключом падает «No valid authorization signatures». Реальный apply = клиентский addSigners({signers:[{signerId, policyIds:[id]}]}) (user-present, owner авторизует). SDK walletApi.updateSigner НЕ существует (был баг в скрипте, починен на updateWallet). (2) Privy policy conditions матчат ТОЛЬКО verifyingContract/chainId, НЕ domain.name (API reject). (3) ClobAuthDomain (деривация кредов) НЕ имеет verifyingContract → нельзя allow-листить → под политикой деривация всегда DENY → креды должны быть до политики. Детали → docs/PRIVY_POLICY_SPEC.md.РЕШЕНИЯ для следующей сессии (Rick склонился):
copy-trader-mu: ✅ возможно. План: импорт 8 открытых позиций Rick из positions.json (Малайзия) → copy_positions (ключ DID cmq45…), маппинг tokenID/leader/cost/openedAt/markPnl/leaderExitSeen → token_id/leader_address/cost/opened_at/mark_pnl/leader_exit_seen, status=open, side=BUY, size=null (reconcile читает on-chain). Затем DID в mu FOLLOWER → стоп старого copy-trader → mu управляет (reconcile сверяет on-chain balanceOf перед close, не перекупает). Креды Rick уже в кэше. NEXT: «мигрируй на mu» со свежим бюджетом (живые деньги, верификация каждого шага). После — FOLLOWER можно ALL.Все 3 чанка Q1 сделаны+задеплоены+проверены на ОБОИХ кошельках:
96b8496 — copy-loop-mu шлёт per-user credsReady: !!u.executor в PnL-пуше (executor готов = L2-креды закэшированы).e042b97 — pnl_post хранит credsReady в снапшоте, pnl_get отдаёт весь блоб (клиент читает). Проверено: Rick+tester credsReady=True.e042b97+8dd1b41 — PrivyAccount эффект: при walletReady, если credsReady и не привязано (localStorage policy_attached_v2:<did>) → навешивает orders-only политику. 🔑 КЛЮЧЕВОЕ ОТКРЫТИЕ: addSigners НЕ обновляет policyIds у УЖЕ существующего делегата (no-op) → нужно removeSigners({address}) + addSigners({signers:[{signerId, policyIds:[id]}]}). Защитно: если policied-add упал → re-add БЕЗ политики (делегирование никогда не теряется на живом акке). Флаг v1→v2 (v1 проставился на no-op).additionalSigners[].overridePolicyIds=[xq797h4uovkjp5k3tybo3sjb] ✅, делегат kc34… цел, Rick 8 позиций целы, демон без denied-ошибок. Сервер теперь за юзера ТОЛЬКО торгует (Exchange-ордера ALLOW), вывод = DENY. Юзер контролирует через owner-auth.skipWaiting+clientsClaim — новый бандл активируется сразу, но всё равно убедиться что сервер отдаёт новый hash. (2) addSigners resolve ≠ политика применена (no-op для existing) → проверять через getWallet, не доверять resolve. (3) runtime-подтверждение «копия под политикой» = event-driven (следующая сделка кита).ИТОГ пост-гейт: ✅ Чанк1 USER_EOA + ✅ Q2 один демон (copy-trader-mu оба акка) + ✅ Q1 политика A. Осталось 🔲 Чанк3 чистка убыточных китов (0x84cfff -$3.96/26, 0xcf6c54 -$5, 0x492442 -$4.65).
Контекст: orders-only политика (Q1) сломала авто-редем — redeem-dw cron падал policy_violation. Резолвнутые рынки (игры 14 июня) висели «open», капитал заперт.
executeDepositWalletBatch (EIP-712 Batch домен, verifyingContract=сам DW). Политика рубит весь Batch (тот же домен, что и вывод). Спека предупреждала: redeem должен быть user-present.3519b63/bc6ed3e): client-side user-present redeem — dwRedeem.ts (buildRedeemCalls из Data API ?redeemable=true + buildWrapCalls), useDepositWallet.redeemResolved подписывает OWNER юзера (политика делегата не мешает). Авто-эффект на загрузке + ручная кнопка. Грабля: redeem отработал, но WRAP (USDC.e→pUSD) не успел — гонка (баланс USDC.e не «приземлился» к моменту чтения). Повторный клик довреппил.ethereum_typed_data_message НЕ умеет ходить в массив calls[] (API: «Type 'Call[]' not found») → нельзя матчить calls[].target. Top-level поля (wallet/nonce/deadline) не различают. ethereum_calldata — для eth_sendTransaction, не для signTypedData-Batch. Вывод: с gasless-Batch архитектурой нельзя разрешить redeem но запретить вывод — они неотличимы для политики. (Все тест-политики отклонены валидацией, мусора нет.)9c68add/d90f542/4787b59): снять политику с делегата → сервер сам редемит. B-сервер: redeem-шаг добавлен в deposit-watch (мультиюзер cron 3мин, главный сервер) — резолвнутые CTF→USDC.e→pUSD для ВСЕХ юзеров авто. B-клиент: делегат добавляется БЕЗ политики при активации (activateWallet addSigners без policyIds) → cron может редемить.4787b59) — он не нужен (B = добавлять делегата без политики при активации). Rick восстановил делегата кнопкой «Активировать» (addSigners без policyIds, isDelegated=false → сработало). Verify: kc34→[NO POLICY], pUSD $64.53, redeemable=0. 🔑 УРОК: removeSigners+addSigners на КАЖДОЙ загрузке = rate-limit + потеря делегата. Не делать auto-detach; делегат без политики ставить ОДИН раз при активации.kc34→[xq797] (cron-редем заблокирован, ручная кнопка работает; детач отложен, тест-акк).eth_sendTransaction to=CTF (газ на DW), Privy-политика разрешит через ethereum_transaction.to (этот field_source РАБОТАЕТ, в отличие от Batch-calls), вывод (transfer на token) → DENY. Тогда и авто, и «сервер не выводит».Аудит проекта: Rust cargo clippy --all-targets ✅ 0, tsc -b ✅ 0, node-executor node --check ✅ все 29 .mjs валидны. ESLint клиента 18 замечаний (react-hooks v7, react@19) → почищено до 0 (3 чанка 762276f/b38e474/8ecd44f, pushed).
set-state-in-effect → derive из raw-стейта+условие (const x = authenticated ? raw : null) вместо синхронного reset в эффекте; либо setState в async-колбэке/IIFE. (2) reset-on-prop-change → adjust-state-during-render (if (addr !== prev) { setPrev(addr); setPage(0); }). (3) immutability cum += в map → reduce с push в аккумулятор. (4) exhaustive-deps t → добавить в deps (стабилен: useT=useCallback([lang])). (5) only-export-components → вынести context+hook в *Context.ts, провайдер экспортит только компонент.react-hooks/preserve-manual-memoization падает на optional-chaining в deps useCallback ([..., user?.email?.address]) → «memoization could not be preserved». Fix: вынести в плоскую переменную перед хуком (const userEmail = user?.email?.address; → dep [..., userEmail]).client/src/hooks/polymarketAuthContext.ts + siweAuthContext.ts (context+hook), провайдеры импортят оттуда.client/, поведение идентичное. Не деплоил (git only).Контекст: «My Copies показывает не всю стату» + поиск свежих китов + старт плана улучшения дискавери.
copy-trader-mu (15 Jun) импортировали только 8 ОТКРЫТЫХ из positions.json (Малайзия /root/poly-exec), а 145 ЗАКРЫТЫХ потерялись → My Copies считал PnL с нуля. Затащил 144 (1 дубль) в copy_positions под DID, маппинг 1-в-1, source_trade_id='backfill-malaysia-20260615' (откат 1 командой). Рестарт демона (loadLedger грузит ВСЕ статусы) → realized +16.08 → +27.44, closed 28 → 174. Бэкап .backfill-tmp/ (НЕ в git). Деньги не трогал (realized уже on-chain в pUSD).TradePage/useLeaderHoldings): строка «🐋 holds $X» берётся ТОЛЬКО из китов, на кого юзер подписан АКТИВНО И кто СЕЙЧАС держит рынок (Вариант B). Нет строки = подписка paused / кит вышел / отписался после копии. НЕ копирует неподписанных — гейт /api/copy/active (WHERE status='active') + leaderIndex фан-аут в демоне. FOLLOWER=ALL = про юзеров, не китов.whales.rs): скан /trades каждые 5 мин (firehose 500 последних, фильтр ≥$1K) → бэкфилл истории кита (/trades?user=) → резолв CLOB /markets/{id} → метрики. Слабо: firehose мелкий (пропускает в пик), per-trade winrate врёт./api/signals/top-traders 3 (Ideal-Copying 0x67bb64…, Elderly-Necklace 0x9b4914…); Sociable-Rooster 0x44c1df… УБРАН — бэктест 30д под наш конфиг PF 0.24 (−42%) несмотря на $85K лайфтайм. Подписки = INSERT в copy_subscriptions (DID + дефолт fixed$2/maxPerTrade5/maxExp40/dd25/mirror/5-95¢); демон ловит через pollSubs (leaders 13→16). Логин-юзер: источник истины = БД (/api/copy/subscriptions), не localStorage.d01c771 B1, 2f651dd B2, pushed): позиционный PnL/honest winrate. 4 поля в whale_wallets (realized_pnl_pos/win_rate_pos/closed_positions/open_positions), функция update_positional_stats (нетто buys/sells по conditionId+payout, только CLOSED=resolved или fully-exited; UPDATE-FROM CTE), вызов после update_win_rates (2 места). Отдаётся в /api/signals/top-traders (realizedPnlPos/winRatePos/closedPositions/openPositions). Чинит артефакт 100% (Happy-Default 100→96%, Mellow-Thermals 100→97%). 🔑 size=shares, amount=size×price (валидировано). Старые per-trade поля оставлены.c0f9aeb E1, 270772b E2, pushed): бэктест «копировали бы нашим конфигом». E1 эндпоинт GET /api/signals/whale/{address}/backtest?days&size&pmin&pmax → {copies,winRate,pnl,pf,roi,perDay} (первый BUY на (market,outcome) в band на resolved-рынке за N дней, fixed size, hold-to-resolution; win→size*(1-p)/p, loss→−size; ROW_NUMBER окно; pf=null=без лоссов). Совпал с ручным: Ideal 1.62/Sociable 0.24/Elderly 1.64. E2 UI хук useBacktests(addresses) (Promise.allSettled, key=sorted-join) + бейдж 🎯 PF на карточках LeadersPage (зел≥1.2/жёлт≥1/красн<1, тултип ru/en). Деплой клиента = npm run build(tsc-b+vite)+pm2 restart (index вмораживается). 🔑 wt.timestamp=unix int → CAST(strftime('%s','now') AS INTEGER)-days*86400.quality_score/quality_flag + бейдж + сортировка), F (TG-уведомления — отдельный бот/токен). План → docs/WHALE_DISCOVERY_PLAN.md.59ee24b+9c96e78, pushed+deployed): Жалоба Rick — (1) equity-кривая на профиле кита строилась из массива trades ТЕКУЩЕЙ страницы (50) → менялась при перелистывании, без осей; (2) бейдж 🎯 PF в списке «с 5 сделок», внутри другая картина. Фикс 1 (график): whale_profile (routes.rs equity = ВСЕ resolved BUY за всю историю (no pagination, win→size*(1-p)/p, loss→−amount, кумулятив), хук useWhaleProfile прокидывает equity[], новый EquityChart (WhaleProfile.tsx) с осью денег ($ max/0/min) + осью времени (даты start/mid/end), 140px (App.css). Старый EquitySparkline (постранично, 56px, без осей) выпилен. Фикс 2 (PF-гейт): MIN_GREEN_COPIES=20 в LeadersPage.tsx — зелёный только при PF≥1.2 И копий≥20; прибыльный-но-тонкий/borderline/∞-на-малой-выборке → жёлтый; PF<1.0 → красный. Тултип: «мало копий (<20)». Решения Rick: п.1 (учёт открытых позиций в PF) НЕ делать — риск ломать; бейдж PF внутрь кита НЕ дублировать. Контекст PF: backtest copies = воронка ~274 сделок→123 BUY-resolved→49 уник(рынок+исход)→whale_trades всего ~30д глубиной). PF=живой пересчёт по БД (кэша нет), растёт по мере резолва рынков. Граблей нет.Контекст: Rick — «PolyCopy стало медленно грузиться» (скрин: страница кита «Copy leader» висит в скелетоне, карточки не наполняются).
nproc=1 (ОДНО ядро!), load average 3.0, CPU idle 0%. Rust-сервер живой (/health=200) но latency скачет 30мс → 1.2с в зависимости от контеншна. Даже 404-роуты отвечали 1.3-2.4с = подтверждение очереди на ядре. Тормозит ТОЛЬКО веб-морда; демон copy-trader-mu (Malaysia) и торговля не страдают.vastai — кратковременный процесс, всплесками до 38% CPU. Rick: НЕ ТРОГАТЬ — рабочий проект. (2) Фоновые синки самого скринера в одноядерном процессе: gamma-sync 29k маркетов каждые ~2 мин = 5-26с; whale-scan до 70с; периодич. Cron sync failed: Gamma API parse error (Polymarket отдаёт битый ответ). Эти синки = ФИКСИРОВАННАЯ нагрузка, не растёт от числа юзеров, но на 1 ядре душит запросы./swapfile 1ГБ но НЕ активирован./swapfile 2ГБ (swapoff→rm→fallocate -l 2G→chmod 600→mkswap→swapon), добавил в /etc/fstab (/swapfile none swap sw 0 0), vm.swappiness=10 в /etc/sysctl.conf (страховка от OOM, не активное торможение). Активен, переживёт ребут.copy-trader-mu УЖЕ там (7ч аптайм). Ping Ванкувер↔Малайзия = 159мс.docs/MIGRATION_TO_MALAYSIA_PLAN.md — 7 фаз с командами/чеклистом (подготовка→код+сборка→БД→демон localhost→nginx+SSL→DNS cutover→финал+fallback), риски/откат, критерий успеха. Делать ПОЗЖЕ по решению Rick, фаза за фазой с коммитами. ⚠️ Главный риск — двойная БД (у демона свой screener.db, у main API свой; на cutover определить авторитетную = main-API-овую с подписками).ssh root@72.62.247.119:/root/polymarket-screener (Rust server-rust/src, фронт client/src) + демон /root/poly-exec/copy-loop-mu.mjs. Прод poly-dev.szhub.space (3240). Локальный репо /home/app/polymarket-screener (доки + клиент, БЕЗ git remote).npm run build на main-сервере (Node 22 — на Malaysia Node 18, Vite не запустится) → rsync -a --delete client/dist/ + изменённые client/src на Malaysia.cargo build --release НА Malaysia (source $HOME/.cargo/env) до рестарта (running API цел) → pm2 restart polymarket-screener. Сборка ~70с.copy-trader-mu (money-path, реальные деньги) НЕ трогать. Рестарт API его не задевает (всегда проверять restarts=0). Правки демона — только осознанно, с бэкапом, node --check, тихий рестарт, мониторинг логов банка.#0c1c22, accent #22E06A, loss #ff7a7a).data-accent (хук useAccent, пикер в SettingsPage). Profit/loss инвариантны.whale_wallets.pnl_7d (моментум 7д, агрегатор update_win_rates).whale_trades 30→100д; deep backfill 90д (fetch_user_trades 30 страниц/стоп по дате); курсор history_backfilled_at (refresh_tracked_whales топ-300+копируемые, 25/цикл).GET /api/copy/market-resolution (service-token) + realizedOnClose в демоне (won?size-cost:-cost на резолв-закрытиях) + read-time в analytics_users. Спека redesign/DAEMON_REALIZED_FIX.md.closed (resolved/untradeable) … pnl≈$X).docs/BETA_LAUNCH_PLAN.md, FRIEND_BETA_PLAN.md, ROADMAP_V2.md на Malaysia.docs/COMPETITOR_ANALYSIS.md §7-11 + инфографика docs/competitor-infographic.png (= kb.szhub.space/competitor-infographic.png). Roadmap фич P0-P3 в доке. Ключ: прайсинг-спред $0(Olympus/Stand)→$499/мес(Poly Syncer) → валидировано место под премиум-тир; белый океан = честная цена входа фолловера (никто не делает), полный замкнутый стек discovery→score→copy→честность, RU/EN, единая copyability-оценка. Угрозы: консолидация (HashDive→Unusual Whales), конвергенция B↔A (трекеры добавляют copy).copy_positions.entry_price vs leader_price, виден в whale-профиле) и P0.4 (RU/EN i18n) — УЖЕ в коде.LeadersPage.tsx + i18n/translations.ts, локальные коммиты 517f7d1+61b7393, НЕ задеплоено): edgeScore() теперь (1) ужимает luck-prone метрики (WR/PF/realized-PnL) на confidence = closedPositions/25 (малый settled-сэмпл не всплывает в топ; null=полный кредит, без регрессий); (2) добавлены Sharpe-консистентность (вес 0.12) + max-drawdown (0.08), посчитанные client-side из spark-серии (scale-invariant). Tooltip обновлён RU+EN. tsc+eslint чисто. Деплой = при следующем npm run build клиента.CopyConfigModal, дефолт 0.20–0.80 (не фейдить крайние фавориты); выход = зеркалить дефолтом (свои TP/SL позже); тема = Light/Dark Side (Grok-концепт, слоган «Two sides. One market. You choose.», тумблер COPY⇄FADE, акула vs череп, «INVERTED LEADERBOARD»). Мокап docs/counter-concept.html/png. Механика: таргет покупает YES@p → мы покупаем NO@(1-p) (шортов нет). Слои внедрения: backend mode:copy|fade (server-rust/src/copytrade.rs+db) → daemon инверсия стороны (copy-loop-mu.mjs) → UI Fade-вкладка + risk-cap в конфиге → тема/анимация в самом конце (Rick: функционал сначала). ⚠️ Правовое: НЕ использовать марки Star Wars/Jedi/Sith в копирайте, убрать «powered by Polymarket».Что: полный визуальный редизайн фронта в стиль Editorial (serif) + Liquid-Chrome бренд. Утверждён Rick'ом по живому мокапу.
redesign/STYLE_GUIDE.md (палитра, типографика, glow-правило, компоненты, контент-правила YES/NO). Рефы: docs/theme-refs/ref-02.jpeg (editorial), ref-07.jpeg (chrome).client/src/redesign/editorial.css — scoped .pk-*, наследует theme-переменные приложения (--bg-primary/--bg-card/--border/--text-*/--accent/--profit/--loss) → светлая/тёмная + 6 accent-тем работают сами. COPY/FADE = profit/loss (инвариантны к accent).tnum/lnum), UI → Inter. (Перебрали Georgia→Fraunces→Newsreader; цифры JetBrains Mono→Inter.) Подключены в client/index.html. ⚠️ font-переменные --pk-serif/sans/mono определены на ВСЕХ .pk-* контейнерах (не только root/leaders) — иначе var пустой → откат на Inter.client/public/brand/shark.png + skull.png (вырезаны из углов ref-07 через playwright-canvas, без вшитого текста) → аватары COPY/FADE.LeadersPage, заголовок Winners(copy)/Losers(fade) — i18n titleCopy/titleFade, FADE=SOON пока), Whale Profile(хром-герой по центру), Portfolio(TradePage), Copy Config модалка(CopyConfigModal), Wallet, MarketDetail, Copy-subscriptions(CopyTradingPage), More, Settings. Превью-демо: роут /preview (PreviewPage).npm run build на main (Node22) → rsync dist/ → malaysia:/root/polymarket-screener/client/dist/ → pm2 restart polymarket-screener (⚠️ Rust держит SPA-fallback index.html в памяти — рестарт обязателен иначе старый бандл на неизвестных роутах). Демон copy-trader-mu НЕ трогался (restarts=1 весь сессион). Src синкнут на Malaysia, закоммичен там (13a76bf). Локальный main-репо: 14 коммитов (нет git remote).serviceWorkers:'block', set sz_theme/sz_accent в localStorage) → скриншоты на kb.szhub.space/polikopi-*.png. ⚠️ PWA service-worker кэширует — Rick'у чистить кэш кнопкой/инкогнито.PrivyAccount.tsx:145 авто-редем просит подпись на каждом mount (money-path, см. STYLE_GUIDE §TODO, session-guard). (2) FADE-функционал = демон chunk B (спека redesign/COUNTER_TRADING_CHUNK_B.md, НЕ задеплоен). (3) хедер-вордмарк «Поликопи» опц.Доделаны 3 TODO. Всё на проде poly-dev.szhub.space / Malaysia. Демон copy-trader-mu не пострадал (copy-юзеры торгуют норм).
6555979): PrivyAccount.tsx авто-редем (redeemResolved useEffect) → session-guard (sessionStorage sz_redeem_tried_<dw>, ставится ДО вызова) → подпись просится ≤1 раз/сессию, не на каждой навигации. Перезагрузка = повтор..pk-wordmark в TopBar.tsx + editorial.css).6e5994f, Malaysia e7563ed): copy_positions + колонки direction(default 'copy')/leader_asset; _impl/db.rs миграция (идемпот.+бэкфилл 4400 строк→copy, leader_asset=token_id); _impl/copytrade.rs positions_get/upsert отдают/пишут поля. Backend ТОЛЬКО на Malaysia (server-rust/src/, нет локально); _impl/*.rs = миррор (md5 совпадал), деплой = rsync _impl/db.rs+_impl/copytrade.rs → Malaysia server-rust/src/ → cargo build --release -j2 (1m12s) → pm2 restart polymarket-screener.ec51ece): демон copy-loop-mu.mjs инверсия. ВСЕ fade-пути гейтятся cfg.direction==='fade' → copy-путь байт-в-байт прежний (проверено live). 4 правки: deriveCfg(+direction/fadeMin 0.20/fadeMax 0.80 + resolveComplement() через chunk-A /api/copy/market-tokens, MARKET_TOKENS_URL из SUBS_URL); BUY инвертит на комплемент NO@(1-p), risk-cap+binary-only, пишет direction/leaderAsset; SELL зеркальный выход по leaderAsset→продаёт наш токен @(1-price); reconcile FIX (сравнивает leaderAsset, не token_id — иначе ложно закрывал бы fade каждый цикл; no-op для copy). Источник: _impl/copy-loop-mu.mjs. Деплой демона: бэкап ~/openclaw-backups/copy-loop-mu-pre-chunkB-2026-06-23.mjs → scp → pm2 restart copy-trader-mu (env сохранился, БЕЗ --update-env). MARKET_TOKENS_URL не нужен в env (fallback из SUBS_URL).1587787, Malaysia 15bdce8): CopyConfig +direction/fadeMin/fadeMax (optional, back-compat); CopyConfigModal секция Mode COPY/FADE (fade=красный) + risk-band ¢ + объяснение, двуязычно через lang. FADE пишет в config подписки → демон фейдит.redesign/STYLE_GUIDE.md §TODO. Leaders верхний тумблер Winners⇄Losers пока статичный (нужен инвертир. лидерборд).Активировал верхний Leaders-тумблер COPY⇄FADE (был «SOON») и отладил FADE-«Losers»-доску по фидбеку Rick'а. Все правки — фронт, демон не тронут.
b398e2e): COPY=🐋Winners / FADE=💀Losers. Тап карточки в fade → профиль → CopyConfigModal сразу direction='fade' (через router state).18f8305+2abd2dc):closedPositions>=10 && (realizedPnlPos ?? totalPnl) < 0. Иначе вылазили «100% WR на 2 сделках» / плюсовые киты. (Высокий WR но минусовой P&L = валидный fade-таргет: выигрывает по мелочи, проигрывает крупно.)useTopTraders('quality',300) + ВСЯ сортировка на клиенте (metricOf по вкладке) → нет рефетча/лага/мелькания при переключении вкладок и COPY⇄FADE.Date.parse(lastSeen) (свежесть, как на карточке «active 52 min»), НЕ activeDays — иначе порядок выглядел рандомным.boardEmoji), хром-фото-кропы были нечитаемы на 42px. .pk-cav = flex-center, font-size 21..pk-cwr .pk-wrv {display:block} + min-width → «50%» и «WIN-RATE» не налезают (стек).dd9773e, SettingsPage.tsx clearSW): разрегистр SW → чистка Cache Storage → reload на ?u=<ts> (cache-bust). Диагностика кэша: сервер ОК — sw.js/index.html/registerSW.js все no-cache,no-store, vite-pwa autoUpdate+skipWaiting+clientsClaim. Залипание = открытая PWA-сессия крутит старый JS до перезагрузки. Проверка свежести: FADE «Found: 24» (новая) vs «157» (старый кэш). ⚠️ «Clear Local Data» ≠ «Update app» (первая чистит только localStorage)./root/poly-exec/deposit-watch.mjs, запуск pm2 cron */10 мин (status=stopped между тиками = норм, autorestart=false). Логика: ловит native USDC на DW → swap native→USDC.e (Uniswap V3 SwapRouter02 0x68b3...Fc45, fee=100, slippage 0.5%) → wrap USDC.e→pUSD (Onramp 0x9307...B8ee). Газлесс через RelayClient.executeDepositWalletBatch (relayer-v2). Privy-креды из .env.privy, рантайм-env (SUBS_URL/SERVICE_TOKEN) из pm2 демона. DRY=1 = симуляция (дефолт LIVE). Депозит Rick (DW 0x50a8061e) сконвертирован → pUSD $16.walletApi case-sensitive, /api/copy/active отдаёт ownerEoa lowercase → резолвить checksummed из getUsers().linkedAccounts; (2) localhost→127.0.0.1 для builder sign-url — Node резолвит localhost→::1 (IPv6), API бинд только IPv4 → ECONNREFUSED.copy-loop-mu.mjs: эфф. buy-цена px×(1+BUY_SLIPPAGE) не выходит за priceMax (только если band задан, cfg.priceMax<1). maxExposure $-кап НЕ делали (Rick: регулируется числом открытых сделок maxOpen). Деплой: бэкап→патч→node --check→selftest→pm2 restart copy-trader-mu.scp ~/.git-credentials с основного сервера → Malaysia + git config credential.helper store. Прод-ветка на Malaysia = redesign-nav, remote Zserg5585/polymarket-screener. Теперь push работает.brain/audits/polikopi-full-audit-2026-06-24.md. 5 фиксов чанками (build+restart+commit+push после каждого), все на GitHub:7ae0810 — service_ok → const-time service_token_ok (money-эндпоинты демона).8828bc7 — /api/polymarket/sign whitelist: только POST /submit + GET /transactions (единственные, что SDK подписывает — и фронт, и демон); произвольное → 403. Не ставили auth-gate (sign зовётся в онбординге до SIWE-логина).253ff66 — validate_config() server-side в upsert (reject negative/NaN/percent>100/inverted bands) + NumInput clamp на клиенте. Закрыл и M3.4a40242 — partial_cmp().unwrap_or(Ordering::Equal) в gamma.rs (NaN из Gamma API убивал sync-таск).1b90f56 — derive/create-key больше не возвращают secret/passphrase (фронт их не использует — ордера server-side из cred_store).server-rust/src/*.rs на Malaysia → cargo build --release (PATH=/root/.cargo/bin, pm2 restart polymarket-screener → smoke-curl 127.0.0.1:3240. Бэкапы `0x3f3aa торгует non-binary (спорт-команды «A vs B», Up/Down) + крайних фаворитов (No@0.98). FADE требует binary yes/no + цена лидера в band 0.2-0.8. Под это его сделки не попадали → демон молча пропускал (skip fade (non-binary outcome))./api/signals/top-traders?sort=quality&limit=300 (поля: address, avgBuy, realizedPnlPos, totalPnl, winRate, closedPositions, lastSeen, topCategory). Фильтр fade-таргета: realizedPnlPos<0 (лузер) + closedPositions>=15 + avgBuy 0.2-0.8 + lastSeen свежий. Потом проверка реальных сделок через data-api.polymarket.com/trades?user= на долю binary yes/no в band. Топ: Whale#4 0x1eaf5d5f (-80k, 10/20 fadeable), #1768 0xd2f35966 (-117k), #147 0x4be1fa92 (-594k). ⚠️ cat=Sports часто non-binary; «Will X win» — binary, «A vs B» — non-binary; проверять сделки, не категорию.apt install sqlite3; бэкап ~/openclaw-backups/screener.db.pre-fade-subs-*): схема copy_subscriptions(user_address, leader_address, config, status). INSERT #147; #4 и #1768 уже были COPY (Rick копировал лузеров!) → UPDATE config на fade. ⚠️ Для FADE ставить priceMin:1, priceMax:99 — иначе copy-band priceMin/priceMax (применяется ДО fade-band в demon) сужает диапазон. Первая fade-позиция открылась (#147, Yes@0.76, $3.82, pending).positionDirections): эндпоинт position-leaders теперь отдаёт {success, data:{cid:leaderStr}, directions:{cid:dir}}; TradePage карточка + TradeHistory показывают 💀 FADE по directions. copy_positions.direction уже отдаётся в /positions payload.053c587) сломал прод — изменил формат data value со строки на объект {leader,direction} → старый кэшированный PWA-фронт упал (e.address.toLowerCase is not a function на объекте). Фикс fcbf015: back-compat — data снова строки, direction в ОТДЕЛЬНОМ поле directions. ПРАВИЛО: для PWA НЕЛЬЗЯ менять формат API ломающе (кэшированные клиенты падают) — только ДОБАВЛЯТЬ поля. Деплоить Rust-фикс ПЕРВЫМ (чинит старый кэш сразу), потом фронт.pending 57 мин (on-chain 0, нет last_order_id), reconcile сам НЕ убрал. Вероятная причина — демон не смог записать статус через Rust API во время моих 6 рестартов (API down ~5с). Проверить: (1) reconcile pending-timeout реально срабатывает? (2) не копятся ли зомби-pending при рестартах API? (3) нужен ли демону busy_timeout/retry на saveLedger при недоступном API. Очищен вручную UPDATE (бэкап screener.db.pre-zombie-cleanup-2026-06-24).0x1eaf5d5f и #1768 0xd2f35966 Rick попросил вернуть с fade на copy — ПОТОМУ ЧТО его copy-P&L на них был ПЛЮСОВОЙ (+$2.30, +$2.05) несмотря на общий минус кита. Урок: fade-таргет выбирать не только по общему P&L кита, но и сверять с реальным copy-P&L юзера — если copy уже в плюсе, не ломать. Оригинальные config восстановлены из бэкапа. На fade остался только #147 (чистый лузер, не копировался). Бэкап screener.db.pre-revert-copy-2026-06-24.database is locked (Rust API держит) → нужен sqlite3 heredoc с .timeout 8000 (busy_timeout). Бэкап перед каждой БД-правкой.screener.db.pre-fade-subs. Урок: fade-таргет сверять не только с общим P&L кита, но и с РЕАЛЬНЫМ copy-P&L юзера по этому киту (SUM realized_pnl/mark_pnl из copy_positions GROUP BY leader). Работающий copy не ломать./api/signals/top-traders?sort=quality&limit=400 → исключить ВСЕ подписки юзера (copy_subscriptions) → фильтр realizedPnlPos<0 && closedPositions>=25 && avgBuy 0.25-0.72 && lastSeen<3дн → композит-скор (по 1 баллу: realized<0, pnl7d<0, winRatePos<0.5, backtestPf<1, totalPnl<0) → для топа проверить РЕАЛЬНЫЕ сделки через data-api /trades?user= на долю binary yes/no в band 0.2-0.8. Идеальный fade = высокий скор (консистентный лузер по всем метрикам) + высокая доля fadeable binary.0x2a696600 (score 5/5: realized -22k, 7d -16k активно теряет, WR44%/WRpos43%, PF0.85, 11/25 binary) + 0xfe787d2d (-128k, PF0.87, 8/25 binary). Config fade priceMax99. Бэкап screener.db.pre-fade2-2026-06-24.position-leaders+leader-holdings грузили только ACTIVE-подписки → позиции от archived/paused лидеров без суммы → union с лидерами открытых позиций (660…-ряд коммитов). (2) leader-holdings брал currentValue (=0 для resolved/redeemable) → перешёл на initialValue (entry-ставка кита, переживает резолв) + фильтр по нему. Коммиты 8d84575(union)+245806d(initialValue).053c587 сменил формат position-leaders value (строка→объект) → старый кэш PWA упал (e.address.toLowerCase). Фикс fcbf015: data остаётся строками + directions ОТДЕЛЬНЫМ полем. ПРАВИЛО подтверждено: для PWA только ДОБАВЛЯТЬ поля.copy-loop-mu.mjs): демон поллил только active leaders → выходы archived-китов не зеркалились (позиции висели до резолва). Фикс: pollSubs добавляет exit-only roster (лидеры открытых позиций из ledger, {...deriveCfg({}), exitOnly:true}); handleTradeForUser BUY if(cfg.exitOnly)return (только выход, не вход). + поменял порядок loadLedger→pollSubs в main (иначе ledger пуст на 1м pollSubs). Бэкап copy-loop-mu-pre-exitmirror.redeem-dw.mjs): не запускается на cron (в отличие от deposit-watch) → выигрыши висели. 2 бага в скрипте починены: localhost:3240→127.0.0.1 (IPv6 ::1, как в deposit-watch) + добавлен USER_EOA (last-wins подписал бы чужим). Запуск: DEPOSIT_WALLET=<dw> USER_EOA=<eoa> node redeem-dw.mjs. TODO: redeem-dw на pm2 cron для авто-claim.positions_upsert падал с database is locked → 500 → демон НЕ мог сохранить статусы ([ledger] save http 500) → reconcile закрывал в памяти, запись терялась → pending застревали НАВСЕГДА (копились 9-10 дней у всех акков). Причина: SQLite busy_timeout НЕ задан на pool-соединениях (WAL-pragma только на 1м conn; busy_timeout per-connection). Фикс 660ea54: SqliteConnectionManager::with_init(|c| busy_timeout=5000) — каждое соединение ждёт лок. Это же объясняло почему reconcile pending-timeout "не работал". Очищено вручную 29 зомби (Rick 8 + тестер1 8 + тестер2 13), все on-chain 0..timeout 8000 обязателен. busy_timeout-фикс это смягчает и для CLI.copy-loop-mu.mjs+copy-exec-mu.mjs. 3 фикса → боевой copy-trader-mu (Malaysia /root/poly-exec, LIVE/POLL/MIRROR_EXIT_LIVE=1):openedAt=ВРЕМЯ СДЕЛКИ ЛИДЕРА (в POLL на минуты старше из-за лага Data API) → отменял свежие ордера до фила → orphan-фил без ledger-записи. Фикс: поле reservedAt=wall-clock резерва, timeout от него (бэкомпат || openedAt). Это та же причина, по которой казалось что "pending-timeout не работает".cost по лидерской цене, executor берёт price×(1+BUY_SLIPPAGE) → realizedOnClose=size−cost завышал. Фикс: cost=size×min(0.99, price×(1+BUY_SLIPPAGE)).await resolveComplement=единственный yield). Фикс: per-user fadeInflight Set по conditionId через await./root/poly-exec/ (cwd процесса copy-trader-mu, pm2 id 45). НЕ в git этого сервера, НЕ в _impl (там отставшая на 49 строк копия). Вторая копия /root/polymarket-screener/node-executor/ НЕ используется демоном. Перед патчем ВСЕГДА diff снапшот↔/root/poly-exec (был байт-в-байт идентичен).*.bak-YYYYmmdd-HHMM → залить .new → node --check под .mjs-именем → atomic mv → pm2 restart copy-trader-mu → pm2 logs --nostream (проверить exec=ok, ledger загружен, [poll] идёт). Откат = cp bak обратно + restart.positions_upsert: database is locked в Rust API → демон [ledger] save http 500 → ❌ reserve write failed — aborting copy → ордер НЕ ставится (15× за окно логов). Незакрытый хвост зомби-бага 25 Jun. busy_timeout=5000 недостаточен: cron-писатели (Whale scan, gamma deactivate, update stats) держат write-txn 19–41 секунды (SQLite WAL = один писатель), positions_upsert ждёт 5с → проигрывает → 500. Фиксы (Rust API, pm2 id 42 на Malaysia, НЕ демон): #1 busy_timeout→30–60с (быстрый митигейт, но 41с-txn может перекрыть); #2 укоротить cron write-txn (батч-коммиты, тяжёлое вне транзакции); #3 ⭐ вынести money-таблицы (copy_positions) в ОТДЕЛЬНЫЙ .db-файл — WAL/lock per-db, cron-писатели физически не блокируют money-path. Rick склонялся к #1+#3, ждёт go.pm2 logs copy-trader-mu --out --nostream | grep "save http 500" (частота дропов) + pm2 logs polymarket-screener --out --nostream | grep -iE "locked|Whale scan" (длина cron-txn). Источник истины проблемы — длительность (NNNNNms) у Whale scan.deposit-watch на MALAYSIA (/root/poly-exec/deposit-watch.mjs). НЕ на главном сервере. Одноразовый скрипт (main() один проход → exit 0), pm2 гоняет его по cron_restart */10 (каждые 10 мин). status=«stopped» между прогонами = НОРМА (не крэш): уптайм/рестарты не растут, exit_code 0. Не пугаться и не «чинить» рестартом.GET /api/copy/wallets) детектит входящий нативный USDC и авто-конвертит в pUSD. Цепочка: native USDC 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 → USDC.e 0x2791Bca1…84174 (Uniswap V3 fee=100, SwapRouter02 0x68b3465833…) → pUSD 0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB (Onramp 0x93070a847… wrap()). Gasless через relayer-v2.polymarket.com (executeDepositWalletBatch, DW без MATIC), подпись per-user Privy embedded EOA.SUBS_URL=http://127.0.0.1:3240/api/copy/active (берёт /wallets заменой /active→/wallets), SERVICE_TOKEN (=COPY_SERVICE_TOKEN API), MIN_CONVERT_USD=1, LIVE (без DRY), FOLLOWER пуст=все. builder-sign hardcoded http://127.0.0.1:3240/api/polymarket/sign — отдаёт сам Rust API (id42, порт 3240), отдельного :3240-сервиса нет. ⚠️ именно 127.0.0.1, не localhost (Node резолвит localhost→::1, API биндит только IPv4 → ECONNREFUSED).ssh malaysia → cd /root/poly-exec → SUBS_URL=… SERVICE_TOKEN=$(pm2 env 47|grep SERVICE_TOKEN…) DRY=1 node --experimental-global-webcrypto deposit-watch.mjs → ждём «DRY | N wallet(s) / done». Логи: /root/.pm2/logs/deposit-watch-out.log (ищи [swap] CONFIRMED/[wrap] CONFIRMED/pUSD now $…). После правок env: pm2 save.*/30 * * * * /root/poly-exec/redeem-all.sh. node-executor/ БОЛЬШЕ НЕТ (снесена при переезде 21 Jun). Старые croны главного (redeem-dw.mjs */15, run-deposit-watch.sh */3) были мёртвым мусором (cd в несуществующую папку → молча падали) → удалены 29 Jun. Не воссоздавать на главном. Если глядя на главный сервер кажется что «авто-конверт сломан» — смотреть Malaysia.90cd6ce): монтирует <EdgeScanner /> (таблица edge_scores, скорер edge.rs, эндпоинт /api/signals/edge; факторы whale/volume/momentum/spread/price).a0fd248): /api/signals/whales/insider (routes.rs whale_insider+InsiderQuery) — свежие BUY (≤hours, дефолт 24) от КАЧЕСТВЕННЫХ китов quality_flag=1 AND win_rate_pos>=0.5 AND closed_positions>=30; GROUP BY market+address+outcome; JOIN whale_wallets(WR/pnl_7d)+screener_markets(тек.цена). Хук useWhaleInsider, UI InsiderFeed/InsiderCard (whaleCodename + 🎯 WR%, дельта, 🆕 свежесть, «Войти»), фильтр 24/72/168ч. Отличие от Консенсуса: один сильный кит с его WR, не агрегат. 🔲 улучшение: строгий «до консенсуса» (HAVING distinct-whales ≤N).POST /api/copy/positions/{tok}/close 200 46 (46 байт = {"success":true,"status":"closed","noop":true}). Он нажал на РЕЗОЛВНУТЫЙ выигрыш, который в БД уже closed → эндпоинт вернул noop. Портфель UI берёт позиции с on-chain Data API, не фильтрует по БД → резолвнутые/redeemable позиции засоряют список с мёртвой кнопкой.redeem-dw.mjs строил ОДИН атомарный batch из всех redeemable + слал на relayer. В батче мешались CTF и neg-risk позиции. Баг: для neg-risk слалась CTF-сигнатура redeemPositions(collateral,parentId,cid,indexSets) на neg-risk адаптер 0xd91E80cF… → revert → атомарный batch реверотил ЦЕЛИКОМ, вкл. валидный CTF-редим Spread France $13. Лог: batch would revert: execution reverted каждые 30 мин.redeem-dw.mjs, коммит 063bca7):seen → выигрышный «Yes $5» на том же cid пропускался. Worthless только если Σ всех исходов ≤ $0.01.NegRiskAdapter.redeemPositions(bytes32 conditionId, uint256[] amounts) (селектор 0xdbeccb23, проверен в байткоде адаптера; 4-арг 0x01b7037c тоже есть, но реверотит для neg-risk). amounts индексируется по outcomeIndex, значения = точный on-chain CTF.balanceOf(DW, positionId) (6 decimals), НЕ Data-API size (округляет → revert при request>balance).winners_left: 0. pUSD $0.69→$15.59 (демон часть уже раскидал в копии)./root/poly-exec/ (cwd демона, ВНЕ git) и разошлись с git-tracked /root/polymarket-screener/node-executor/ (у последнего core-файлы были устаревшие: copy-loop-mu.mjs отставал на 127 строк — без manual-close/pullCloseIntents; redeem-dw.mjs без neg-risk фикса). poly-exec = истина для рантайма. Синхронизировал живой код → repo/node-executor (rsync ТОЛЬКО *.mjs/*.sh/*.cjs, секреты/стейт НЕ трогал), ужесточил node-executor/.gitignore (добавил .env.privy, cl-state.json, copy-state*.json, positions.json*, *.bak-* — раньше НЕ игнорились = риск утечки Privy-секрета!), коммит 063bca7 → push origin/redesign-nav. Рантайм НЕ тронут, демон не рестартился..env.privy/стейта/node_modules + правку PM2-script-path + кронов (redeem-all.sh, deposit-watch cron_restart, redeem cron). Делать осознанно по процедуре рестарта (direct pm2 start copy-loop-mu.mjs + полный инлайн-env + pm2 save).b9c19bf)/home/app/polymarket-screener (master, tip cecbd3a) висела незакоммиченная работа = зеркало уже задеплоенного на Malaysia: Инсайдер-фронт (useWhaleInsider.ts+FlowPage Insider/Fire табы вместо ComingSoon), B1 pUSD-пречек в EnterTradeModal, dwApprovals.ts ALL_SPENDERS (neg-risk V2 0xe2222 + adapter 0xd91E), миноры (CopyConfigModal/TradeHistory/TradePage/useLeaderHoldings/i18n), docs/ROADMAP_EXECUTION.md. Закоммичено одним коммитом b9c19bf.git remote -v пусто) → push отсюда невозможен. Push/deploy-истина = Malaysia (там эта же работа уже на redesign-nav: a0fd248/960d9d4 и др.). При «сохранись» на главной = только локальный commit; push делается на Malaysia.Проверено на Malaysia (read-only), рестарта денежного демона НЕ было:
copy-trader-mu (pm2 id48) уже запускается из РЕПО: script path = /root/polymarket-screener/node-executor/copy-loop-mu.mjs (== poly-exec байт-в-байт), node_modules 146==146. Крон redeem-all.sh и run-health-check.sh тоже cd repo/node-executor. ⇒ дрейф КОДА закрыт (демон+croны = репо-код)./root/poly-exec (данные/секреты, gitignored, НЕ код): cwd=poly-exec, .env.privy (cwd-relative './.env.privy'), абс. STATE=/root/poly-exec/copy-state-mu.json (live), .env.health, clob-creds-*.json, cl-state.json; deposit-watch (id47, stopped) script+cwd=poly-exec.Карточка WhaleProfile.tsx показывает СТАТУ САМОГО КИТА (реализованную), НЕ «бэктест под наш конфиг» (тот — устаревший глобал-дефолт, остаётся только на LeadersPage). Секции: A хедер / B «Стата кита» (ROI реализ.=Σpnl÷Σinvested, WR, Резолвнуто, P&L) / C объём / D эффективность по цене входа / E поведение / F график P&L (тумблер 7д/30д/90д/Всё) / G топ рынки / H последние сделки / I мои настройки копирования / J авто-рекомендация. Trust-блок в футере.
GET /api/signals/whale/{addr}/price-buckets — резолвнутые BUY per(market,outcome)→4 бэнда (≤0.40/0.40-0.60/0.60-0.80/≥0.80), trades/WR/ROI/PnL/invested; P&L базис = /equity (кросс-чек с totalPnl копейка-в-копейку). profile расширен: activeDays/tradesPerEvent(÷distinct event_id)/avgHoldDays(julianday end_date−timestamp); avgBuy был.useWhalePriceBuckets (D + ROI для B), useCopySubscriptions.get(leader) (I, config: priceMin/Max в %, maxOpenPerLeader, maxPerEvent), buildRec(lang,best,worst,priceMax) (J, data-driven best/worst бэнд).equity по окну + пересчёт cum от 0 внутри окна (дефолт Всё=полная история). Бэкенд не трогается.useBacktests)./root/polymarket-screener/server-rust (порт 3240), НЕ локальный _impl/ (снапшот). routes.rs держал чужой WIP (phantom_loss) → коммит через git-swap (git show HEAD>tmp+вставка+swap+commit+restore; working tree=deployed, коммит=HEAD+мои). commit msg через -F файл (кавычки ломаются в ssh). client/dist в .gitignore. Ветка redesign-nav, push origin Zserg5585/polymarket-screener.docs/WHALE_CARD_PLAN.md (чанки 0-7, все ✅).По конкурент-ресёрчу (docs/COMPETITOR_ANALYSIS.md разд.5) → план docs/WHALE_ANALYTICS_PLAN.md (11 чанков P0-P2). Все правки в WhaleProfile.tsx + инлайн-лейблы ru/en/es.
whale_price_buckets: агрегат резолвнутых BUY-позиций per market+outcome): /api/signals/whale/{addr}/categories (WR/ROI/PnL по screener_markets.category топ-8), /hold-buckets (распределение времени холда <1д/1-3д/3-7д/>7д). price-buckets расширен avgPrice (cost-взвеш.) для калибровки. profile расширен copyability{hard,tradesPerDay,reasons[]}.useWhaleCategories, useWhaleHoldBuckets (паттерн как useWhalePriceBuckets). Frontend-метрики (стабильность/форма/стрик/трек-рекорд) считаются из уже загружаемого equity-массива — без бэкенда.first_seen = артефакт индексатора (когда МЫ проиндексировали кита), НЕ создание кошелька → «новый=инсайдер» НЕ делать (мислейбл). equity хронологичен asc, equity[0].t=ранняя резолвнутая сделка./copy (нет роута/catch-all)→чёрный экран; список копий=CopyTradingPage в TradePage на /portfolio (ff3e094).2a54e57): wordmark TopBar.tsx, index.html title/og/twitter, public/manifest.webmanifest name/short/desc, theme #0a1420.2a54e57): 🎓 text-overflow:ellipsis НЕ работает на inline <span> — нужен block/inline-block/flex. Фикс: имя в .pk-cname-txt, .pk-cname→flex+min-width:0.8cd26eb; v1 C+волна 623da9b заменён): выбран target после ~9 Grok-батчей. Значок собран ЗАНОВО в коде (PIL supersample 2048→1024, градиент teal→green #2dd4bf→#34d399, 2 кольца+точка) — простую геометрию лучше рисовать самому, чем вырезать из мелкого grid-тайла. Ассеты client/public/: coattail-mark.png(шапка, прозрачный) / icon-192.png / icon-512.png / apple-touch-icon.png(180) / favicon-16/32.png(navy-тайл). Подключено в index.html(favicon png+apple-touch), manifest.webmanifest(icons), TopBar.tsx(<img src="/coattail-mark.png" class="pk-logo-mark">, CSS height 26px). Замена лого = только заменить файлы в public + rebuild (имена те же, код не трогать)..jpg-документы → слать как фото (сжатие ~960px, для веб-лого/favicon хватает) или renamed .png. (b) Grok/image-gen НЕ умеет прозрачный PNG / точные размеры — фон убираю по цвету (greenish/color-key) ИЛИ строю вектор сам; нарезку favicon/apple/PWA + белую моно делаю сам (PIL). (c) PWA service-worker + favicon-кэш → после деплоя хард-рефреш (закрыть вкладку/переоткрыть).