Round 2 — fixes the CRITICAL discovered during round 1 (as severe as the withdrawal
BOLA): lock_/release_/refund_escrow_internal had default PUBLIC EXECUTE and the client
called release_escrow_internal directly with the anon key, so anyone could drain any
victim's LOCKED balance.
- release_escrow_internal now reachable only via new signed confirm-payment edge
function: verifies seller wallet signature (raw + <Bytes>), asserts the wallet owns
the seller identity, derived user_id == trade.seller_id, trade == payment_sent,
single-use nonce, then release runs with the service role. confirmPaymentReceived
now invokes confirm-payment (no more anon rpc).
- lock_escrow_internal now behind new signed lock-escrow edge function (a caller can
only lock its own balance; stops griefing a victim's balance). Removed a zero-amount
no-op lock call.
- Migration 20260725030000: REVOKE EXECUTE on lock_/release_/refund_escrow_internal
from PUBLIC/anon/authenticated; GRANT to service_role only. refund has no direct
client caller (service-role + admin_resolve_dispute only).
- DEPLOY_RUNBOOK.md consolidates the ordered migrations, edge functions and secrets
for both security rounds. Migration 20260725030000 must ship WITH the two new edge
functions + frontend or offer-create/payment-release break.
Remaining (non-fund, Round 2+ follow-up): read RPCs still key on a non-secret user_id
(binding to a signed session would force a sign-prompt on every passive balance read —
deferred); p2p_fiat_offers/trades/messages retain USING(true) (status labels move no
funds now that all escrow movement is service-role-gated).
Critical/high audit remediation on the custodial P2P ledger. Auth model is
wallet-based; identity (citizen/visa) is cryptographically bound to a wallet
(People Chain tiki.citizenNft / active p2p_visa), which enables a correct fix.
- Withdrawal BOLA (CRITICAL): process-withdraw now requires a wallet-SIGNED
challenge; server verifies signature (raw + <Bytes> forms), asserts the signer
OWNS the identity, consumes a single-use nonce (replay), and derives user_id
server-side — the client-supplied user_id is ignored. process-withdrawal (batch)
now requires the service-role key (was: any bearer, incl. public anon key).
request_withdraw is REVOKEd from anon/authenticated + service-role guarded.
- Financial RLS (HIGH): drop the blanket USING(true) SELECT on user_internal_balances,
p2p_balance_transactions, p2p_deposit_withdraw_requests; lock p2p_user_payment_methods
(IBAN PII) + p2p_fiat_disputes UPDATE to service_role; legitimate reads move behind
scoped SECURITY DEFINER RPCs.
- Deposit integrity: verify-deposit now binds the on-chain sender to identity ownership
before crediting.
- Admin dispute (CRITICAL fund-logic): DisputeResolutionPanel relabeled trades without
moving escrow. New admin-signed resolve-dispute function + admin_resolve_dispute RPC
moves escrow (release/refund/split) atomically with correct accounting (avoids the
double-count in the legacy resolve_p2p_dispute). Client isAdmin documented as cosmetic.
DEPLOY RUNBOOK (gated; owner runs): 1) apply migrations 20260225/20260725* in order;
2) deploy edge functions process-withdraw, process-withdrawal, verify-deposit, resolve-dispute;
3) set edge secrets PEOPLE_RPC_ENDPOINT (+ optional ADMIN_WALLETS); 4) ship frontend.
Migrations + functions + frontend must go together or the app breaks.
KNOWN RESIDUAL (Round 2 — as severe as the withdrawal BOLA): release_/lock_/refund_
escrow_internal still have PUBLIC EXECUTE and the client calls release_escrow_internal
directly with the anon key from confirmPaymentReceived -> an anon caller can drain any
victim's LOCKED balance. Fix = a wallet-signed confirm-payment edge function (same
pattern as withdrawals) before revoking PUBLIC execute. Not yet fixed.
Fund-logic hardening (audit remediation):
- TransferModal: source asset id/decimals from the canonical MINTABLE_ASSETS
(wUSDT=1000, wDOT=1001, wETH=1002, wBTC=1003) instead of the wrong hardcoded
ids (BTC=3 was the deprecated "Old USDT", ETH=4, DOT=5). Route every non-native
asset through the Asset Hub api; only native HEZ uses the relay balances pallet.
Removes the latent wrong-asset / wrong-chain transfer.
- Replace BigInt(parseFloat(x)*10**dec) with string-based parseTokenInput in
TransferModal + 3 DEX init modals; BigInt(float) threw RangeError on common
fractional amounts (e.g. 100.3 HEZ), breaking sends and pool/bridge init.
- Presale.contribute now resolves a signer via getSigner and passes { signer }
to signAndSend (was signer-less -> broken for all users).
- presale.ts contribute/refund/claimVested now reject on dropped/invalid/usurped/
retracted tx states instead of hanging the UI forever.
STANDARD_RENEWAL_TOPUP still had the bot's old built-in default (10,000).
The live pezbridge_bot_config.json and the Android wallet's
BridgeMultisigConstants both renew to 200,000 (threshold 40,000) - and all
three signing channels must build the same call, or the hash differs and
real pending renewals stop auto-matching in the /multisig/pending UI.
- calculateMultisigAddress was completely broken (hex-decoded an SS58 string
and never hashed the preimage) - fixed via @pezkuwi/util-crypto's real
encodeMultiAddress/createKeyMulti, verified against the actual known
multisig address on-chain.
- ReservesDashboardPage had stale Noter/Berdevk addresses that don't match
the real signers - centralized as BRIDGE_MULTISIG_SPECIFIC_ADDRESSES.
- USDTBridge withdrawal called assets.burn directly as a single-signer
extrinsic (always fails - only the multisig is Admin) while only
checking status.isFinalized (a failed dispatch is still finalized, so it
silently did nothing) - replaced with the correct transfer-to-custody
flow the relayer actually watches for.
- New MultisigOperationsPage (/multisig/pending) lists pending calls from
real Multisig.Multisigs storage and lets any of the 5 signers
approve/reject with their own wallet extension.
- New standalone sign/ app (deployed separately at
pezbridge-sign.pex.mom) - a dedicated, gated signing portal for the same
operations, so signing isn't dependent on this app alone.
1. Fixed enum map missing NotStarted at index 0, causing all status
indices to be off by one (ReferrerApproved decoded as PendingReferral)
2. Restricted founder bypass to only show applications with no referrer,
not all applications regardless of referrer
Replace hardcoded SS58 prefix 42 with api.registry.chainSS58 ?? 42
for robustness in getPendingApprovalsForReferrer, getReferralInfo,
and getMyReferrals functions.
toJSON() for Substrate enums can return string, object ({"approved":null}),
or number (enum index). Previous code assumed string only, causing already
approved applications to appear as pending.
toJSON() returns hex format for AccountId fields but comparison was
against SS58 addresses, causing referrer matching to always fail.
- citizenship-workflow: encodeAddress for referrer in getPendingApprovals
- citizenship-workflow: handle both PascalCase and camelCase KycStatus
- referral: encodeAddress for referrer in getMyReferrals and getReferralInfo
Supabase JS client wraps non-2xx responses as generic FunctionsHttpError
("Edge Function returned a non-2xx status code"), hiding the real error.
Now reads the response body to show the actual error message.
Also adds migration to drop auth.users FK on p2p_withdrawal_limits
(already absent in production, added for migration completeness).
- Upgrade @pezkuwi/api 16.5.11 -> 16.5.36 in supabase edge functions
- Remove manual SS58-to-hex workaround, use native SS58 addresses
- Add kurdistan flag and Dijital Kurdistan images
- Add PezkuwiExplorer to web public assets
- Remove unused react-logo and telegram_welcome images
- Add *.bak to gitignore
PostgrestError is not instanceof Error, so catch blocks were falling
through to generic messages. Now extracts .message and .details from
Supabase errors for better debugging.
- requestWithdraw() now calls process-withdraw edge function instead of
just locking balance in DB. This triggers the full flow: lock balance,
send blockchain TX, complete withdrawal.
- Balance card: numbers right-aligned, 2 decimal places instead of 4
- Added public SELECT RLS policy on payment_methods table (was blocking
anon users from loading payment method dropdown)
Replace all supabase.auth.getUser() calls with P2PIdentityContext that
resolves identity from on-chain citizen NFT or off-chain visa system.
- Add identityToUUID() in shared/lib/identity.ts (UUID v5 from citizen/visa number)
- Add P2PIdentityContext with citizen NFT detection and visa fallback
- Add p2p_visa migration for off-chain visa issuance
- Refactor p2p-fiat.ts: all functions now accept userId parameter
- Fix all P2P components to use useP2PIdentity() instead of useAuth()
- Update verify-deposit edge function: walletToUUID -> identityToUUID
- Add P2PLayout with identity gate (wallet/citizen/visa checks)
- Wrap all P2P routes with P2PLayout in App.tsx
Replace all web3FromAddress calls with getSigner() that auto-detects
walletSource and uses WC signer or extension signer accordingly.
This fixes all signing operations when connected via WalletConnect.
- Compact stat cards on mobile (Dashboard, Referral, P2P)
- Hide unnecessary sections on mobile (Recent Activity, NFTs, Score Calculation, Liquidity Pools, Recent Swaps)
- Fix back arrow overlapping title on all pages
- Swap Settings and Governance nav positions for better mobile dropdown
- Add back arrow to Presale page
- Add web3Enable before all web3FromAddress calls for WalletConnect compatibility
- Fix citizenship authentication signing with WalletConnect
- shared/staking.ts: update for AH (remove babe dep, remove validatorPool, bonding 2 eras)
- StakingDashboard: switch all staking operations from api (RC) to assetHubApi (AH)
Application struct has no status field - was filtering by
appData.status === 'PendingReferral' which always returned false.
Now checks kycStatuses storage instead, and allows founder to see
all pending applications.
- Update staking score/tracking calls from relayApi to peopleApi
- Fix referral score to use on-chain tiered scoring with penalties
- Fix perwerde score to query studentCourses + enrollments storage
- Update Dashboard and StakingDashboard for People Chain API
- Remove Asset Hub and People Chain endpoints from Zagros/Development config (relay chain only)
- Default network now always mainnet
- PEZ Rewards card only renders when pallet is available on chain
- Dynamic grid layout when PEZ Rewards card is hidden
Remove all frontend staking/trust score calculation and localStorage
fallback code. All scores now read directly from People Chain pallets
(pezpallet-trust, pezpallet-referral, pezpallet-tiki). Trust pallet
computes composite score on-chain.
- getAllTikiNFTDetails now calls fetchUserTikiNFTs to query UserTikis storage
- This enables proper authorization check for government roles like Serok
On-chain trust pallet exists but StakingInfoProvider returns None,
causing trust score to be 0 even when user has stake. Use frontend
calculation until runtime upgrade is deployed.
- pezRewards pallet is on People Chain, not Relay Chain
- stakingScore pallet is also on People Chain
- Update getStakingInfo to accept optional peopleApi parameter
- Update StakingDashboard to pass peopleApi
- Remove LP staking score (not available in blockchain)
- All scores now fetched from People Chain
- Add Start Score Tracking button to Dashboard
- Add staking status display showing tracking duration
- Add HEZ/DOT pool to PoolDashboard
- Display DOT, ETH, BTC instead of wDOT, wETH, wBTC to users
- Update priceOracle with correct symbol mappings
- Fix lint errors in check_all_pools.mjs
- Extract MINTABLE_ASSETS to separate file for fast refresh
- Add priceOracle service for fetching CoinGecko prices
- Update SwapInterface to use oracle prices instead of pool reserves
- All swaps route through USDT as base currency
- Multi-hop routing for non-USDT pairs (X → USDT → Y)
- Display real-time USD prices from CoinGecko
- Auto-refresh prices every 30 seconds