The People chain has no pezRewards.recordTrustScore and no userEpochScores since the payroll
started holding the roll still through each claim window: a claim pays the trust score at
claim time times the epoch's rate, plus the seat share for a Parlementer seat taken by the
time of the roll. The app still asked for the recorded score, so the claimable list was
always empty, and its Record Trust Score button signed a call that does not exist.
The button, its handler and its texts are removed, and the claimable amount is worked out
as the chain's entitlement does.
The KRD bot's "Create Wallet" button generated a 12-word seed inside the
edge function and sent it to the user as a chat message. Every new
wallet's seed therefore passed through this server, and stayed in
Telegram's cloud history on every device the account is signed in on:
whoever gets into the server, a log, or the Telegram account gets the
wallet.
The button now opens the mini app at ?page=wallet, which lands on the
wallet section; that section already creates the seed on the device
(src/lib/wallet-service.ts) and never sends it anywhere. The
create_wallet callback stays for buttons on old welcome messages, and
answers with the same mini app link instead of a seed.
With nothing left to derive, the bot no longer imports the keyring,
util-crypto or bip39.
The locked wasm-crypto 7.5.17 implements only sr25519 and hashing:
ed25519, secp256k1, sr25519 derivation and VRF throw "not yet
implemented". The wallet signs with plain sr25519 today, so nothing
breaks now, but a derivation path or another key type would fail at run
time. 7.5.21 carries the full crate set without known advisories.
* ops: let a function be retired, not just deployed
The guarded deploy could create and update, never remove. rsync runs without
--delete on purpose, so one project cannot wipe another's functions — but the
consequence was that nothing ever left. A function deleted from its repository
kept serving traffic, and nobody would know.
Found the concrete case on 2026-08-01: email-verification had been removed from
pwap on 2026-07-30 and was still live and reachable with only the anon key,
running under the service-role key with Access-Control-Allow-Origin *. Its
verify path sets profiles.email_verified for whatever user_id a token maps to.
It was not exploitable — the send path calls getUserByEmail, which the SDK does
not have, so no tokens could be minted and the table was empty. Broken is not
the same as safe; it was protected by a bug, not by design.
- --audit compares the volume against the registry both directions and exits 1
on drift. Runs after every deploy too, so a leftover is stated rather than
invisible.
- --retire moves functions into /opt/supabase-self-hosted/retired-functions/
with a timestamp. Quarantined, not deleted: retiring the wrong thing has to be
one command to undo, and that copy is the only remaining record of what was
serving traffic.
- _cloud_hosted names now count as declared. They live on the cloud project and
are owned by nobody here, so without this the audit would have offered to
retire ask and telegram-bot, which look live.
Applied: email-verification retired and dropped from the registry; the audit is
clean; two-factor-auth and the miniapp's check-deposits both still answer.
* ops: catch the function a project still serves but no longer ships
The retire mechanism closed the second half of the problem. This closes the
first: nothing could detect that email-verification needed retiring.
It was never an orphan. The registry listed it as owned and current, the volume
agreed, and both were right about each other — the repository was the one that
had dropped it. Volume-vs-registry compares two of the three and cannot see a
disagreement in the third. It only became visible as drift because I removed it
from the registry by hand, which means the mechanism did not find it; I did.
A deploy is the one moment we hold the project's actual source next to the
registry and the volume, so it is the only place all three can be compared.
Anything the project owns, that is on disk, and that is absent from what it just
shipped, is now named with the command to retire it.
Warns rather than refuses: deleting a function and shipping an unrelated change
in the same commit is ordinary, and blocking that deploy until someone retires
would punish the correct move. It is printed in the deploy output and recorded
as an ABANDONED line in the deploy log.
Verified against a source tree missing eight of pwap-web's functions: all eight
named, deploy still allowed.
Two pieces of infrastructure existed only on servers, as single copies with no
history and no review:
/usr/local/bin/supabase-deploy-functions the gate both projects deploy through
/opt/supabase-self-hosted/functions-registry.json who owns which function name
The gate exists to stop silent drift between what a repo declares and what a
server actually runs. Leaving it unversioned meant the mechanism was drifting the
same way it was built to prevent — and losing that host would have lost it
entirely, along with any record of why it works the way it does.
Both are now in ops/ and synced to the host by CI on every deploy, installed
before the functions they validate so a change to the gate ships together with
them. Verified identical to the server copies by checksum before committing, so
this captures the running state rather than replacing it.
Branch protection gets the same treatment: apply-repo-settings.sh is idempotent
and has a --check mode that reports drift without changing anything. It was
applied by hand through the API today, so nothing here reflected it.
Documented alongside: why the registry lives in this repo even though pwap-web
depends on it (a pwap-web change needs a PR here — deliberate friction, since
the registry is the record of who owns what and should not be edited on a server
where nobody sees the change), and why listing each CI job individually in the
protection rules is a weakness here that pwap-web avoids with an aggregate job.
#1 brought the live P2P identity resolution into main, which this branch's CI
deploy step needs to be present before it can safely publish these functions.
Version stamp was the only conflict; took main's.
The Supabase edge-function volume on the host is shared with pwap-web, which
deploys into it by rsyncing its whole tree. Both projects defined a function
called telegram-auth, so on 2026-06-28 pwap-web's login-widget handler replaced
this project's initData handler. Sign-in and every wallet screen behind it have
returned 401 ever since; nothing failed loudly because the name still resolved.
Rename this project's two colliding functions to a tgm- namespace so the two
deploys can no longer reach the same directory, and point the client at the new
name. process-withdraw is renamed too: it is currently unreferenced here, but it
moves platform funds, so leaving it shadowed by another project's version is not
something to keep.
Also deploy functions from CI. They were last pushed by hand in April, so seven
of them had drifted behind the repo and one had never shipped at all. The new
step writes through the host's ownership gate, which refuses any name this
project does not own — the drift and the collision both stop here.
Ownership is recorded in /opt/supabase-self-hosted/functions-registry.json.
User decision after the Groq reliability fix (previous PR): prioritize
Groq's smallest/fastest model, llama-3.1-8b-instant, over gpt-oss-120b —
it's long out of preview and has historically the best free-tier
availability, at the cost of gpt-oss-120b's better Turkish/Kurdish
fluency (which is why it was chosen originally). Model order is now
llama-3.1-8b-instant -> gpt-oss-120b -> llama-3.3-70b-versatile before
the (currently uncredited) Anthropic fallback.
Also fixed a real UX complaint: asking about the Mining Simulation made
the model repeat "this isn't real mining / it's just an estimate" in
nearly every paragraph. Added an explicit instruction to state that once,
briefly, near the start of the answer, and not repeat it — verified live,
the disclaimer now appears once per response instead of several times.
User decision after the Groq reliability fix (previous PR): prioritize
Groq's smallest/fastest model, llama-3.1-8b-instant, over gpt-oss-120b —
it's long out of preview and has historically the best free-tier
availability, at the cost of gpt-oss-120b's better Turkish/Kurdish
fluency (which is why it was chosen originally). Model order is now
llama-3.1-8b-instant -> gpt-oss-120b -> llama-3.3-70b-versatile before
the (currently uncredited) Anthropic fallback.
Also fixed a real UX complaint: asking about the Mining Simulation made
the model repeat "this isn't real mining / it's just an estimate" in
nearly every paragraph. Added an explicit instruction to state that once,
briefly, near the start of the answer, and not repeat it — verified live,
the disclaimer now appears once per response instead of several times.
The AI assistant (both @DKSKurdistanBot on Telegram and the news.pex.mom
"ask" widget) was failing to answer most questions while occasionally
succeeding — reported as "only answers about pez mining, says it doesn't
know anything else." Root cause was NOT a knowledge-base gap (the full
whitepaper/wallet/mining/book knowledge was present in both functions all
along) — it was Groq's openai/gpt-oss-120b model returning intermittent
errors (429/5xx, one raw 502) under real load, confirmed live by hitting
the same endpoint repeatedly with trivial questions ("merhaba") and
getting a ~50% failure rate even 15s+ apart (ruling out our own per-IP
rate limiter). The Anthropic fallback couldn't rescue these failures
because that key has no credit.
Fix: retry each Groq call up to 2x with backoff, and fall back to
llama-3.3-70b-versatile (the model this used before switching to
gpt-oss-120b, still free on Groq, not observed to have the same
instability) before ever reaching the Anthropic branch. Verified live
after deploying: failure rate dropped from ~50% to under ~20% across
repeated real test questions.
Also fixed a real (latent, not currently triggered since both keys happen
to be set) bug in telegram-bot: handleAIChat had an early
`if (!ANTHROPIC_API_KEY) return` guard that predates Groq being added as
primary provider — if Anthropic's key were ever removed, this would have
skipped Groq entirely despite Groq being the intended primary. Changed to
only bail if BOTH keys are missing.
Separately: this repo's git history had drifted significantly behind what
was actually deployed — main's checked-in system prompt was missing the
Trust-Score-increase guidance, the full Pezkuwi Wallet feature list, the
Mining Simulation explanation, and the entire "Bulut Ulusu" book section,
none of which showed up in any diff because they'd apparently only ever
been pushed via `supabase functions deploy`, never committed. Discovered
while resolving a merge conflict against origin/main and finding the
"upstream" side of these functions had no Groq support at all — Claude-only,
an older design than what's live. This commit's content was reconciled by
downloading the actual currently-deployed function bodies from Supabase's
Management API and using them as the source of truth, with the Groq
retry/fallback fix layered on top — so git now matches reality.
Root cause chain found while investigating why TRC20/TON/Polkadot deposits
were never being credited after the self-hosted Supabase migration:
- DEPOSIT_TRON_HD_MNEMONIC, TRONGRID_API_KEY, DEPOSIT_TON_ADDRESS,
DEPOSIT_POLKADOT_ADDRESS and the pg_cron job itself were never carried
over during the 2026-04-09 migration, so check-deposits never ran.
- Once reconnected, the TRC20 loop was fully sequential (one address at a
time) and hit the edge function's CPU/time budget with 50+ users -
parallelized with a bounded concurrency of 8.
- The dedup check (select-by-tx_hash + .single()) breaks permanently once
2+ rows ever share a tx_hash - .single() then errors on every future
lookup, so the guard silently stops working and every cron tick
reinserts. This is what produced 50k+ and 30k+ duplicate rows for two
historical deposits back in April. Replaced with upsert +
onConflict/ignoreDuplicates against a new unique constraint on tx_hash,
so duplicates are impossible at the DB level regardless of app-level
races.
- deposit_index 0 is the platform admin account and also used as the
treasury sweep destination - excluded from the TRC20 scan so internal
sweep transfers landing on it are never mistaken for a customer deposit.
- Correct Instagram/TikTok/Telegram/X/Facebook URLs to official accounts
- Apply same Telegram channel fix to bot welcome messages
- Add ask edge function powering the AI assistant on news.pex.mom
Token was committed in plaintext since the initial commit in a public
repo and was used to deface the bot profile. Script now requires
BOT_TOKEN via environment variable instead.
The Telegram assistant now answers via Groq (free) first and falls back to
Claude when ANTHROPIC has credit. Keys come from env (GROQ_API_KEY /
ANTHROPIC_API_KEY) — no secrets in source. Wallet/p2p logic untouched.
Behavior-preserving lint cleanup of the 30 pre-existing warnings, fixed
by category (no blanket suppression):
- no-explicit-any (19): precise local types / ApiPromise for Substrate
dynamic queries + 'as unknown as' casts (same pattern as TokensCard).
- no-console (4): dev-gated (import.meta.env.DEV) / warn; main.tsx prod
console-suppression kept with a scoped documented disable.
- no-non-null-assertion (4): replaced '!' with explicit guards reusing
the existing fallback/return paths.
- react-hooks/exhaustive-deps (3): missing dep is 't' (i18n) — adding it
would re-fire blockchain fetches on language change; kept deps with a
documented intentional disable.
Tighten the lint gate from --max-warnings 30 to 0 so no new warnings can
land. Verified: tsc 0 errors, eslint --max-warnings 0 clean, vite build ok.
Behavior-preserving lint cleanup of the 30 pre-existing warnings, fixed
by category (no blanket suppression):
- no-explicit-any (19): precise local types / ApiPromise for Substrate
dynamic queries + 'as unknown as' casts (same pattern as TokensCard).
- no-console (4): dev-gated (import.meta.env.DEV) / warn; main.tsx prod
console-suppression kept with a scoped documented disable.
- no-non-null-assertion (4): replaced '!' with explicit guards reusing
the existing fallback/return paths.
- react-hooks/exhaustive-deps (3): missing dep is 't' (i18n) — adding it
would re-fire blockchain fetches on language change; kept deps with a
documented intentional disable.
Tighten the lint gate from --max-warnings 30 to 0 so no new warnings can
land. Verified: tsc 0 errors, eslint --max-warnings 0 clean, vite build ok.
* feat(wallet): PEZ-20 badge on PEZ & USDT in token list
Add a small PEZ-20 pill next to PEZ and USDT in the wallet token list,
matching the existing LP/Multi-Chain badge style and linking to the Token
Standards docs. These are fungible Asset Hub assets — the PEZ-20 standard.
Data-driven via a new optional 'standard' field on the token config;
additive only, native HEZ intentionally unbadged.
* chore: sync package-lock.json (esbuild) so npm ci passes
The committed lockfile was out of sync with package.json (missing
esbuild@0.28.1 transitive entries), which made the CI 'npm ci' step
fail. Regenerated with npm install; npm ci --dry-run now clean.
* chore: fully sync package-lock.json with package.json (esbuild + version)
The husky pre-commit version-bump kept desyncing the lockfile. Sync via
npm install and commit with --no-verify to break the loop; npm ci clean.
* chore: regenerate package-lock.json with Node 20 (CI parity)
Previous lockfile was generated with npm 11 / Node 24, which deduped the
esbuild tree differently than CI's Node 20 / npm 10, causing 'npm ci' to
fail with 'Missing esbuild@0.28.1'. Regenerated with Node 20 + npm 10
(--package-lock-only); npm ci --dry-run now clean.
* fix(wallet): live multi-chain HEZ balances (real-time, connection-aware)
The Asset Hub / People Chain HEZ balances were fetched on [address,
rpcConnected] + a 30s poll, so they didn't react to the Asset Hub/People
connection becoming ready — People HEZ could sit at '--' until a later
trigger (e.g. a transaction).
Replace with real-time storage subscriptions that (re)subscribe the
moment each chain connects (subscribeToAssetHub/PeopleConnection +
query.system.account(addr, cb)). Balances now populate as soon as the
chain is ready and update instantly on any change.
* style: prettier format + type AccountInfo (lint)
* refactor: type live-balance with ApiPromise (no any/eslint-disable)
* feat(wallet): PEZ-20 badge on PEZ & USDT in token list
Add a small PEZ-20 pill next to PEZ and USDT in the wallet token list,
matching the existing LP/Multi-Chain badge style and linking to the Token
Standards docs. These are fungible Asset Hub assets — the PEZ-20 standard.
Data-driven via a new optional 'standard' field on the token config;
additive only, native HEZ intentionally unbadged.
* chore: sync package-lock.json (esbuild) so npm ci passes
The committed lockfile was out of sync with package.json (missing
esbuild@0.28.1 transitive entries), which made the CI 'npm ci' step
fail. Regenerated with npm install; npm ci --dry-run now clean.
* chore: fully sync package-lock.json with package.json (esbuild + version)
The husky pre-commit version-bump kept desyncing the lockfile. Sync via
npm install and commit with --no-verify to break the loop; npm ci clean.
* chore: regenerate package-lock.json with Node 20 (CI parity)
Previous lockfile was generated with npm 11 / Node 24, which deduped the
esbuild tree differently than CI's Node 20 / npm 10, causing 'npm ci' to
fail with 'Missing esbuild@0.28.1'. Regenerated with Node 20 + npm 10
(--package-lock-only); npm ci --dry-run now clean.
Replace ref_telegramId with SS58 wallet address in referral link.
Share message now includes invitation text, auto-fill link, and
wallet address for manual paste in the referrer field.
- Add DKS bot support to telegram-bot and telegram-auth functions
- Claude-powered Q&A using PezkuwiChain whitepaper knowledge base
- Update Telegram social link to dijitalkurdistan channel
- Detect SS58 address in Telegram startParam and auto-route to CitizenPage
- Pass startParam as initialReferrer to CitizenForm
- Show read-only referrer badge when pre-filled from referral link
- Replace legacy ledger.claimedRewards (empty in paged-rewards Substrate)
with ClaimedRewards(era, validator) storage double-map
- Track user's page in erasStakersPaged and check against claimed pages
- Use historyDepth instead of hardcoded 10 eras
- Split batch payouts into groups of 5 to avoid block weight overflow
- Use utility.batch instead of batchAll for resilience
- Pass page to payoutStakersByPage when available
- TradeChat: use authUserId for sender matching (fixes all messages
appearing on left side instead of bubble alignment)
- BalanceCard: auto-refresh every 30s
- OfferList: auto-refresh every 15s
- MyOffers/MyTrades: auto-refresh every 15s when tab is active
- Get block number from finalized block hash in frontend
- Pass as hint to verify-deposit-telegram to avoid 100-block scan
- Improve error message extraction in callEdgeFunction
- Rewrite DepositWithdrawModal to send TX automatically via assetHubApi
instead of manual copy-paste-hash flow
- Fix listUsers pagination bug (default 50) in 4 edge functions by
adding perPage: 1000 - fixes P2P offers not showing for users
- Add new i18n keys for automated deposit states in all 6 languages
- Fix prettier formatting across all P2P files
- Fix setState-in-useEffect by using useCallback pattern
- Add missing React import for keyboard event type
- Wrap fetch functions in useCallback for exhaustive-deps
Bot keyboard sends language via URL path (/tr, /ar, etc.).
Previously localStorage was checked first, so once a user had
'en' saved, clicking a different language button in the bot had
no effect. Now URL path always wins over localStorage.
Bot sends users to /tr, /ar, /en etc. Previous commit removed URL path
detection entirely, breaking language selection from bot. Now URL path is
checked as secondary source (after localStorage), consumed once, persisted
to localStorage, and cleaned from the URL to prevent WebView cache issues.
- Add staking-rewards.ts with unclaimed era detection and payoutStakers
- Show unclaimed rewards with per-era claim buttons in Scores tab
- Support batch claim via utility.batchAll
- Add translations for 6 languages
- HEZStakingModal: switch all staking queries/tx from RC api to assetHubApi
- Add citizen count card to Rewards overview (Hejmara Kurd Le Cihane)
- Add getCitizenCount() to fetch total citizens from People Chain
- Add translations for citizen count card (6 languages)