rename DATABISNIS_API → HYPERION_API (nama menyesatkan: host itu Hyperion vexascan.com, ⊖ API databisnis sendiri; rename bersih simbol + env)

config.py: HYPERION_API = getenv('HYPERION_API') or getenv('DATABISNIS_API') or
default — env lama masih dibaca utk backward-compat deploy (NFS config.env ⊖
perlu diubah) ; konsumen (dashboard _get_liquid_vex, distribute verify_txid,
claim reward_from_tx) rename simbol ; .env.example + docker-compose.yml
(HYPERION_API: ${HYPERION_API:-...} x3 service) ; oracle test_dashboard:
default + fallback env lama (importlib.reload) ; docs SPEC + AGENTS

image web + dist + claim rebuild + push
This commit is contained in:
proitlab committed 2026-08-13 21:22:33 +07:00
1 parent fb16eeafaa
commit 663770f51b
9 files changed
+42 -25

No files matched your search

+4 -2
View File
@@ -63,8 +63,10 @@ VEX_REVOTE_TAG_DAYS=1
# SEGERA` (segera kadaluarsa, masih dibayar).
VEX_WARN_DAYS=3
# API databisnis untuk saldo liquid akun (balance BP, disajikan dashboard)
DATABISNIS_API=https://api.databisnis.id
# Hyperion API untuk saldo liquid akun + verifikasi txid (balance BP,
# disajikan dashboard; verifikasi distribusi/klaim). Nama lama `DATABISNIS_API`
# masih dibaca utk backward-compat deploy lama.
HYPERION_API=https://api.databisnis.id
# Dashboard: TTL cache (detik) untuk saldo liquid — gagal fetch juga di-cooldown
DASH_LIQUID_TTL=60
+4 -4
View File
@@ -13,7 +13,7 @@ Two tools scan the Vexanium blockchain voters table for accounts whose only vote
`VEX_DB_BACKEND=mysql VEX_DB_HOST=127.0.0.1 VEX_DB_PORT=3306 VEX_DB_USER=databisnis VEX_DB_PASS=databisnis VEX_DB_NAME=databisnisid ./venv/bin/python test_mariadb.py`
- Docker Swarm (production stack): images are registry-pushed `git.proit.id/proitlab/databisnisid-web` + `databisnisid-scan` + `databisnisid-dist` + `databisnisid-claim` — build & push them first (`docker build -t git.proit.id/proitlab/databisnisid-web . && docker push git.proit.id/proitlab/databisnisid-web`, same for the scan, dist and claim images), then from a swarm manager run `docker stack deploy -c docker-compose.yml databisnisid`. Stack = mariadb (internal) + web (gunicorn dashboard, published `:5001`; also receives `TZ` + `DISTRIBUTE_HOUR`/`DISTRIBUTE_WEEKDAY`/`DISTRIBUTE_FIRST_RUN` from `.env` for the BERITA banner, V38) + scan (`scan_loop.py`, runs `get_voters.py` at `SCAN_HOURS` default `0,8,16`, local timezone `TZ` default `Asia/Jakarta`, plus one scan at container start via `SCAN_RUN_ON_START`) + distribute (`distribute_loop.py`, runs `distribute.py` twice-weekly at `DISTRIBUTE_WEEKDAY` default `wed,sat` (CSV, multi-day V44) + hour `DISTRIBUTE_HOUR` default `10`, one-off `DISTRIBUTE_FIRST_RUN` default baked `2026-08-17`; schedule read via `config`, konsisten dgn dashboard); key `VEX_BP_PRIVATE_KEY` read from the bind-mounted `/app/.env` → host `/mnt/nfs/server5.saltis.id/data/databisnisid/app/config.env` via `config.py` `load_dotenv`, `:ro`) + claim (`claim_loop.py`, poll `claimrewards` at the 24h window, key + Telegram also from the same NFS bind). `mariadb` is pinned by `placement.constraints: node.hostname == server5.saltis.id` because its data lives in the host bind mount `/data/db/mariadb/databisnisid/data` on that node; `web`/`scan`/`distribute`/`claim` exclude node `server2U` (`node.hostname != server2U`) but otherwise can run on any node and reach it over the overlay network `appnet`. Inspect: `docker stack services databisnisid`, `docker service logs databisnisid_scan`, `docker stack rm databisnisid`. `docker stack deploy` ignores `build:` (images must already be in the registry) and ignores `env_file` (env is inlined with `${VAR}` interpolation from `.env`). The local dev box has no swarm anymore (torn down) — if you re-init one there, the mariadb constraint leaves that task **Pending** since no node is named `server5.saltis.id`.
- Web dashboard (read-only, reads the store via `db.py`): production `./venv/bin/gunicorn -c gunicorn.conf.py dashboard:app` → http://127.0.0.1:5000/ (run from repo dir). Easier: `./run.sh` (same command, works from any cwd, `$@` passed through). `gunicorn.conf.py` imports `config` → `.env` honored; `DASH_WORKERS` (default 2) controls workers, `DASH_HOST`/`DASH_PORT` the bind. Dev server (single-process) still works via `./venv/bin/python dashboard.py`. Vote bands (V40/V42/V52): BARU (new first-time voters, `first_seen_at` within 3 days, own table on top, never paid — maturity is measured from `first_seen_at`, NOT `last_vote` which is week-quantized) → main list: KADALUARSA (28–31 days, pinned amber rows at the top with `VOTE ULANG` badge + legend) then VALID (the paged 50/page list, `staked DESC`, live owner search via `/api/search`); voters older than 31 days are stored by the scan but hidden. Since V42, **REVOTE** (returning voters, `voter_first_seen.first_seen_at ≤ now−3d`) are VALID immediately — they join the main list tagged with a mint `REVOTE` tag (legend `TAG MINT = REVOTE KURANG DARI 1 HARI · VALID LANGSUNG`), no separate band, no maturity wait; since V43 the mint tag shows only while `last_vote > now−VEX_REVOTE_TAG_DAYS` (default 1 day) — after that they're plain VALID (still paid); VALID rows within `VEX_WARN_DAYS` (default 3) of the KADALUARSA cutoff get an amber dashed `VOTE ULANG SEGERA` badge (legend `TAG AMBER = KADALUARSA DALAM 3 HARI · N PEMILIH`); only genuinely new voters pass the 3-day maturity in BARU. Data freshness comes from the daily scan run — the dashboard never scans.
- Config: all tunables load from env / `.env` via `config.py` (python-dotenv): `VEX_TARGET_BP`, `VEX_API_NODE`, `VEX_API_NODES` (CSV pool RPC failover distribusi V58, default = `VEX_API_NODE` + `https://api.databisnis.id`), `VEX_DB_PATH`, `VEX_DB_BACKEND`, `VEX_DB_HOST`, `VEX_DB_PORT`, `VEX_DB_USER`, `VEX_DB_PASS`, `VEX_DB_NAME`, `VEX_MIN_STAKED_VEX`, `DASH_PAGE_SIZE`, `DASH_HOST`, `DASH_PORT`, `DASH_WORKERS`, `VEX_STALE_DAYS`, `VEX_MATURITY_DAYS`, `VEX_EXPIRED_DAYS`, `VEX_REVOTE_TAG_DAYS`, `VEX_WARN_DAYS`, `DATABISNIS_API`, `DASH_LIQUID_TTL`, `VEX_BP_PRIVATE_KEY`, `DISTRIBUTE_HOUR`, `DISTRIBUTE_WEEKDAY` (CSV multi-hari V44, default `wed,sat`), `DISTRIBUTE_FIRST_RUN` (default baked `2026-08-17`), `DISTRIBUTE_MAX_ATTEMPTS`, `DISTRIBUTE_ENABLED`, `TELEGRAM_BOT_TOKEN`, `TELEGRAM_COMMUNITY_CHAT_IDS`, `TELEGRAM_INTERNAL_CHAT_IDS`, `CLAIM_RETRY_SECONDS`, `VEX_BP_FEE_WALLET`, `VEX_BP_FEE_PERCENT`. Copy `.env.example` → `.env` to override; `.env` is gitignored. Chain constants (`vexcore`/scope/table) stay hardcoded.
- Config: all tunables load from env / `.env` via `config.py` (python-dotenv): `VEX_TARGET_BP`, `VEX_API_NODE`, `VEX_API_NODES` (CSV pool RPC failover distribusi V58, default = `VEX_API_NODE` + `https://api.databisnis.id`), `VEX_DB_PATH`, `VEX_DB_BACKEND`, `VEX_DB_HOST`, `VEX_DB_PORT`, `VEX_DB_USER`, `VEX_DB_PASS`, `VEX_DB_NAME`, `VEX_MIN_STAKED_VEX`, `DASH_PAGE_SIZE`, `DASH_HOST`, `DASH_PORT`, `DASH_WORKERS`, `VEX_STALE_DAYS`, `VEX_MATURITY_DAYS`, `VEX_EXPIRED_DAYS`, `VEX_REVOTE_TAG_DAYS`, `VEX_WARN_DAYS`, `HYPERION_API`, `DASH_LIQUID_TTL`, `VEX_BP_PRIVATE_KEY`, `DISTRIBUTE_HOUR`, `DISTRIBUTE_WEEKDAY` (CSV multi-hari V44, default `wed,sat`), `DISTRIBUTE_FIRST_RUN` (default baked `2026-08-17`), `DISTRIBUTE_MAX_ATTEMPTS`, `DISTRIBUTE_ENABLED`, `TELEGRAM_BOT_TOKEN`, `TELEGRAM_COMMUNITY_CHAT_IDS`, `TELEGRAM_INTERNAL_CHAT_IDS`, `CLAIM_RETRY_SECONDS`, `VEX_BP_FEE_WALLET`, `VEX_BP_FEE_PERCENT`. Copy `.env.example` → `.env` to override; `.env` is gitignored. Chain constants (`vexcore`/scope/table) stay hardcoded.
- Syntax check: `node --check get_voters.js`, `./venv/bin/python -m py_compile config.py get_voters.py dashboard.py db.py gunicorn.conf.py scan_loop.py distribute.py distribute_loop.py telegram.py claim.py claim_loop.py test_get_voters.py test_dashboard.py test_mariadb.py test_distribute.py test_distribute_loop.py test_images.py test_claim.py`.
- Tests (the verification oracles): `./venv/bin/python test_get_voters.py`, `./venv/bin/python test_dashboard.py`, `./venv/bin/python test_distribute.py`, `./venv/bin/python test_distribute_loop.py`, `./venv/bin/python test_images.py`, and `./venv/bin/python test_claim.py` must all exit 0. `test_images.py` (V31) is a static check that each image (`Dockerfile`/`Dockerfile.scan`/`Dockerfile.dist`/`Dockerfile.claim`) copies every intra-project module its copied modules import. `test_distribute_loop.py` (V37/V44) is pure calendar logic — weekly schedule boundary (multi-day CSV) + one-off first-run. They mock the network / use a temp DB — the live node is too flaky/slow for a full-scan test. Run after touching the relevant file.
@@ -29,10 +29,10 @@ Two tools scan the Vexanium blockchain voters table for accounts whose only vote
- Freshness filter (spec §V13/V40): `last_vote` (estimated last re-vote date) is derived at scan time from the Vexanium weight formula `last_vote_weight = staked_raw × 2^(weeks since 2000 / 52)` → `last_vote = 2000-01-01 + round(52 × log2(weight / (staked×10000))) weeks` — the chain (`stake2vote`) quantizes the exponent to whole weeks (`int64((now−2000-01-01)/(86400×7))/52.0`), so derived dates land on 7-day boundaries (B12; earlier 365.25-day divisor drifted ~33 days forward for 2026 votes). Since V40 `normalize()` stores **every** BP voter with a derivable `last_vote` — any age; only unverifiable votes (zero/empty weight) are dropped. Banding (BARU/VALID/KADALUARSA), display and payout are computed at query time (dashboard cuts 28/31 days on `last_vote` age; distribution pays `last_vote > now−28d`). **Maturity (3 days) is measured from `first_seen_at`, not `last_vote`** — `last_vote` is week-quantized so a new voter voting Mon–Thu would look 4–6 days old and bypass the wait (B15); V52 uses `voter_first_seen.first_seen_at` (scan-precise to the second) for the new-voter wait. The heuristic over-reads when a voter unstaked without re-voting (weight ÷ smaller stake ⇒ future date), so `normalize()` clamps any `last_vote` past scan time down to `now`. Since V41, `voter_first_seen.first_seen_at` separates PEMILIH BARU (new, first-seen within the 3-day maturity) from REVOTE (returning re-voters); since V42 REVOTE are VALID immediately (paid + shown in the main list, mint tag), only new voters wait out maturity; since V43 the mint tag lasts only `VEX_REVOTE_TAG_DAYS` (1) and VALID rows nearing the KADALUARSA cutoff (within `VEX_WARN_DAYS`, 3) get a `VOTE ULANG SEGERA` badge — display-only.
- The web list shows RANK / AKUN / STAKE (VEX) / TOTAL REWARD (VEX) / VOTE TERAKHIR columns (3 stats cells). TOTAL REWARD = all-time sum of `distribute_payments.amount` where `status='sent'` (0,0000 for never-paid), joined per page via a LEFT JOIN subquery. Vote weight stays in the DB and `/api/search` JSON but is not rendered as a column.
- Token contract is `vex.token` (NOT `eosio.token` — that name doesn't exist on Vexanium), 4-decimal VEX, chain_id `f9f432b1851b5c179d2091a96f593aaed50ec7466b74f89301f957a83e56ce1f`. Distribution signs `vex.token::transfer` with the BP `active` key.
- Distribution (spec §V19–V27): each run pays the whole liquid balance pro-rata by stored `staked`, shares floored at 4 decimals with dust left in the account, one transfer per voter. A txid that can't be confirmed (`GET {DATABISNIS_API}/v2/history/get_transaction?id=<txid>` returns no `executed`) is never re-sent the same run — that payment stays `failed` and rejoins the next run. No-op (exit 0, no writes) when the voters table is empty or balance < 0.0001. The dashboard's `/history` view renders `distribute_runs` + `distribute_payments` read-only.
- Distribution (spec §V19–V27): each run pays the whole liquid balance pro-rata by stored `staked`, shares floored at 4 decimals with dust left in the account, one transfer per voter. A txid that can't be confirmed (`GET {HYPERION_API}/v2/history/get_transaction?id=<txid>` returns no `executed`) is never re-sent the same run — that payment stays `failed` and rejoins the next run. No-op (exit 0, no writes) when the voters table is empty or balance < 0.0001. The dashboard's `/history` view renders `distribute_runs` + `distribute_payments` read-only.
- Claim (spec §V32–V34): the BP reward is claimed once per 24h window via `vexcore::claimrewards` (`owner` = BP). The `claim` service polls every `CLAIM_RETRY_SECONDS` only near the window (`last_claim_time` from the `vexcore` `producers` table + 24h) — no all-day tx spam; a window already ≥24h past clamps to now so a missed claim is caught up, never spin-timeout (B8). `get_table_rows` responses are always `{"rows":[...]}` — parse `data.get('rows')`, never `data[0]` (B7, V39). Landed detection = Hyperion `executed` OR `last_claim_time` advanced past pre-send value, checked on BOTH the send-success and send-exception paths (`_confirm_landed`) because pyntelope's `send()` returns HTTP 500 as a body instead of raising on a chain rejection (V46, B11); Hyperion `executed: false` on a not-yet-indexed tx still falls back to the `last_claim_time` advance check (V50, B14). `next_window` raises if all producer reads fail so a flaky node never triggers a premature claim. Reward = sum of `vex.bpay`+`vex.vpay` → BP transfers read from the claim tx via Hyperion (`reward_from_tx`), balance-delta as fallback (V33); if neither can be read → fee `pending` + internal `KLAIM MENDARAT · REWARD TAK TERUKUR` alert, never a misleading 0-reward success (B10). 10% fee floored at 4-dec goes to `bpdbsjasprod` (`VEX_BP_FEE_WALLET`), memo `BP FEE YYYY-MM-DD`, txid verified via Hyperion before any resend. A cycle completes only when the claim row (`claim_runs`) is recorded AND the fee is `sent`; an unsent fee is retried each cycle and resumed on restart. No mutex with distribution — the daily payout freezes the balance at run start, so a claim landing mid-run is simply paid out the next run.
- Dashboard layout (spec §V28): desktop gives AKUN/STAKE/REWARD/VOTE equal width with AKUN left and the other three centered; tablet keeps the fixed STAKE column (RANK 56 / AKUN 1fr / STAKE 160px / REWARD 1fr / VOTE 1fr); `/history` uses a six-column run grid and a four-column payment grid (AKUN/JUMLAH/STATUS/TXID) that shows only the latest run with its date in the title, and links each TXID to `https://vexascan.com/transaction/{txid}` — no TANGGAL or MEMO column (memo is on-chain only, not stored); mobile turns history rows into labeled cards. HTML responses use `Cache-Control: no-store`; stylesheet URL is versioned with `?v=` for deploy cache-busting.
- Liquid balance (spec §V16): a 4th stat cell "SALDO LIQUID" shows the BP account's liquid VEX (`account.core_liquid_balance`) fetched from `GET {DATABISNIS_API}/v2/state/get_account?account=<BP>`. The dashboard fetches it live but caches per-worker in memory for `DASH_LIQUID_TTL` (default 60s); a failed fetch keeps the last value (or renders `—` if none ever succeeded) and the failure is also cooled-down so the API isn't hammered. The dashboard still never writes to the DB.
- Liquid balance (spec §V16): a 4th stat cell "SALDO LIQUID" shows the BP account's liquid VEX (`account.core_liquid_balance`) fetched from `GET {HYPERION_API}/v2/state/get_account?account=<BP>`. The dashboard fetches it live but caches per-worker in memory for `DASH_LIQUID_TTL` (default 60s); a failed fetch keeps the last value (or renders `—` if none ever succeeded) and the failure is also cooled-down so the API isn't hammered. The dashboard still never writes to the DB.
## Layout
@@ -46,7 +46,7 @@ Two tools scan the Vexanium blockchain voters table for accounts whose only vote
- `telegram.py` — kirim notifikasi status distribusi + klaim via Telegram Bot API (best-effort; ⊥ token/chat id → kanal no-op senyap; gagal kirim → log saja). Dua kanal: `TELEGRAM_COMMUNITY_CHAT_IDS` (CSV) = brief mulai/selesai distribusi saja; `TELEGRAM_INTERNAL_CHAT_IDS` (CSV) = detail distribusi + semua kegagalan + klaim reward (⊥ komunitas, V30/V36). `send_text(text, chat_ids=None)` = komunitas, `send_internal(text)` = internal; dari env/`.env`/NFS `config.env`.
- `dashboard.py` + `templates/index.html` + `templates/history.html` + `static/style.css` + `static/app.js` — Flask web dashboard; reads the store via `db.py`, styled per `DESIGN.md`; `app.js` = debounced live owner search (fetch `/api/search`), degrades to the server-side `?q=` GET form if JS is off. Three vote-age bands (V40/V52): the voters list is split into BARU (top, `PEMILIH BARU · BELUM MATANG`, new accounts by `first_seen_at` within 3 days) and the main list (`voter-list` section, `.reward` cells, V29) where KADALUARSA rows are pinned at the top with amber styling + `VOTE ULANG` badge + legend before the paged VALID rows, which LEFT-joins a `status='sent'` payments aggregate for the TOTAL REWARD column; since V42 the VALID list also holds REVOTE (returning re-voters, `voter_first_seen.first_seen_at ≤ now−3d`) tagged with a mint `REVOTE` tag (legend `TAG MINT = REVOTE KURANG DARI 1 HARI · VALID LANGSUNG`, shows only while `last_vote > now−VEX_REVOTE_TAG_DAYS`) — no separate band, no maturity wait; only genuinely new voters sit in BARU tagged `BARU`; since V43 VALID rows within `VEX_WARN_DAYS` of the KADALUARSA cutoff get an amber dashed `VOTE ULANG SEGERA` badge + legend `TAG AMBER = KADALUARSA DALAM 3 HARI · N PEMILIH` (display-only); search (`?q=` and `/api/search`) covers all bands and tags each result (`group` = `baru|revote|valid|kadaluarsa` + `expired` + `expiring`); `/api/voter/<owner>` (V57) returns `{"owner", "is_valid_voter"}` — payout-eligibility check via the SAME `db.eligible_voters(owner)` window distribute pays from (never a second SQL copy), DB-snapshot only, always 200; Banner BERITA (V38) shows the next distribution run from `DISTRIBUTE_HOUR`/`DISTRIBUTE_WEEKDAY`/`DISTRIBUTE_FIRST_RUN` + `TZ` (mirror of `distribute_loop.next_boundary`, ⊥ impor lintas-image; drift guarded in test_dashboard); the `SETIAP ... · HH:00` recurrence line renders only for regular weekly rounds — hidden while the next event is the one-off first-run (`_is_first_run_next`, date ≠ weekly day). A `stats-note` caption states the list is single-vote-to-BP accounts ≥ min stake (V47); account names link to `https://vexascan.com/account/{owner}` in the index list, `/history` pay-list, and live search (`.owner-link`, V51); a promo card (`BY DATABISNISID · VEXWALLET`) links the VexWallet Android app on the index page via the IDRS logo (`static/idrs.png`) + `UNDUH DI PLAY STORE` (V53); BARU rows show a `MATURITY PERIOD` countdown column (`VALID DALAM N HARI`) instead of TOTAL REWARD. `/history` = riwayat distribusi read-only; `run-list` = six-column desktop/tablet grid, `pay-list` = four-column grid (AKUN/JUMLAH/STATUS/TXID) showing only the latest run with its date in the title, and labeled mobile cards.
- `gunicorn.conf.py` — gunicorn production config (bind/workers from `config`, sync worker). `run.sh` — launcher: `./run.sh` = `./venv/bin/gunicorn -c gunicorn.conf.py dashboard:app` from any cwd.
- `config.py` — loads env/`.env` (python-dotenv) → `TARGET_BP`, `API_NODE`, `API_NODES` (pool RPC distribusi, CSV `VEX_API_NODES`), `DB_PATH`, `DB_BACKEND`, `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASS`, `DB_NAME`, `MIN_STAKED_VEX`, `PAGE_SIZE`, `DASH_HOST`, `DASH_PORT`, `DASH_WORKERS`, `VEX_STALE_DAYS`, `VEX_MATURITY_DAYS`, `VEX_EXPIRED_DAYS`, `DATABISNIS_API`, `DASH_LIQUID_TTL`, `BP_PRIVATE_KEY`, `DISTRIBUTE_HOUR`, `DISTRIBUTE_MAX_ATTEMPTS`, `TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_IDS`, `CLAIM_RETRY_SECONDS`, `BP_FEE_WALLET`, `BP_FEE_PERCENT`; shared by get_voters & dashboard.
- `config.py` — loads env/`.env` (python-dotenv) → `TARGET_BP`, `API_NODE`, `API_NODES` (pool RPC distribusi, CSV `VEX_API_NODES`), `DB_PATH`, `DB_BACKEND`, `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASS`, `DB_NAME`, `MIN_STAKED_VEX`, `PAGE_SIZE`, `DASH_HOST`, `DASH_PORT`, `DASH_WORKERS`, `VEX_STALE_DAYS`, `VEX_MATURITY_DAYS`, `VEX_EXPIRED_DAYS`, `HYPERION_API`, `DASH_LIQUID_TTL`, `BP_PRIVATE_KEY`, `DISTRIBUTE_HOUR`, `DISTRIBUTE_MAX_ATTEMPTS`, `TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_IDS`, `CLAIM_RETRY_SECONDS`, `BP_FEE_WALLET`, `BP_FEE_PERCENT`; shared by get_voters & dashboard.
- `scan_loop.py` — scheduler dalam container stack: menunggu batas `SCAN_HOURS` (lokal via `TZ`), panggil `get_voters.main()`; skan-awal `SCAN_RUN_ON_START` + tunggu DB siap; loop tak pernah keluar.
- `distribute_loop.py` — scheduler dua-kali-minggu dalam container stack (service `distribute`): tunggu `DISTRIBUTE_WEEKDAY` (default `wed,sat`, CSV multi-hari V44) + `DISTRIBUTE_HOUR` (lokal via `TZ`), plus satu one-off `DISTRIBUTE_FIRST_RUN` (`YYYY-MM-DD`, default baked `2026-08-17`); jadwal dibaca via `config` (konsisten dgn dashboard V38); panggil `distribute.main()`, loop tak pernah keluar; gagal dicatat dan dicoba di jadwal berikutnya; ⊥ distribusi-awal saat start (snapshot bisa basi).
- `Dockerfile` — image web `databisnisid-web` (gunicorn dashboard; `DASH_HOST=0.0.0.0` di stack agar ingress menjangkaunya). `Dockerfile.scan` — image `databisnisid-scan` (scan_loop; sertakan `tzdata`). `Dockerfile.dist` — image `databisnisid-dist` (distribute_loop; sertakan `tzdata` + pyntelope via requirements). `Dockerfile.claim` — image `databisnisid-claim` (claim_loop + claim + distribute untuk fee; sertakan `tzdata` + pyntelope). Di stack produksi keempatnya di-push ke registry `git.proit.id/proitlab/databisnisid-{web,scan,dist,claim}` (⊥ `build:` di compose). `.dockerignore` — venv/.env/artifak tak masuk build context.
+6 -6
View File
@@ -12,7 +12,7 @@ Auto-claim: tiap hari di jendela klaim (`last_claim_time` + 24 jam) → `vexcore
- config via env/`.env` (`config.py`, python-dotenv); default = konstanta lama
- db: `db.py` abstraksi backend — `sqlite` (default, stdlib `sqlite3` → `DB_PATH`, WAL → pembaca tak terblokir) atau `mysql` (MariaDB/MySQL via PyMySQL, `VEX_DB_*`); container uji via `docker-compose.dev.yml`
- api: `API_NODE` (default `https://v2.vexascan.com:2096`, public, flaky → retry)
- saldo liquid akun BP: API databisnis (`DATABISNIS_API`, default `https://api.databisnis.id`) — diambil dashboard, cache TTL `DASH_LIQUID_TTL` (default 60s), cooldown kegagalan
- saldo liquid akun BP: API databisnis (`HYPERION_API`, default `https://api.databisnis.id`) — diambil dashboard, cache TTL `DASH_LIQUID_TTL` (default 60s), cooldown kegagalan
- contract=scope=`vexcore` (≠ `vexio`, EOS convention ⊥) — ⊥ env-able
- filter: ∃ voter where `producers.length==1 & producers[0]==TARGET_BP`
- `staked` scaled ×10000 → store ÷10000
@@ -43,7 +43,7 @@ Auto-claim: tiap hari di jendela klaim (`last_claim_time` + 24 jam) → `vexcore
- service `claim` stack (image `-claim`): `claim_loop.py` tidur sampai mendekati jendela (margin), lalu poll `claimrewards` tiap `CLAIM_RETRY_SECONDS` (default 60) sampai diterima chain; ⊥ spam 1440 tx gagal/hari
- siklus klaim selesai ⊥ bila klaim tercatat di `claim_runs` DAN fee terkirim; fee pending/failed diulang tiap siklus + dilanjutkan saat restart (crash-safe, ⊥ fee hilang); setelah klaim sukses jendela dihitung ulang dari `last_claim_time` segar
- ⊥ mutex dgn distribute: distribute beku saldo di awal run (V20) → klaim yang jatuh di tengah run didefer ke run berikutnya
- klaim butuh `VEX_BP_PRIVATE_KEY` + `DATABISNIS_API` (verifikasi txid V22); Telegram notif klaim sukses/gagal (best-effort V30)
- klaim butuh `VEX_BP_PRIVATE_KEY` + `HYPERION_API` (verifikasi txid V22); Telegram notif klaim sukses/gagal (best-effort V30)
## §I — Interfaces
api: POST `https://v2.vexascan.com:2096/v1/chain/get_table_rows`
@@ -55,7 +55,7 @@ web: GET `/` (Flask, disajikan gunicorn di produksi) → HTML spec-list, paged 5
web: GET `/` + `?q=<substring>` → filter owner (server-side, no-JS fallback); pager bawa `q`
web: GET `/api/search?q=` → JSON `{query,count,cap,results:[{owner,staked,weight,rank,last_vote,total_reward}]}`, rank global, cap 500
web: GET `/api/voter/<owner>` → JSON `200 {owner, is_valid_voter}` (payout-eligible check, V57); selalu 200, ⊖ 404; DB-snapshot saja, ⊖ format-validation
env: `VEX_TARGET_BP`, `VEX_API_NODE`, `VEX_API_NODES` (CSV pool RPC failover distribusi, V58), `VEX_DB_PATH`, `VEX_DB_BACKEND`, `VEX_DB_HOST`, `VEX_DB_PORT`, `VEX_DB_USER`, `VEX_DB_PASS`, `VEX_DB_NAME`, `VEX_MIN_STAKED_VEX`, `DASH_PAGE_SIZE`, `DASH_HOST`, `DASH_PORT`, `DASH_WORKERS`, `VEX_STALE_DAYS`, `DATABISNIS_API`, `DASH_LIQUID_TTL`, `SCAN_HOURS`, `SCAN_RUN_ON_START`, `TZ`, `DISTRIBUTE_HOUR`, `DISTRIBUTE_WEEKDAY`, `DISTRIBUTE_FIRST_RUN` (dashboard banner V38; web service di stack menerima TZ + jadwal) — via `config.py` (`.env`)
env: `VEX_TARGET_BP`, `VEX_API_NODE`, `VEX_API_NODES` (CSV pool RPC failover distribusi, V58), `VEX_DB_PATH`, `VEX_DB_BACKEND`, `VEX_DB_HOST`, `VEX_DB_PORT`, `VEX_DB_USER`, `VEX_DB_PASS`, `VEX_DB_NAME`, `VEX_MIN_STAKED_VEX`, `DASH_PAGE_SIZE`, `DASH_HOST`, `DASH_PORT`, `DASH_WORKERS`, `VEX_STALE_DAYS`, `HYPERION_API`, `DASH_LIQUID_TTL`, `SCAN_HOURS`, `SCAN_RUN_ON_START`, `TZ`, `DISTRIBUTE_HOUR`, `DISTRIBUTE_WEEKDAY`, `DISTRIBUTE_FIRST_RUN` (dashboard banner V38; web service di stack menerima TZ + jadwal) — via `config.py` (`.env`)
api: POST `https://v2.vexascan.com:2096/v1/chain/get_currency_balance` → body {code:`vex.token`, account, symbol:`VEX`} → `["X.XXXX VEX"]`
api: POST `https://v2.vexascan.com:2096/v1/chain/get_currency_stats` → {supply, max_supply, issuer} (cek precision)
api: POST `https://v2.vexascan.com:2096/v1/chain/push_transaction` (signed via pyntelope) → txid
@@ -90,13 +90,13 @@ V11: search `q` → filter owner substring (LIKE, wildcard-escape, case-insensit
V12: produksi web disajikan gunicorn (`dashboard:app`, `gunicorn.conf.py`); `DASH_WORKERS` default 2; `.env` ikut termuat via import `config`
V13: `last_vote` = epoch 2000-01-01 + `round(52 × log2(last_vote_weight / (staked×10000)))` PEKAN (Vexanium/EOSIO `stake2vote`: eksponen = `int64((now − epoch)/(86400×7)) / 52.0` → bobot dikuantisasi pekan utuh; jadi tanggal kelipatan 7 hari, 00:00:00; regresi oracle baris nyata `..tg` 1291 pekan → 2024-09-28, `1.crownz` 1314 pekan → 2025-03-08); None bila bobot/stake nol; estimasi yang melewati `now` (unstake tanpa revote) diklamp ke `now`; voter dgn `last_vote=None` dibuang di `normalize` (bobot tak bisa diverifikasi → tanggal tak diketahui, ⊥ bisa diklasifikasi); V40: vote basi (> VEX_STALE_DAYS) TIDAK lagi dibuang di scan — SEMUA umur disimpan; banding/tampil/bayar menghitung umur saat query (⊥ peran scan)
V15: web list = kolom RANK/AKUN/STAKE (VEX)/TOTAL REWARD (VEX)/VOTE TERAKHIR — ⊥ BOBOT SUARA; stats = SALDO LIQUID/TOTAL PEMILIH/STAKE TERTINGGI atas semua baris TAMPIL (V40: ⊖ yang tersembunyi > 31 hari); TOTAL VEX dihapus (V48: raw single-vote stake mudah disalahartikan); V40/V42/V43: BARU (`PEMILIH BARU · BELUM MATANG`, new saja) + daftar utama — kadaluarsa menyatu DI ATAS baris VALID (dipin, sorot amber + badge `VOTE ULANG` + legenda `BARIS AMBER = VOTE LEWAT …`), lalu VALID (pager) = matang-baru + REVOTE (tag mint `REVOTE` + legenda `TAG MINT = REVOTE KURANG DARI 1 HARI · VALID LANGSUNG`, V43) + badge amber dashed `VOTE ULANG SEGERA` (V43) pada baris `exp_cut < last_vote ≤ warn_cut` + legenda `TAG AMBER = KADALUARSA DALAM {WARN} HARI · N PEMILIH`; REVOTE ⊖ lagi bagian sendiri (V42); `/api/search` hasil `{owner,staked,weight,rank,last_vote,total_reward,group,expired,expiring}` — `group` = `baru|revote|valid|kadaluarsa` (weight disimpan & di-API, ⊥ dirender); V47: catatan `stats-note` menyatakan TOTAL PEMILIH = akun suara tunggal ke BP stake ≥ `MIN_STAKED_VEX`; V51: nama akun (index + history pay-list + search live) jadi link `https://vexascan.com/account/{owner}` (`.owner-link`, `target=_blank rel=noopener`) — display-only
V16: saldo liquid akun `TARGET_BP` dari `GET {DATABISNIS_API}/v2/state/get_account` (parse `account.core_liquid_balance`) → sel stat SALDO LIQUID; cache in-memory per worker TTL `DASH_LIQUID_TTL`; gagal fetch → nilai lama (atau `—` bila belum pernah sukses) + `at` ikut diset (cooldown, ⊥ pukulan berulang); read-only, ⊥ tulis DB
V16: saldo liquid akun `TARGET_BP` dari `GET {HYPERION_API}/v2/state/get_account` (parse `account.core_liquid_balance`) → sel stat SALDO LIQUID; cache in-memory per worker TTL `DASH_LIQUID_TTL`; gagal fetch → nilai lama (atau `—` bila belum pernah sukses) + `at` ikut diset (cooldown, ⊥ pukulan berulang); read-only, ⊥ tulis DB
V17: penyimpanan lewat `db.py` (backend `VEX_DB_BACKEND` = `sqlite` default | `mysql`); query berbagi sintaks pakai placeholder `%s` (diterjemahkan `?` utk sqlite); LIKE escape pakai `ESCAPE '!'` — backslash memutus literal string MySQL (B2); `db.query` → list (fetchall sqlite=list / PyMySQL=tuple diseragamkan, B3); replace snapshot mysql = CREATE IF NOT EXISTS + DELETE + INSERT satu transaksi (MVCC → pembaca dapat snapshot konsisten saat ganti harian, tak kena torn read); oracle utama (test_get_voters/test_dashboard) tetap sqlite hermetik; `test_mariadb.py` opt-in (skip exit 0 bila `VEX_DB_BACKEND != mysql`)
V18: stack produksi = Docker Swarm (`docker-compose.yml`, `docker stack deploy -c docker-compose.yml databisnisid`): mariadb bind mount `/data/db/mariadb/databisnisid/data` pd node `server5.saltis.id` + placement constraint `node.hostname == server5.saltis.id` (data node-lokal → mariadb harus selalu di node itu; di swarm yg ⊥ punya node itu task-nya Pending, bukan bug); web (gunicorn, publik `:5000`) + scan (`scan_loop.py`) + distribute (`distribute_loop.py`, image `databisnisid-dist`) di node mana pun, terhubung via overlay `appnet`; image dari registry `git.proit.id/proitlab/databisnisid-web` & `-scan` & `-dist` (⊥ `build:`); scan berjalan pd jam `SCAN_HOURS` (default 0,8,16, lokal `TZ` default Asia/Jakarta via `datetime.now()`) + skan-awal `SCAN_RUN_ON_START` (setelah DB siap) → tabel voters langsung ada (dashboard ⊥ 500); distribute loop harian `DISTRIBUTE_HOUR` (default 10, lokal) — ⊥ distribusi-awal saat start (snapshot bisa basi); key `VEX_BP_PRIVATE_KEY` dari bind `/mnt/nfs/server5.saltis.id/data/databisnisid/app/config.env:/app/.env:ro` dibaca `config.py` `load_dotenv` (⊥ interpolasi env compose); loop tak pernah keluar; `stack deploy` ⊥ `env_file` (env inline via `${VAR}` interpolasi `.env`); image web set `DASH_HOST=0.0.0.0` agar ingress menjangkau; healthcheck web = cek socket TCP (⊥ ketergantungan isi DB)
V19: target transfer = kontrak `vex.token` (konstanta chain hardcoded; ⊥ diubah ke `eosio.token`)
V20: saldo distribusi dari chain `get_currency_balance` pd `vex.token`; share = floor(balance×stake÷total_stake) 4 desimal; Σshare ≤ balance; sisa (dust) tetap di akun
V21: ∀ transfer → dicatat di `distribute_payments` sbg `pending` SEBELUM kirim; status → `sent`|`failed` dr hasil tx; ⊥ hapus baris (append)
V22: retry → hanya baris `failed`; baris `sent` ⊥ dikirim ulang; sebelum resend, verifikasi txid via `GET {DATABISNIS_API}/v2/history/get_transaction` → `executed` → tandai `sent` (⊥ duplikat)
V22: retry → hanya baris `failed`; baris `sent` ⊥ dikirim ulang; sebelum resend, verifikasi txid via `GET {HYPERION_API}/v2/history/get_transaction` → `executed` → tandai `sent` (⊥ duplikat)
V23: memo per run = `DATABISNISID PROFIT SHARE YYYY-MM-DD` — sama utk seluruh transfer run, hanya on-chain (⊖ disimpan DB)
V24: run harian `DISTRIBUTE_HOUR` (default 10) waktu lokal `TZ`; ⊥ beku/carryover — hitung ulang tiap hari dari snapshot tersimpan
V25: pemilih kosong | saldo < 1 unit shareable → no-op senyap, ⊥ tulis DB
@@ -124,7 +124,7 @@ V54: baseline landing klaim STABIL per window (B16): `poll_claim` baca `last_cla
V55: reward klaim TAK PERNAH tercatat/notif sbg `0.0000` sukses bila tak terukur (B17): `_measure_reward` (1) RETRY `reward_from_tx` 4× dgn jeda `SETTLE_SECONDS` (default 30) — tx klaim baru mendarat bisa belum terindeks Hyperion sesaat → None palsu; (2) fallback selisih saldo 3× HANYA mengakui delta POSITIF (`after > before`) sbg terukur — delta 0 ambigu (node balik saldo lama / saldo belum ter-update) → tetap None; keduanya gagal → fee `pending` + alert `KLAIM MENDARAT · REWARD TAK TERUKUR` (jalur V45), ⊖ `KLAIM REWARD SUKSES 0.0000` menyesatkan. Reward 0 ASLI (tx executed tanpa transfer bpay/vpay) tetap terbaca via isi tx (`reward_from_tx` → Decimal('0')) → fee `skipped` (V33). Oracle test_claim: V33 reward-0 via isi tx (bukan delta 0); B17-a reward_from_tx None + delta 0 → claimed + fee pending + alert ⊖ sukses-0; B17-b reward_from_tx None lalu berhasil → reward asli tercatat | V55,V45,V33
V56: baseline SALDO klaim STABIL per window (B18): `poll_claim` baca `before_balance` (saldo liquid) SEKALI di awal window & oper ke SETIAP `_try_claim_once(last_before, before_balance)` — ⊖ baca ulang per attempt: klaim yang mendarat di attempt 1 membuat `before` attempt 2 (ditolak already-claimed) SUDAH termasuk reward → delta 0 → reward tak terukur → alert palsu (kasus 10 Agt: tx `bfa9ab02` mendarat 15:46:18 reward 1679.3365, app ukur tx ditolak `b0a8b2f9` + before post-credit → alert `KLAIM MENDARAT · REWARD TAK TERUKUR`). Baseline stabil → delta fallback mengukur reward benar walau tx tercatat = attempt ditolak. Bila None (pemanggil langsung) → baca sendiri sbg fallback. Oracle test_claim: B18 attempt-1-mendarat (confirm basi) → retry, attempt-2-ditolak (last maju) → landed + reward 1679.3365/fee 167.9336 terukur, ⊖ alert | V56,V54,V55
V57: endpoint `GET /api/voter/<owner>` (web) → `200 {owner, is_valid_voter}` — `is_valid_voter` = akun memenuhi SYARAT REWARD saat ini (jendela payout distribusi V40/V52), BUKAN sekadar ada di tabel `voters` (BARU/KADALUARSA/tersembunyi/akun tak dikenal → `false`). Single source kebenaran: `db.eligible_voters(owner=None)` (db.py) memakai SQL jendela payout yg SAMA — `last_vote > now−STALE AND first_seen_at IS NOT NULL AND first_seen_at ≤ now−MATURITY` (cutoff UTC-naive) — dipakai BAIK `distribute.py:run_distribution` (tanpa owner → semua baris `(owner,staked)`) MAUPUN endpoint (dengan owner → `[]`|one row) → ⊖ duplikasi SQL (drift, gaya V38); endpoint: `is_valid_voter = len(db.eligible_voters(owner)) > 0`; DB-snapshot only (kesegaran = skan harian, V10), selalu 200, ⊖ format-validation nama akun. Oracle test_dashboard (V57): true utk acct###/revoter1/expire1; false utk newacct1 (BARU)/zzzold (kadaluarsa)/zzhide (tersembunyi)/asing; drift-guard endpoint==db + pool distribusi==db
V58: hardening distribusi vs node RPC mati: `config` += `API_NODES` (CSV `VEX_API_NODES`, default = `VEX_API_NODE` + `https://api.databisnis.id` — node mainnet terverifikasi, host sama dgn DATABISNIS_API tapi melayani juga `/v1/chain`). `distribute.py`: (1) `_post_json` mencoba SEMUA node per putaran (`max_retries` putaran penuh, jeda 2s antar putaran) → semua gagal → `RuntimeError` (bukan None); (2) `build_signed_transfer` coba tiap node utk `link()`+`sign()` (net di-bind ke node itu → broadcast `send()` ikut node sama) → semua gagal → raise; (3) `fetch_balance_with_retry(dry_run)` — real-run retry backoff `BALANCE_RETRY_DELAYS=[30,60,90,120,150,180]s` (~10.5 mnt) sebelum menyerah ke jadwal berikutnya; dry-run SATU attempt (gagal cepat, preview ⊥ tertahan). Net-effect: satu node mati → payout tetap jalan via cadangan; SEMUA node mati di real-run → jendela retry 10 mnt (bukan langsung abort) → lalu notify_failure + jadwal berikutnya; failed payments tetap rejoin run berikutnya (V22). Scan/claim ⊥ berubah (tetap `API_NODE` tunggal; fee klaim via `dist.build_signed_transfer` mewarisi failover gratis). Oracle test_distribute: V58 failover saldo (node mati → cadangan), semua-node-mati → raise, backoff retry-sampai-sukses + dry-run-satu-attempt, build_signed_transfer coba kedua node (mock pyntelope Net) | V58,V20,V22,V26
V58: hardening distribusi vs node RPC mati: `config` += `API_NODES` (CSV `VEX_API_NODES`, default = `VEX_API_NODE` + `https://api.databisnis.id` — node mainnet terverifikasi, host sama dgn HYPERION_API tapi melayani juga `/v1/chain`). `distribute.py`: (1) `_post_json` mencoba SEMUA node per putaran (`max_retries` putaran penuh, jeda 2s antar putaran) → semua gagal → `RuntimeError` (bukan None); (2) `build_signed_transfer` coba tiap node utk `link()`+`sign()` (net di-bind ke node itu → broadcast `send()` ikut node sama) → semua gagal → raise; (3) `fetch_balance_with_retry(dry_run)` — real-run retry backoff `BALANCE_RETRY_DELAYS=[30,60,90,120,150,180]s` (~10.5 mnt) sebelum menyerah ke jadwal berikutnya; dry-run SATU attempt (gagal cepat, preview ⊥ tertahan). Net-effect: satu node mati → payout tetap jalan via cadangan; SEMUA node mati di real-run → jendela retry 10 mnt (bukan langsung abort) → lalu notify_failure + jadwal berikutnya; failed payments tetap rejoin run berikutnya (V22). Scan/claim ⊥ berubah (tetap `API_NODE` tunggal; fee klaim via `dist.build_signed_transfer` mewarisi failover gratis). Oracle test_distribute: V58 failover saldo (node mati → cadangan), semua-node-mati → raise, backoff retry-sampai-sukses + dry-run-satu-attempt, build_signed_transfer coba kedua node (mock pyntelope Net) | V58,V20,V22,V26
V59: kirim per-pemilih ⊖ pernah menyesatkan: (1) TAHAN yang sungguhan saat `verify_txid` → None (Hyperion tak terjangkau): break + tandai `failed` di ATTEMPT PERTAMA (bukan hanya di attempt terakhir — B19: kode lama print `ditahan` tapi lanjut retry → re-sign dgn tapos/expiration baru → txid BARU → re-broadcast berpotensi ganda walau tx pertama mendarat; oracle lama lolos karena set `DISTRIBUTE_MAX_ATTEMPTS=1`); (2) deteksi penolakan chain dari body HTTP-500: pyntelope `send()` ⊥ raise pada 500 (pola V46/B11), respons `{"code":500,"error":...}` datang sbg dict — `_send_rejected(resp)` mengenali bentuk error → raise RuntimeError → jalur verify/retry, ⊖ tandai `sent` utk tx yang ⊥ pernah mendarat (B19 juga: tanpa deteksi, penolakan duplicate/insufficient-balance dicatat `sent` dan ⊥ pernah dikejar). Oracle test_distribute: V59 hold-atau-retry di attempt 1 (3 attempt default, build dipanggil sekali per pemilih), V59 rejection-500 → `failed` + partial (⊖ `sent`) | V59,V22,V21
V35: kill-switch distribusi `DISTRIBUTE_ENABLED` default false → run nyata (bukan dry-run) no-op: exit 0, ⊥ baca saldo, ⊥ tanda tangan, ⊥ tulis DB, ⊥ notif; `--dry-run` tetap menampilkan rencana (read-only); nilai `true`/`1`/`yes` → normal
V36: notifikasi klaim reward (sukses `notify_claim` & gagal `notify_claim_failure`) → kanal INTERNAL saja, ⊥ pernah ke KOMUNITAS (V30); fee gagal ⊥ spam — satu notif per klaim (saat transisi ke `failed`)
+2 -2
View File
@@ -30,7 +30,7 @@ import db
import telegram
import distribute as dist
from config import (API_NODE, BP_FEE_PERCENT, BP_FEE_WALLET, BP_PRIVATE_KEY,
CLAIM_RETRY_SECONDS, DATABISNIS_API, TARGET_BP,
CLAIM_RETRY_SECONDS, HYPERION_API, TARGET_BP,
TOKEN_CONTRACT)
VEX_PREC = Decimal('0.0001')
@@ -159,7 +159,7 @@ def reward_from_tx(txid):
if not txid:
return None
try:
resp = dist.requests.get(f'{DATABISNIS_API}/v2/history/get_transaction',
resp = dist.requests.get(f'{HYPERION_API}/v2/history/get_transaction',
params={'id': txid}, timeout=10)
resp.raise_for_status()
data = resp.json()
+7 -3
View File
@@ -20,7 +20,7 @@ API_NODE = os.getenv('VEX_API_NODE', 'https://v2.vexascan.com:2096')
# Node pool RPC (V58): CSV `VEX_API_NODES` — node utama + cadangan utk failover
# distribusi (fetch saldo, ABI/TAPOS, broadcast). Default = `API_NODE` +
# `https://api.databisnis.id` (node mainnet Vexanium terverifikasi, host sama
# dgn DATABISNIS_API tapi melayani juga /v1/chain). Dipakai `distribute.py`;
# dgn HYPERION_API tapi melayani juga /v1/chain). Dipakai `distribute.py`;
# scan/klaim tetap memakai `API_NODE` tunggal.
API_NODES = [
n.strip() for n in os.getenv(
@@ -77,8 +77,12 @@ VEX_REVOTE_TAG_DAYS = int(os.getenv('VEX_REVOTE_TAG_DAYS', '1'))
# SEGERA` (segera kadaluarsa, masih dibayar).
VEX_WARN_DAYS = int(os.getenv('VEX_WARN_DAYS', '3'))
# API databisnis untuk saldo liquid akun (balance BP, disajikan dashboard)
DATABISNIS_API = os.getenv('DATABISNIS_API', 'https://api.databisnis.id')
# Hyperion API untuk saldo liquid akun + verifikasi txid (balance BP,
# disajikan dashboard; verifikasi distribusi/klaim). `HYPERION_API` nama baru
# (V-rename); `DATABISNIS_API` masih dibaca utk backward-compat deploy lama.
HYPERION_API = (os.getenv('HYPERION_API')
or os.getenv('DATABISNIS_API')
or 'https://api.databisnis.id')
# Dashboard: TTL cache (detik) untuk saldo liquid — gagal fetch juga di-cooldown
DASH_LIQUID_TTL = int(os.getenv('DASH_LIQUID_TTL', '60'))
+2 -2
View File
@@ -14,7 +14,7 @@ import requests
from flask import Flask, abort, jsonify, render_template, request
import db
from config import (DASH_HOST, DASH_LIQUID_TTL, DASH_PORT, DATABISNIS_API,
from config import (DASH_HOST, DASH_LIQUID_TTL, DASH_PORT, HYPERION_API,
DISTRIBUTE_FIRST_RUN, DISTRIBUTE_HOUR, DISTRIBUTE_WEEKDAY,
PAGE_SIZE, TARGET_BP, VEX_EXPIRED_DAYS, VEX_MATURITY_DAYS,
VEX_REVOTE_TAG_DAYS, VEX_STALE_DAYS, VEX_WARN_DAYS)
@@ -213,7 +213,7 @@ def _get_liquid_vex():
value = None
try:
resp = requests.get(
f'{DATABISNIS_API}/v2/state/get_account',
f'{HYPERION_API}/v2/state/get_account',
params={'account': TARGET_BP},
timeout=8,
)
+2 -2
View File
@@ -20,7 +20,7 @@ import requests
import db
import telegram
from config import (API_NODES, BP_PRIVATE_KEY, DATABISNIS_API,
from config import (API_NODES, BP_PRIVATE_KEY, HYPERION_API,
DISTRIBUTE_ENABLED, DISTRIBUTE_MAX_ATTEMPTS, TARGET_BP,
TOKEN_CONTRACT, VEX_SYMBOL)
@@ -116,7 +116,7 @@ def verify_txid(txid):
Hyperion tak terjangkau (⊥ kirim ulang bila tak bisa dipastikan).
"""
try:
resp = requests.get(f'{DATABISNIS_API}/v2/history/get_transaction',
resp = requests.get(f'{HYPERION_API}/v2/history/get_transaction',
params={'id': txid}, timeout=10)
resp.raise_for_status()
return bool(resp.json().get('executed'))
+3 -3
View File
@@ -48,7 +48,7 @@ services:
DASH_HOST: "0.0.0.0" # wajib di container → gunicorn bind terbuka utk ingress
DASH_PORT: "5000"
DASH_WORKERS: "2"
DATABISNIS_API: ${DATABISNIS_API:-https://api.databisnis.id}
HYPERION_API: ${HYPERION_API:-https://api.databisnis.id}
DASH_LIQUID_TTL: "60"
TZ: ${TZ:-Asia/Jakarta}
DISTRIBUTE_HOUR: ${DISTRIBUTE_HOUR:-10}
@@ -112,7 +112,7 @@ services:
DISTRIBUTE_FIRST_RUN: ${DISTRIBUTE_FIRST_RUN:-2026-08-17}
DISTRIBUTE_MAX_ATTEMPTS: ${DISTRIBUTE_MAX_ATTEMPTS:-3}
DISTRIBUTE_ENABLED: ${DISTRIBUTE_ENABLED:-false}
DATABISNIS_API: ${DATABISNIS_API:-https://api.databisnis.id}
HYPERION_API: ${HYPERION_API:-https://api.databisnis.id}
volumes:
- /mnt/nfs/server5.saltis.id/data/databisnisid/app/config.env:/app/.env:ro
networks:
@@ -139,7 +139,7 @@ services:
CLAIM_RETRY_SECONDS: ${CLAIM_RETRY_SECONDS:-60}
VEX_BP_FEE_WALLET: ${VEX_BP_FEE_WALLET:-bpdbsjasprod}
VEX_BP_FEE_PERCENT: ${VEX_BP_FEE_PERCENT:-0.10}
DATABISNIS_API: ${DATABISNIS_API:-https://api.databisnis.id}
HYPERION_API: ${HYPERION_API:-https://api.databisnis.id}
volumes:
- /mnt/nfs/server5.saltis.id/data/databisnisid/app/config.env:/app/.env:ro
networks:
+12 -1
View File
@@ -441,13 +441,24 @@ def main():
check('V40 config default VEX_EXPIRED_DAYS', config.VEX_EXPIRED_DAYS == 3)
check('V43 config default VEX_REVOTE_TAG_DAYS', config.VEX_REVOTE_TAG_DAYS == 1)
check('V43 config default VEX_WARN_DAYS', config.VEX_WARN_DAYS == 3)
check('V16 config default DATABISNIS_API', config.DATABISNIS_API == 'https://api.databisnis.id')
check('V16 config default HYPERION_API', config.HYPERION_API == 'https://api.databisnis.id')
check('V16 config default DASH_LIQUID_TTL', config.DASH_LIQUID_TTL == 60)
check('V17 config default DB_BACKEND', config.DB_BACKEND == 'sqlite')
check('V17 config default DB_HOST/PORT', config.DB_HOST == '127.0.0.1' and config.DB_PORT == 3306)
check('V17 config default DB_USER/PASS/NAME',
(config.DB_USER, config.DB_PASS, config.DB_NAME) == ('', '', ''))
# ————— backward-compat: env lama DATABISNIS_API masih dihormati —————
import importlib
os.environ['DATABISNIS_API'] = 'http://hyperion-lama'
importlib.reload(config)
check('rename fallback env lama DATABISNIS_API dihormati',
config.HYPERION_API == 'http://hyperion-lama')
os.environ.pop('DATABISNIS_API', None)
importlib.reload(config)
check('rename default pulih stlh env lama dibuang',
config.HYPERION_API == 'https://api.databisnis.id')
# ————— V16: saldo liquid — cache TTL + cooldown kegagalan —————
dashboard._get_liquid_vex = real_get_liquid
dashboard._liquid_cache.update(at=0.0, value=None)