48 Commits

Author SHA1 Message Date
pezkuwichain 0651b80eb7 fix: sync renewal topup with the live bridge config (10K -> 200K wUSDT)
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.
2026-07-19 01:48:13 -07:00
pezkuwichain dd58fe9164 Add multisig pending-operations UI, fix broken multisig address calc, new signing portal
- 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.
2026-07-14 07:19:34 -07:00
pezkuwichain fe3c65a706 governance: redesign Assembly into on-chain parliament landing (6 languages)
Replace the placeholder Assembly page with a professional on-chain governance
landing (animated 201-seat hemicycle, live chain ticker, blocs, sessions, active
votes, features). Content is fully localized into the 6 standard app languages
(en/tr/kmr/ckb/fa/ar) with RTL for ckb/fa/ar; each language variant is lazy-loaded.
2026-06-30 23:27:19 -07:00
pezkuwichain ed07878646 fix(auth): make password reset work via Supabase native recovery flow (#19)
PasswordReset called a non-existent 'password-reset' edge function, so
users could never reset a forgotten password. Switch to Supabase Auth's
built-in recovery: resetPasswordForEmail() to request the email, and
updateUser({password}) within the PASSWORD_RECOVERY session to set the
new password. Generic success message (no account enumeration); sign out
after reset to force clean re-login.
2026-06-24 16:58:12 -07:00
pezkuwichain 7dddae633a identity: remove unused issueDate const (lint fix) 2026-06-20 18:28:38 -07:00
pezkuwichain a92d61db8a identity: redesign e-ID + passport to match the Digital Kurdistan brand
Rebuild the /identity ID card and passport as flip cards rendered fully in
code (HTML/CSS/SVG) with each citizen's real data:

- e-ID: holographic light card, front (BÊ KURDISTAN JÎYANE NÎNE, photo, gold
  chip, bilingual fields NAV/NAME · PASNAV/SURNAME · DATE OF BIRTH ·
  NATIONALITY KURDISTANÎ/KURDISH · ID NUMBER, faux-QR, e-ID: KOMARA KURDISTAN
  footer) + back (DIGITAL KURDISTAN STATE: ID number, digital wallet ID,
  biometric status, valid-until, gov services, support; authority seal).
- Passport: navy cover with the gold ram (mouflon) emblem + Sorani/English
  titles, flips to a holographic data page (flag stripe, bilingual fields,
  photo, ICAO-style MRZ).
- Adds a Surname field and pulls the connected wallet address for the e-ID
  back. Document labels are the official bilingual set (not UI-translated);
  data stays device-local. Tap ↻ to flip.
2026-06-20 17:53:56 -07:00
pezkuwichain 6e55418703 assets: add pezkuwi-global-union image (for upcoming Global Union surface) 2026-06-20 06:35:35 -07:00
pezkuwichain 56b442fdff chore(web): drop dead bundled docs — app uses external docs.pezkuwichain.io
The in-app /docs route (web/src/pages/Docs.tsx, since c56e021a, Apr 2026) only
redirects to https://docs.pezkuwichain.io; it no longer reads the local
docs-structure.json or web/public/docs/*.md. The 907 bundled markdown + rustdoc
files under web/public/docs and web/public/sdk_docs are generated from
Pezkuwi-SDK by generate-docs-structure.cjs and are not served at runtime, so
they are removed from tracking and gitignored to stop the recurring tree churn.
2026-06-20 06:05:51 -07:00
pezkuwichain 35d7ab38fa i18n: translate the P2P/Buy-Sell nav label per language
The Finance 'P2P/Buy-Sell' tile label was the hardcoded Latin string
'P2P/Buy-Sell' in all six locales, so it stayed untranslated (English) when
switching to Sorani/Farsi/Arabic/Turkish/Kurmancî. Keep the universal 'P2P'
token and translate the buy-sell part (TR Al-Sat, KMR Kirîn-Firotin,
CKB کڕین-فرۆشتن, FA خرید-فروش, AR بيع-شراء).
2026-06-20 05:57:19 -07:00
pezkuwichain 070d682759 feat(p2p): rename Finance tile to P2P/Buy-Sell + add 'Buy with Visa' nav action
The Finance section tile label becomes 'P2P/Buy-Sell'. In the P2P dashboard's
top nav, next to the Messages icon, a Visa card (CreditCard) action 'Buy with
Visa' opens buy-sell.pezkiwi.app in a new tab. Adds p2pNav.buyVisa in all 6
locales.
2026-06-19 17:37:14 -07:00
pezkuwichain cd56ab8fb6 feat(social): replace Help tile with Loto (Newroz) → loto.pex.mom
In the Social section of both the desktop app grid and the mobile home, the Help
tile becomes a Loto tile with the Newroz flame logo (loto-icon.svg) that opens
loto.pex.mom in a new tab. Adds the mobile.app.loto i18n key in all 6 locales.
Help remains reachable from the landing page.
2026-06-19 17:32:57 -07:00
pezkuwichain b012fcaaac fix(security): patch ws (high DoS) and dompurify (XSS) via npm audit fix
Unblocks the deploy security gate — production deps only, no major bumps.
2026-06-15 18:07:37 -07:00
pezkuwichain 7a1d3e7917 feat(social): wire DKS Rojname → news.pex.mom and Events → kurdishtts.pezkiwi.app
- KurdMedia: DKS Rojname channel now links to the Dijital Kurdistan News site
- Social Events item opens the Kurdish TTS app (was coming-soon/locked)
- applies across mobile drawer, mobile home and desktop landing pallets
2026-06-15 17:54:19 -07:00
pezkuwichain 2ee3caac0d fix(ci): audit only production deps in the deploy gate (--omit=dev) (#18)
The security-audit gate ran 'npm audit --audit-level=high' over all deps,
so newly-published advisories on build-only tooling (esbuild, elliptic via
vite-plugin-node-polyfills, etc.) repeatedly blocked production deploys
even though that code ships to no user. Scope the gate to production
dependencies with --omit=dev. Verified: 'npm audit --audit-level=high
--omit=dev' → 0 vulnerabilities. TruffleHog secret scanning is unchanged.
2026-06-12 23:39:55 -07:00
pezkuwichain 78e93e9766 feat(web): PEZ-20 badge on PEZ & USDT balance cards (#17)
* fix(ci): unblock deploy pipeline (audit gate + orphan submodule)

The Quality Gate & Deploy pipeline was failing at security-audit
(npm audit --audit-level=high), which blocks telegram-gate and the
whole deploy chain — that is why production was serving a stale bundle.

- npm audit fix (no --force, lockfile only): clears the critical vitest
  advisory (GHSA-5xrq-8626-4rwp) and the high elliptic one; only low-
  severity items remain, so 'npm audit --audit-level=high' now exits 0.
- Remove the orphaned 'exchange' gitlink: it is an empty submodule
  pointer with no .gitmodules mapping, which made git print
  'fatal: no submodule mapping found' during checkout.

Verified: lint, test (32 passed), and vite build all pass; audit gate
is green. No package.json changes.

* feat(web): PEZ-20 badge on PEZ and USDT balance cards

Add a small reusable Pez20Badge pill next to the PEZ and USDT tokens in
the wallet balance view, linking to the Token Standards docs. These are
fungible assets on Asset Hub, i.e. the PEZ-20 standard — this gives users
the familiar ERC-20-style mental model at a glance.

Additive only: no labels removed, native HEZ is intentionally not badged
(it is the native/gas token, not a PEZ-20 asset).
2026-06-12 23:28:05 -07:00
pezkuwichain 83d66feacc fix(ci): unblock deploy pipeline (audit gate + orphan submodule) (#16)
The Quality Gate & Deploy pipeline was failing at security-audit
(npm audit --audit-level=high), which blocks telegram-gate and the
whole deploy chain — that is why production was serving a stale bundle.

- npm audit fix (no --force, lockfile only): clears the critical vitest
  advisory (GHSA-5xrq-8626-4rwp) and the high elliptic one; only low-
  severity items remain, so 'npm audit --audit-level=high' now exits 0.
- Remove the orphaned 'exchange' gitlink: it is an empty submodule
  pointer with no .gitmodules mapping, which made git print
  'fatal: no submodule mapping found' during checkout.

Verified: lint, test (32 passed), and vite build all pass; audit gate
is green. No package.json changes.
2026-06-11 18:42:45 -07:00
pezkuwichain d6ace14e70 fix(web): live collator/nominator counts after AHM + reliable B2B redirect (#15)
Staking migrated to Asset Hub (AHM), but the landing page still read
nominators from the relay (api.query.staking.counterForNominators),
which is now empty there — so the count showed '—'. Collators were read
from collatorSelection.candidates (empty; collators are invulnerables)
and only on Asset Hub, missing the People chain set.

- Nominators: query Asset Hub staking.counterForNominators (verified 30).
- Collators: count collatorSelection.invulnerables on both Asset Hub and
  People chain (2 + 2), tracked per-chain and summed.
- NetworkStats.tsx already used the correct sources; this aligns the
  landing page with it.

B2B button (/bereketli SSO interstitial): if there is no Supabase session
or the token exchange fails, redirect to https://bereketli.pezkiwi.app
instead of stranding the user on app.pezkuwichain.io/bereketli. (The
backend CORS allowlist was also missing app.pezkuwichain.io; fixed
server-side so the SSO exchange itself now succeeds.)
2026-06-11 16:41:14 -07:00
pezkuwichain 2cbfd21539 fix(cosign): explicit GHCR login before sign + verify
docker/login-action writes ~/.docker/config.json but cosign on self-
hosted runner does not always read it. Add 'cosign login ghcr.io'
before sign (build-image) and verify (deploy-app, deploy-pex) so the
registry blob upload/download authenticates correctly.

The previous run signed via Sigstore (Fulcio cert + Rekor tlog entry
created) but failed at the final 'push signature blob to GHCR' step
with UNAUTHORIZED. Explicit cosign login solves this.
2026-05-09 13:41:29 +03:00
pezkuwichain f7c070e45b fix(deps): drop invalid create-ecdh override (max version is 4.x not 5.x)
The earlier npm override 'create-ecdh: ^5.0.1' resolved to no version on
the registry. CI install failed with ETARGET. Removing the override —
elliptic override alone covers the high-severity transitive vulns.
Remaining 6 lows in vite-plugin-node-polyfills chain accepted.
2026-05-09 12:27:07 +03:00
pezkuwichain 06ed9734c6 ci(security): Faz 3 + ekstra — runner consolidation, auto-rollback, cosign, SRI, dep cleanup
* Faz 3.1 — All CI jobs moved to self-hosted pwap-runner (DEV VPS).
  No more dependency on GitHub-hosted runners — supply-chain attack
  surface from GHA runner image compromise eliminated.
* Faz 3.3 — Automatic rollback on health-check fail. Each deploy stamps
  /.deploy-sha into the artifact. On health-check failure, the deploy
  job reads the previous SHA from the live site, pulls that image, and
  redeploys. Telegram notification differentiates: rolled-back-OK,
  rollback-also-failed, no-prev-available, manual-rollback-needed.
* E.3 — cosign keyless image signing. build-image signs the GHCR
  manifest via Sigstore Fulcio (OIDC, no long-lived keys). deploy-app
  and deploy-pex verify the signature before extracting /dist —
  unsigned or tampered images cannot deploy. Identity-pinned to this
  workflow file.
* E.5 — Subresource Integrity (SRI). vite-plugin-subresource-integrity
  injects sha384 integrity= into <script>/<link> tags at build time.
  CDN/proxy compromise cannot inject tampered JS — browser blocks on
  hash mismatch.
* E.2 — Dependabot triage. 14 alerts: 7 high + 4 moderate cleared via
  npm audit fix + npm overrides (elliptic, create-ecdh). 6 low
  (transitive in vite-plugin-node-polyfills chain) accepted; the
  upstream fix proposes a semver-major DOWNGRADE which makes no sense.
* E.1 — Branch protection on main: CI Gate  required, 1 review
  required, force-push and deletion blocked.
2026-05-09 12:08:49 +03:00
pezkuwichain d93d4c6cd0 fix(docker): correct dist path after WORKDIR=/build/web
Stage 2 was looking for /build/dist but vite emits to /build/web/dist
(WORKDIR is /build/web in stage 1). Fix the COPY --from=builder path.
2026-05-08 21:39:07 +03:00
pezkuwichain faba2dee5d fix(docker): build context = pwap root so shared/ is reachable
Vite aliases @pezkuwi/utils → ../shared/utils, so the Docker build context
must include both web/ and shared/. Previous context: ./web missed shared/
which caused 'Could not load /shared/utils/formatting' at module resolution.

Changes:
- Dockerfile WORKDIR=/build/web; COPY web/* and shared/* explicitly
- Workflow context: ./ + file: ./web/Dockerfile
- Move .dockerignore from web/ to pwap root (matches new context)
2026-05-08 20:44:19 +03:00
pezkuwichain ca3976fe62 ci(security): Faz 1+2 — Telegram CEO gate, image-based deploy, hardened audits
Faz 1 — State-actor threat-model defenses:
* Telegram approval gate via PEXSEC_BOT — CEO must approve every deploy in Telegram (30-min timeout). Runs on new self-hosted pwap-runner on DEV VPS, shares /tmp/pexsec-gates/ with pexsec-bot.service.
* DEV VPS app-deploy user privilege drop — deploys no longer run as root. CI key restricted with no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-user-rc. Privilege drop verified (cannot read /etc/shadow, /root/, sudo blocked).
* Image-based deploy — Dockerfile (node 20 build → busybox:musl dist) pushed to GHCR with SHA tag. Deploys pull image, extract /dist, scp to VPS. Immutable artifacts, full provenance.
* Health check + Telegram failure alert post-deploy.
* Rollback path: workflow_dispatch with rollback_to=<sha> — skips build, redeploys old image. CEO gate still required.

Faz 2 — Higher-tier defenses:
* TruffleHog secret scan — PR diff (fast) + push full-repo (verified secrets only).
* CodeQL SAST workflow — javascript-typescript, security-extended + security-and-quality queries. PR + push + weekly cron.
* npm audit raised from --audit-level=critical to --audit-level=high (caught more CVEs).
* CI Gate  explicit merge-block job — fails if any required check is not success/skipped.
2026-05-08 20:32:48 +03:00
pezkuwichain 7fea37eb5d ci(deploy): allow workflow_dispatch to trigger deploy jobs
Enables manual re-deploy via 'gh workflow run quality-gate.yml' without
needing a code push. Useful for: redeploy after secret rotation, post-
incident recovery, deploy verification.
2026-05-08 15:06:19 +03:00
pezkuwichain 68379dcf3a ci(deploy): mirror web build to pex.mom for geo-redundancy
Split monolithic deploy job into bump-version + deploy-app + deploy-pex.
Both deploys run in parallel from same build artifact, independent
secrets per VPS. If one country blocks a domain, the other VPS keeps
serving the same version.

- bump-version: single source of version bump, runs before both deploys
- deploy-app: existing target /var/www/subdomains/app on DEV VPS
- deploy-pex: new target /var/www/pex.mom on VPS3 (217.77.6.126)

Requires secrets: VPS_PEX_HOST, VPS_PEX_USER, VPS_PEX_SSH_KEY, VPS_PEX_SSH_PORT
2026-05-08 14:07:35 +03:00
pezkuwichain 56f276af1b fix(wallet): add 20s timeout to web3Enable to prevent indefinite hang
- Wrap web3Enable() with Promise.race against a 20-second timeout
- On timeout: show descriptive error explaining the popup may be blocked
- Surface actual error messages (incl. timeout) instead of generic 'Failed to connect wallet'
- Both auto-restore and manual connect button now fail fast instead of hanging
2026-05-05 13:12:36 +03:00
pezkuwichain f024d21cf5 fix(wallet-modal): add loading state for extension connect, fix Play Store link
- Extension button now shows 'Approve in extension...' spinner while web3Enable waits
- Add generic error fallback for errors not matching 'authorize'/'not found' patterns
- Replace 'Coming soon on Play Store' with real Play Store download link (io.pezkuwichain.wallet)
- WalletConnectModal mobile hint now links directly to Play Store
- Updated in all 6 locales: en, tr, ar, fa, kmr, ckb
2026-05-05 08:28:52 +03:00
pezkuwichain 67bc28cff4 docs(readme): fix exchange URL to pex.network, add pex.mom as alt website 2026-05-04 00:36:26 +03:00
pezkuwichain d7fa9dd570 docs(readme): update URLs to app.pezkuwichain.io, pex.mom, docs.pezkuwichain.io 2026-05-04 00:34:22 +03:00
pezkuwichain 428b058cbc chore: add res/ to .gitignore (internal-only resources) 2026-05-04 00:28:44 +03:00
pezkuwichain 0b5e318538 fix(deps): npm audit fix — patch 14 high/moderate vulnerabilities in web/
Fixes: vite, rollup, dompurify, lodash, postcss, ajv, bn.js, defu,
flatted, h3, minimatch, picomatch, brace-expansion, qs
Remaining 7 (low/moderate): uuid + vite-plugin-node-polyfills require
--force (major breaking changes, deferred)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 00:16:27 +03:00
pezkuwichain 568507ab98 chore: remove leftover dev artifacts (screenshots, Zone.Identifier, PS1 script, PDFs)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 00:08:25 +03:00
pezkuwichain 198f53b96f fix(config): point production WS endpoint to rpc.pezkuwichain.io
- App.tsx fallback: localhost:9944 → wss://rpc.pezkuwichain.io
- All locales: remove hardcoded ws://127.0.0.1:9944 from error message
2026-05-03 02:00:40 +03:00
pezkuwichain 9babb94e07 fix(auth): add pexsecBot for Telegram login on app.pezkuwichain.io
- pex.mom uses @PexMomBOT (8690398980)
- app.pezkuwichain.io uses @pexsecBot (8754021997)
- Edge function selects token based on bot_id from request
2026-05-01 23:32:25 +03:00
pezkuwichain ef6a7b2583 feat(i18n): add landing page translations for Sorani, Arabic, and Farsi
All 187 landing.* keys were missing from ckb/ar/fa locales, causing fallback to English.
2026-05-01 19:32:29 +03:00
pezkuwichain d446d711ba fix(web): replace AppLayout footer with identical LandingPageDesktop footer
Footer now uses lp-footer CSS classes and identical markup to pre-login landing page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 16:49:12 +03:00
pezkuwichain d1af76f444 fix(web): remove ArrowRightLeft icon from trading button + match bottom tab bar to pre-login design
- Remove ArrowRightLeft icon from desktop nav Trading dropdown button
- Bottom tab bar: add max-w-md mx-auto (centered) and bump z-index to z-50 to match MobileHomeLayout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 16:16:09 +03:00
pezkuwichain 914d914b74 fix(lint): remove unused bodyOnly prop from LandingPageDesktop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 15:38:04 +03:00
pezkuwichain 8f57224700 feat(web): restore authenticated desktop home layout with modern section cards
- Add body content sections (HeroSection, NetworkStats, TrustScoreCalculator, ChainSpecs, RewardDistribution) after section grid
- Update section cards with distinct gradient header colors per category (Finance/green, Governance/purple, Social/blue, Education/orange)
- Fix bottom tab bar to be full-width (removed max-w-md mx-auto)
- Adjust role/score cards background to bg-gray-800/70 for contrast against main bg
- Add bodyOnly prop to LandingPageDesktop (non-breaking, unused)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 15:36:31 +03:00
pezkuwichain 1e047eba91 fix(ci): set SSH port 2222 for DEV VPS deploy 2026-05-01 14:09:27 +03:00
SatoshiQaziMuhammed 14d6da24db Merge pull request #14 from pezkuwichain/redesign
feat(web): authenticated home redesign + Telegram OAuth + SMTP
2026-05-01 10:22:36 +03:00
pezkuwichain 346a30fcbb fix(lint): remove unused keyMaterial variable + stale eslint-disable in LandingPageDesktop 2026-05-01 10:16:37 +03:00
pezkuwichain bac4148020 feat(web): authenticated home redesign + Telegram OAuth + SMTP
- AppLayout: logged-in desktop home redesigned — score cards, 4 section
  cards (Finance/Governance/Social/Education), governance extras, fixed
  bottom tab bar (Home/Citizen/Referral) matching mobile layout
- AppLayout: Trading dropdown in header (Presale/Staking/MultiSig),
  Logout button; removed 8-button grid
- Removed PalletsGrid and TokenomicsSection components
- Login: Telegram OAuth via oauth.telegram.org popup + postMessage +
  custom Edge Function (hash verification, find-or-create user,
  magic link token exchange)
- Login: X (Twitter) OAuth 2.0 wired to Supabase
- supabase/functions/telegram-auth: new Edge Function — verifies
  Telegram Login Widget hash, issues Supabase magic link token
- vite.config.ts: process-shim alias to fix TDZ with node polyfills
- i18n: updated locales (en/tr/kmr) for new UI sections
- SDK docs search index regenerated
2026-05-01 10:12:03 +03:00
pezkuwichain 709d408983 feat(landing): desktop landing page redesign
- New LandingPageDesktop component with full redesign
- Kurdish flag design tokens, animated sun, ticker band
- Three hero variants (V1 editorial split, V2 terminal, V3 mosaic)
- Live chain data: blocks, validators, nominators, citizens, proposals
- Features grid, architecture section, tokenomics tabs, referral steps
- Pallet grid with SVG sprite icons (16 pallets, 4 pillars)
- Dev-only hero variant switcher (auto-removed in prod)
- Mobile unchanged (MobileHomeLayout), logged-in unchanged
2026-04-30 23:30:09 +03:00
pezkuwichain 69789548e7 fix: prevent 'API not ready' on mobile by blocking wallet connect until blockchain initializes
- Add isApiInitializing state (true during WS connect, false on ready/fail)
- Add isApiReadyRef for closure-safe polling in connectWalletConnect
- connectWalletConnect now waits up to 30s for API instead of throwing immediately
- WalletModal connect buttons disabled + show spinner while blockchain is initializing
2026-04-27 15:00:58 +03:00
pezkuwichain 86ff43e206 feat: write p2p_user_id to tg_users on Telegram wallet link
TelegramConnect: query tg_users instead of users, resolve visa UUID
from p2p_visa table and store as p2p_user_id for cross-platform P2P.

P2PIdentityContext: when citizen resolves their UUID, backfill
tg_users.p2p_user_id if their wallet is linked to a Telegram account.
2026-04-27 13:31:22 +03:00
pezkuwichain 09da6e80b7 chore: remove mobile/ from monorepo, suspend CI mobile job
Mobile app moved to /home/mamostehp/pwap-mobile (local, suspended).
Will be re-integrated when mobile development resumes.

- Removed mobile/ directory entirely
- Removed Mobile App job from quality-gate.yml so CI no longer blocks
2026-04-27 03:06:15 +03:00
pezkuwichain d3362173df chore: update exchange submodule to pex.network release + add shared images
Exchange submodule advanced to include:
- sweeper.js: TRC-20 JWT Bearer auth, DOT transferAll, PEZ-AH pre-fund
- docker-compose.yml: pex.network defaults for VITE_API_BASE_URL and SMTP_FROM
- .github/workflows/build-deploy.yml: pex.network build arg for web service

Shared images added: keziyakurd, kiwi_perwerde, kurdistan_assembly, pezkuwi, satoshi_qazi_muh
2026-04-27 02:21:08 +03:00
977 changed files with 14565 additions and 61913 deletions
+41
View File
@@ -0,0 +1,41 @@
# pwap/web Docker build context (root) — exclude everything not needed
# for `web/` build. Other monorepo subprojects stay out of the image.
# Other monorepo dirs (we only need web/ + shared/)
exchange/
mobile/
pwap-mobile/
docs/
res/
# All node_modules everywhere
**/node_modules/
**/dist/
**/build/
# Git, GitHub
.git/
.github/
# Env files (built-in vars are passed as build-args from CI)
**/.env
**/.env.*
!**/.env.example
# Editor / OS
.vscode/
.idea/
*.swp
*.swo
.DS_Store
# Logs
*.log
# Cache
**/.eslintcache
**/coverage/
# Already-built artifacts (we rebuild fresh inside container)
web/dist/
shared/**/dist/
+58
View File
@@ -0,0 +1,58 @@
name: CodeQL (SAST)
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
schedule:
# Every Sunday at 02:00 UTC — catches CVEs disclosed during the week
- cron: '0 2 * * 0'
permissions:
contents: read
security-events: write
actions: read
concurrency:
group: codeql-${{ github.ref }}
cancel-in-progress: true
jobs:
analyze:
name: Analyze ${{ matrix.language }}
runs-on: pwap-runner
strategy:
fail-fast: false
matrix:
language: [javascript-typescript]
steps:
- uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# OWASP top-10 + injection + auth flaws + prototype pollution
queries: security-extended,security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform analysis
uses: github/codeql-action/analyze@v3
with:
category: /language:${{ matrix.language }}
# GitHub Advanced Security dashboard upload requires paid plan.
# SARIF saved as a downloadable artifact instead.
upload: false
output: /tmp/codeql-results
- name: Upload SARIF as artifact
uses: actions/upload-artifact@v4
continue-on-error: true
with:
name: codeql-sarif-${{ matrix.language }}
path: /tmp/codeql-results/*.sarif
retention-days: 7
+536 -19
View File
@@ -6,14 +6,25 @@ on:
pull_request: pull_request:
branches: [ main, develop ] branches: [ main, develop ]
workflow_dispatch: workflow_dispatch:
inputs:
rollback_to:
description: 'Rollback to git SHA (skips build, redeploys old image). Empty = normal deploy.'
required: false
default: ''
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
permissions:
contents: write # version bump commit
packages: write # GHCR push
env: env:
VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL }} VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL }}
VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }} VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }}
REGISTRY: ghcr.io
IMAGE_NAME: pezkuwichain/pwap-web
jobs: jobs:
# ======================================== # ========================================
@@ -21,7 +32,7 @@ jobs:
# ======================================== # ========================================
web: web:
name: Web App name: Web App
runs-on: ubuntu-latest runs-on: pwap-runner
steps: steps:
- name: Checkout code - name: Checkout code
@@ -75,13 +86,164 @@ jobs:
path: web/dist/ path: web/dist/
# ======================================== # ========================================
# DEPLOY WEB APP TO VPS # BUILD & PUSH DOCKER IMAGE TO GHCR
# Immutable artifact for audit + rollback (vs ephemeral GHA artifact).
# Tagged with git SHA so any commit can be redeployed by SHA.
# ======================================== # ========================================
deploy: build-image:
name: Deploy Web name: Build & Push Image
runs-on: ubuntu-latest runs-on: pwap-runner
needs: [web, telegram-gate]
if: |
github.ref == 'refs/heads/main' &&
(github.event_name == 'push' ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_to == ''))
permissions:
contents: read
packages: write
id-token: write # cosign keyless signing via Sigstore OIDC
outputs:
image_sha: ${{ steps.meta.outputs.image_sha }}
image_digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Install cosign
uses: sigstore/cosign-installer@v3
with:
cosign-release: 'v2.4.1'
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract image metadata
id: meta
run: |
SHORT_SHA="${GITHUB_SHA:0:7}"
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
echo "image_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
echo "image=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" >> $GITHUB_OUTPUT
- name: Build and push
id: build
uses: docker/build-push-action@v6
with:
context: ./
file: ./web/Dockerfile
push: true
tags: |
${{ steps.meta.outputs.image }}:${{ steps.meta.outputs.short_sha }}
${{ steps.meta.outputs.image }}:latest
build-args: |
VITE_NETWORK=MAINNET
VITE_WS_ENDPOINT=wss://rpc.pezkuwichain.io
VITE_WS_ENDPOINT_FALLBACK_1=wss://mainnet.pezkuwichain.io
VITE_ASSET_HUB_ENDPOINT=wss://asset-hub-rpc.pezkuwichain.io
VITE_PEOPLE_CHAIN_ENDPOINT=wss://people-rpc.pezkuwichain.io
VITE_WALLETCONNECT_PROJECT_ID=8292a793b7640e8364c378e331e76d04
VITE_SUPABASE_URL=${{ secrets.VITE_SUPABASE_URL }}
VITE_SUPABASE_ANON_KEY=${{ secrets.VITE_SUPABASE_ANON_KEY }}
cache-from: type=registry,ref=${{ steps.meta.outputs.image }}:cache
cache-to: type=registry,ref=${{ steps.meta.outputs.image }}:cache,mode=max
provenance: false
- name: Sign image with cosign (keyless, Sigstore Fulcio)
env:
COSIGN_EXPERIMENTAL: '1'
run: |
IMAGE_DIGEST="${{ steps.meta.outputs.image }}@${{ steps.build.outputs.digest }}"
# cosign needs its own registry auth — docker/login-action only writes
# ~/.docker/config.json which cosign on self-hosted runner can't read
echo "${{ secrets.GITHUB_TOKEN }}" | cosign login ghcr.io -u "${{ github.actor }}" --password-stdin
echo "Signing $IMAGE_DIGEST"
cosign sign --yes "$IMAGE_DIGEST"
echo "✅ Image signed (transparency log: rekor.sigstore.dev)"
# ========================================
# TELEGRAM CEO APPROVAL GATE
# Runs on self-hosted pwap-runner (DEV VPS) where pexsec-bot.service
# writes the gate file to /tmp/pexsec-gates/<sha> when CEO clicks
# Approve/Cancel in Telegram. 30-minute timeout = deploy cancelled.
# ========================================
telegram-gate:
name: Telegram deploy approval
runs-on: pwap-runner
needs: [web, security-audit] needs: [web, security-audit]
if: github.ref == 'refs/heads/main' && github.event_name == 'push' if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
steps:
- name: Send approval request and wait
env:
BOT_TOKEN: ${{ secrets.PEXSEC_BOT_TOKEN }}
CEO_CHAT_ID: ${{ secrets.TELEGRAM_CEO_CHAT_ID }}
SHA: ${{ github.sha }}
ACTOR: ${{ github.actor }}
MESSAGE: ${{ github.event.head_commit.message }}
run: |
SHORT="${SHA:0:7}"
GATE_DIR="/tmp/pexsec-gates"
mkdir -p "$GATE_DIR" 2>/dev/null || true
rm -f "$GATE_DIR/$SHORT" 2>/dev/null || true
# Strip Markdown special chars to prevent Telegram parse errors
SAFE_MSG=$(echo "${MESSAGE}" | head -1 | tr -d '_*`[]()#|{}!' | cut -c1-120)
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
-H "Content-Type: application/json" \
-d "{
\"chat_id\": \"${CEO_CHAT_ID}\",
\"parse_mode\": \"Markdown\",
\"text\": \"🚀 *pwap/web Deploy Approval*\\n\\n\`${SHORT}\` — ${ACTOR}\\n\\n_${SAFE_MSG}_\\n\\nTargets: app.pezkuwichain.io + pex.mom\",
\"reply_markup\": {
\"inline_keyboard\": [[
{\"text\": \"✅ Approve\", \"callback_data\": \"deploy_approve:${SHORT}\"},
{\"text\": \"❌ Cancel\", \"callback_data\": \"deploy_cancel:${SHORT}\"}
]]
}
}"
echo "Waiting for Telegram approval (max 30 min)..."
TIMEOUT=1800
ELAPSED=0
while [ $ELAPSED -lt $TIMEOUT ]; do
if [ -f "$GATE_DIR/$SHORT" ]; then
DECISION=$(cat "$GATE_DIR/$SHORT")
rm -f "$GATE_DIR/$SHORT" 2>/dev/null || true
if [ "$DECISION" = "approved" ]; then
echo "Deploy approved."
exit 0
else
echo "Deploy cancelled."
exit 1
fi
fi
sleep 10
ELAPSED=$((ELAPSED + 10))
done
echo "No approval received within 30 minutes — deploy cancelled."
exit 1
# ========================================
# VERSION BUMP (RUNS BEFORE BOTH DEPLOYS)
# ========================================
bump-version:
name: Bump Version
runs-on: pwap-runner
needs: [web, security-audit, telegram-gate, build-image]
# Skip on rollback (workflow_dispatch with rollback_to set)
if: |
github.ref == 'refs/heads/main' &&
(github.event_name == 'push' ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_to == ''))
outputs:
new_version: ${{ steps.bump.outputs.version }}
steps: steps:
- name: Checkout code - name: Checkout code
@@ -101,61 +263,416 @@ jobs:
git config user.email "github-actions[bot]@users.noreply.github.com" git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Bump version - name: Bump version
id: bump
working-directory: ./web working-directory: ./web
run: | run: |
npm version patch --no-git-tag-version npm version patch --no-git-tag-version
VERSION=$(node -p "require('./package.json').version") VERSION=$(node -p "require('./package.json').version")
echo "NEW_VERSION=$VERSION" >> $GITHUB_ENV echo "version=$VERSION" >> $GITHUB_OUTPUT
cd .. cd ..
git add web/package.json git add web/package.json
git commit -m "chore(web): bump version to $VERSION [skip ci]" || echo "No version change" git commit -m "chore(web): bump version to $VERSION [skip ci]" || echo "No version change"
git push || echo "Nothing to push" git push || echo "Nothing to push"
- name: Download build artifact # ========================================
uses: actions/download-artifact@v4 # DEPLOY TO app.pezkuwichain.io (DEV VPS)
with: # Pulls SHA-tagged image from GHCR, extracts /dist, scp to VPS.
name: web-dist # Health check + auto-rollback to .deploy-tag-prev on failure.
path: dist/ # ========================================
deploy-app:
name: Deploy app.pezkuwichain.io
runs-on: pwap-runner
needs: [telegram-gate, bump-version, build-image]
if: |
always() &&
needs.telegram-gate.result == 'success' &&
((github.event_name == 'push' && needs.build-image.result == 'success' && needs.bump-version.result == 'success') ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_to != ''))
permissions:
contents: read
packages: read
env:
DOMAIN: app.pezkuwichain.io
TARGET_PATH: /var/www/subdomains/app
- name: Deploy to VPS steps:
- name: Determine image SHA
id: sha
run: |
if [ -n "${{ github.event.inputs.rollback_to }}" ]; then
echo "sha=${{ github.event.inputs.rollback_to }}" >> $GITHUB_OUTPUT
echo "Rolling back to: ${{ github.event.inputs.rollback_to }}"
else
echo "sha=${{ needs.build-image.outputs.image_sha }}" >> $GITHUB_OUTPUT
fi
- name: Capture currently-live SHA (for auto-rollback)
id: prev
run: |
# /.deploy-sha is written into every deploy; read what's live now
PREV=$(curl -sf --max-time 5 "https://${{ env.DOMAIN }}/.deploy-sha" | head -c 40 | tr -dc 'a-f0-9' || echo "")
echo "Previous live SHA: ${PREV:-unknown}"
echo "prev=$PREV" >> $GITHUB_OUTPUT
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Install cosign (for verify)
uses: sigstore/cosign-installer@v3
with:
cosign-release: 'v2.4.1'
- name: Verify image signature (cosign keyless)
env:
COSIGN_EXPERIMENTAL: '1'
run: |
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.sha }}"
echo "${{ secrets.GITHUB_TOKEN }}" | cosign login ghcr.io -u "${{ github.actor }}" --password-stdin
echo "Verifying signature for $IMAGE"
cosign verify "$IMAGE" \
--certificate-identity-regexp "^https://github.com/pezkuwichain/pwap/.github/workflows/quality-gate.yml@" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
> /dev/null
echo "✅ Signature valid — image was built by trusted CI"
- name: Extract /dist from image
run: |
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.sha }}"
docker pull "$IMAGE"
CID=$(docker create "$IMAGE")
mkdir -p dist
docker cp "$CID:/dist/." dist/
docker rm "$CID" >/dev/null
# Stamp this build's SHA into dist so future deploys can read PREV
echo "${{ steps.sha.outputs.sha }}" > dist/.deploy-sha
ls -la dist/ | head -10
- name: Deploy to DEV VPS
uses: appleboy/scp-action@v1.0.0 uses: appleboy/scp-action@v1.0.0
with: with:
host: ${{ secrets.VPS_HOST }} host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }} username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }} key: ${{ secrets.VPS_SSH_KEY }}
port: ${{ secrets.VPS_SSH_PORT || 2222 }}
source: 'dist/*' source: 'dist/*'
target: '/var/www/subdomains/app' target: '/var/www/subdomains/app'
strip_components: 1 strip_components: 1
- name: Post-deploy notification - name: Health check (60s window)
id: healthcheck
run: | run: |
echo "✅ Deployed web app v${{ env.NEW_VERSION }} to app.pezkuwichain.io" for i in 1 2 3 4 5 6; do
if curl -sf --max-time 10 "https://${{ env.DOMAIN }}/" >/dev/null; then
echo "✅ ${{ env.DOMAIN }} healthy"
exit 0
fi
echo "Attempt $i/6 failed, retrying in 10s..."
sleep 10
done
echo "❌ Health check failed for ${{ env.DOMAIN }}"
exit 1
# ── Automatic rollback: pull PREV SHA image, redeploy, recheck ──
- name: Auto-rollback to previous SHA
id: rollback
if: failure() && steps.healthcheck.conclusion == 'failure' && steps.prev.outputs.prev != ''
run: |
PREV="${{ steps.prev.outputs.prev }}"
echo "🔄 Rolling back ${{ env.DOMAIN }} to $PREV"
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$PREV"
docker pull "$IMAGE"
CID=$(docker create "$IMAGE")
rm -rf dist && mkdir dist
docker cp "$CID:/dist/." dist/
docker rm "$CID" >/dev/null
echo "$PREV" > dist/.deploy-sha
echo "rollback_sha=$PREV" >> $GITHUB_OUTPUT
- name: SCP rollback artifact
if: steps.rollback.outcome == 'success'
uses: appleboy/scp-action@v1.0.0
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
port: ${{ secrets.VPS_SSH_PORT || 2222 }}
source: 'dist/*'
target: '/var/www/subdomains/app'
strip_components: 1
- name: Re-health-check after rollback
if: steps.rollback.outcome == 'success'
id: healthcheck_rb
run: |
for i in 1 2 3 4 5 6; do
if curl -sf --max-time 10 "https://${{ env.DOMAIN }}/" >/dev/null; then
echo "✅ Rolled back successfully — ${{ env.DOMAIN }} healthy on ${{ steps.rollback.outputs.rollback_sha }}"
exit 0
fi
sleep 10
done
echo "❌ Rollback also failed!"
exit 1
- name: Post-deploy notification
if: success()
run: |
echo "✅ Deployed image ${{ steps.sha.outputs.sha }} to ${{ env.DOMAIN }}"
- name: Notify failure (Telegram)
if: failure()
env:
BOT_TOKEN: ${{ secrets.PEXSEC_BOT_TOKEN }}
CEO_CHAT_ID: ${{ secrets.TELEGRAM_CEO_CHAT_ID }}
NEW_SHA: ${{ steps.sha.outputs.sha }}
PREV_SHA: ${{ steps.prev.outputs.prev }}
ROLLBACK_OUTCOME: ${{ steps.rollback.outcome }}
RECHECK_OUTCOME: ${{ steps.healthcheck_rb.outcome }}
run: |
if [ "$RECHECK_OUTCOME" = "success" ]; then
MSG="⚠️ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed health check, AUTO-ROLLED-BACK to $PREV_SHA. Site healthy."
elif [ "$ROLLBACK_OUTCOME" = "success" ]; then
MSG="🚨 pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed AND rollback to $PREV_SHA also failed. Manual intervention needed."
elif [ -z "$PREV_SHA" ]; then
MSG="❌ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed. No previous SHA available (first deploy?). Manual rollback: gh workflow run quality-gate.yml -f rollback_to=<sha>"
else
MSG="❌ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed. Auto-rollback was not attempted. Manual: gh workflow run quality-gate.yml -f rollback_to=$PREV_SHA"
fi
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
-d "chat_id=${CEO_CHAT_ID}" --data-urlencode "text=$MSG"
# ========================================
# DEPLOY TO pex.mom (VPS3 — geo-redundant mirror)
# ========================================
deploy-pex:
name: Deploy pex.mom
runs-on: pwap-runner
needs: [telegram-gate, bump-version, build-image]
if: |
always() &&
needs.telegram-gate.result == 'success' &&
((github.event_name == 'push' && needs.build-image.result == 'success' && needs.bump-version.result == 'success') ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_to != ''))
permissions:
contents: read
packages: read
env:
DOMAIN: pex.mom
TARGET_PATH: /var/www/pex.mom
steps:
- name: Determine image SHA
id: sha
run: |
if [ -n "${{ github.event.inputs.rollback_to }}" ]; then
echo "sha=${{ github.event.inputs.rollback_to }}" >> $GITHUB_OUTPUT
else
echo "sha=${{ needs.build-image.outputs.image_sha }}" >> $GITHUB_OUTPUT
fi
- name: Capture currently-live SHA (for auto-rollback)
id: prev
run: |
PREV=$(curl -sf --max-time 5 "https://${{ env.DOMAIN }}/.deploy-sha" | head -c 40 | tr -dc 'a-f0-9' || echo "")
echo "Previous live SHA: ${PREV:-unknown}"
echo "prev=$PREV" >> $GITHUB_OUTPUT
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Install cosign (for verify)
uses: sigstore/cosign-installer@v3
with:
cosign-release: 'v2.4.1'
- name: Verify image signature (cosign keyless)
env:
COSIGN_EXPERIMENTAL: '1'
run: |
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.sha }}"
echo "Verifying signature for $IMAGE"
cosign verify "$IMAGE" \
--certificate-identity-regexp "^https://github.com/pezkuwichain/pwap/.github/workflows/quality-gate.yml@" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
> /dev/null
echo "✅ Signature valid — image was built by trusted CI"
- name: Extract /dist from image
run: |
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.sha }}"
docker pull "$IMAGE"
CID=$(docker create "$IMAGE")
mkdir -p dist
docker cp "$CID:/dist/." dist/
docker rm "$CID" >/dev/null
echo "${{ steps.sha.outputs.sha }}" > dist/.deploy-sha
- name: Deploy to VPS3
uses: appleboy/scp-action@v1.0.0
with:
host: ${{ secrets.VPS_PEX_HOST }}
username: ${{ secrets.VPS_PEX_USER }}
key: ${{ secrets.VPS_PEX_SSH_KEY }}
port: ${{ secrets.VPS_PEX_SSH_PORT || 22 }}
source: 'dist/*'
target: '/var/www/pex.mom'
strip_components: 1
- name: Health check (60s window)
id: healthcheck
run: |
for i in 1 2 3 4 5 6; do
if curl -sf --max-time 10 "https://${{ env.DOMAIN }}/" >/dev/null; then
echo "✅ ${{ env.DOMAIN }} healthy"
exit 0
fi
echo "Attempt $i/6 failed, retrying in 10s..."
sleep 10
done
echo "❌ Health check failed for ${{ env.DOMAIN }}"
exit 1
- name: Auto-rollback to previous SHA
id: rollback
if: failure() && steps.healthcheck.conclusion == 'failure' && steps.prev.outputs.prev != ''
run: |
PREV="${{ steps.prev.outputs.prev }}"
echo "🔄 Rolling back ${{ env.DOMAIN }} to $PREV"
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$PREV"
docker pull "$IMAGE"
CID=$(docker create "$IMAGE")
rm -rf dist && mkdir dist
docker cp "$CID:/dist/." dist/
docker rm "$CID" >/dev/null
echo "$PREV" > dist/.deploy-sha
echo "rollback_sha=$PREV" >> $GITHUB_OUTPUT
- name: SCP rollback artifact
if: steps.rollback.outcome == 'success'
uses: appleboy/scp-action@v1.0.0
with:
host: ${{ secrets.VPS_PEX_HOST }}
username: ${{ secrets.VPS_PEX_USER }}
key: ${{ secrets.VPS_PEX_SSH_KEY }}
port: ${{ secrets.VPS_PEX_SSH_PORT || 22 }}
source: 'dist/*'
target: '/var/www/pex.mom'
strip_components: 1
- name: Re-health-check after rollback
if: steps.rollback.outcome == 'success'
id: healthcheck_rb
run: |
for i in 1 2 3 4 5 6; do
if curl -sf --max-time 10 "https://${{ env.DOMAIN }}/" >/dev/null; then
echo "✅ Rolled back successfully — ${{ env.DOMAIN }} healthy on ${{ steps.rollback.outputs.rollback_sha }}"
exit 0
fi
sleep 10
done
echo "❌ Rollback also failed!"
exit 1
- name: Post-deploy notification
if: success()
run: |
echo "✅ Deployed image ${{ steps.sha.outputs.sha }} to ${{ env.DOMAIN }}"
- name: Notify failure (Telegram)
if: failure()
env:
BOT_TOKEN: ${{ secrets.PEXSEC_BOT_TOKEN }}
CEO_CHAT_ID: ${{ secrets.TELEGRAM_CEO_CHAT_ID }}
NEW_SHA: ${{ steps.sha.outputs.sha }}
PREV_SHA: ${{ steps.prev.outputs.prev }}
ROLLBACK_OUTCOME: ${{ steps.rollback.outcome }}
RECHECK_OUTCOME: ${{ steps.healthcheck_rb.outcome }}
run: |
if [ "$RECHECK_OUTCOME" = "success" ]; then
MSG="⚠️ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed health check, AUTO-ROLLED-BACK to $PREV_SHA. Site healthy."
elif [ "$ROLLBACK_OUTCOME" = "success" ]; then
MSG="🚨 pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed AND rollback to $PREV_SHA also failed. Manual intervention needed."
elif [ -z "$PREV_SHA" ]; then
MSG="❌ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed. No previous SHA available (first deploy?). Manual rollback: gh workflow run quality-gate.yml -f rollback_to=<sha>"
else
MSG="❌ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed. Auto-rollback was not attempted. Manual: gh workflow run quality-gate.yml -f rollback_to=$PREV_SHA"
fi
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
-d "chat_id=${CEO_CHAT_ID}" --data-urlencode "text=$MSG"
# ======================================== # ========================================
# SECURITY CHECKS (BLOCKING) # SECURITY CHECKS (BLOCKING)
# npm audit (high + critical) + TruffleHog secret scan
# ======================================== # ========================================
security-audit: security-audit:
name: Security Audit name: Security Audit
runs-on: ubuntu-latest runs-on: pwap-runner
needs: [web] needs: [web]
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
with:
fetch-depth: ${{ github.event_name == 'pull_request' && 0 || 1 }}
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '20' node-version: '20'
- name: Web - npm audit (critical only) - name: Web npm audit (high + critical, production deps only)
working-directory: ./web working-directory: ./web
run: | run: |
npm install npm install
npm audit --audit-level=critical # Audit only production dependencies. Build tooling (vite, esbuild,
# vite-plugin-node-polyfills → elliptic, etc.) ships to no user, and
# advisories on those dev deps kept blocking production deploys.
npm audit --audit-level=high --omit=dev
- name: TruffleHog Secret Scan - name: TruffleHog — PR diff (verified secrets only)
if: github.event_name == 'pull_request'
uses: trufflesecurity/trufflehog@main
with:
base: ${{ github.event.pull_request.base.sha }}
head: ${{ github.event.pull_request.head.sha }}
extra_args: --only-verified
- name: TruffleHog — full repo scan (verified secrets only)
if: github.event_name != 'pull_request'
uses: trufflesecurity/trufflehog@main uses: trufflesecurity/trufflehog@main
with: with:
path: ./ path: ./
extra_args: --only-verified extra_args: --only-verified
# ========================================
# CI GATE — explicit merge-block
# All required checks must succeed (or be skipped, e.g. for rollback path).
# Branch protection on main should require this job's success.
# ========================================
ci-gate:
name: CI Gate ✅
runs-on: pwap-runner
needs: [web, security-audit]
if: always()
steps:
- name: Verify all required jobs succeeded or were intentionally skipped
run: |
results='${{ toJSON(needs) }}'
echo "$results" | python3 -c "
import json, sys
needs = json.load(sys.stdin)
failed = [name for name, job in needs.items() if job['result'] not in ('success', 'skipped')]
if failed:
print('❌ Required jobs failed: ' + ', '.join(failed))
sys.exit(1)
print('✅ All required CI jobs passed or skipped')
"
+11
View File
@@ -1,3 +1,6 @@
# Internal resources (never commit)
res/
# Logs # Logs
logs logs
*.log *.log
@@ -159,3 +162,11 @@ mobile/credentials.json
# Backup files # Backup files
*.bak *.bak
# Generated docs artifacts — the in-app /docs route now redirects to the
# external docs.pezkuwichain.io (see web/src/pages/Docs.tsx, since c56e021a).
# These are (re)built from Pezkuwi-SDK by web/generate-docs-structure.cjs on
# pre(dev|build); they are NOT served at runtime, so we don't track them.
/web/public/docs/
/web/public/sdk_docs/
/web/public/docs-structure.json
-2002
View File
File diff suppressed because it is too large Load Diff
+5 -4
View File
@@ -26,7 +26,7 @@ pwap/
**Status:** ✅ Production Ready **Status:** ✅ Production Ready
The primary web interface for Pezkuwi blockchain at [pezkuwichain.app](https://pezkuwichain.app) The primary web interface for Pezkuwi blockchain at [app.pezkuwichain.io](https://app.pezkuwichain.io)
**Tech Stack:** **Tech Stack:**
- React 18 + TypeScript - React 18 + TypeScript
@@ -166,9 +166,10 @@ RTL support for CKB, AR, FA.
## Links ## Links
- **Website:** https://pezkuwichain.app - **Website:** https://app.pezkuwichain.io
- **SDK UI:** https://pezkuwichain.app/sdk - **Website (alt):** https://pex.mom
- **Documentation:** https://docs.pezkuwichain.app - **Exchange:** https://pex.network
- **Documentation:** https://docs.pezkuwichain.io
## License ## License
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 802 KiB

Submodule exchange deleted from bb3bc812ed
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 677 KiB

-31
View File
@@ -1,31 +0,0 @@
# PWAP WSL Dev Environment Setup
# Run in PowerShell as Administrator
Write-Host "=== PWAP Dev Setup ===" -ForegroundColor Cyan
# 1. Fix .wslconfig - enable mirrored networking
$wslconfig = @"
[wsl2]
memory=48GB
swap=16GB
networkingMode=mirrored
"@
Set-Content -Path "$env:USERPROFILE\.wslconfig" -Value $wslconfig
Write-Host "[OK] .wslconfig updated (mirrored networking)" -ForegroundColor Green
# 2. Restart ADB on default port
$adbPath = "C:\Users\satos\Desktop\platform-tools\adb.exe"
& $adbPath kill-server 2>$null
Start-Sleep -Seconds 1
& $adbPath start-server
Write-Host "[OK] ADB server restarted on port 5037" -ForegroundColor Green
& $adbPath devices
# 3. Shutdown WSL so new config takes effect
Write-Host "`nShutting down WSL..." -ForegroundColor Yellow
wsl --shutdown
Start-Sleep -Seconds 3
Write-Host "[OK] WSL shutdown complete" -ForegroundColor Green
Write-Host "`n=== Done! ===" -ForegroundColor Cyan
Write-Host "Now open WSL again and run: cd pwap && claude" -ForegroundColor White
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

+105
View File
@@ -0,0 +1,105 @@
// ========================================
// Multisig Pending Operations (higher-level orchestration for the review/approve UI)
// ========================================
// Sits on top of multisig.ts's low-level pallet-multisig calls and usdt.ts's bridge constants.
// There is no code path connecting this web app to the usdt-bridge relayer's private local
// database (confirmed while researching this feature - see the usdt-bridge-multisig-migration
// memory), so pending calls are described purely from on-chain state: the one deterministic,
// always-reconstructible candidate (the automation key's periodic allowance renewal) is
// auto-matched by hash; anything else needs a signer to paste the known call data, exactly
// like the Android wallet's "Enter Call Data" fallback exists for the same reason - a call
// hash alone is cryptographically insufficient to recover the original call.
import type { ApiPromise } from '@pezkuwi/api';
import type { SubmittableExtrinsic } from '@pezkuwi/api/types';
import {
getPendingMultisigCalls,
USDT_MULTISIG_CONFIG,
type MultisigTimepoint,
} from './multisig';
import { WUSDT_ASSET_ID, WUSDT_AUTOMATION_KEY_ADDRESS, STANDARD_RENEWAL_TOPUP } from './usdt';
export interface PendingOperation {
callHash: string;
when: MultisigTimepoint;
deposit: string;
depositor: string;
approvals: string[];
approvalsCount: number;
threshold: number;
description: string;
resolvedCall: SubmittableExtrinsic<'promise'> | null;
isAutoMatched: boolean;
}
/**
* The one deterministic, always-reconstructible pending-call shape this UI can auto-describe:
* the automation key's periodic Assets.approve_transfer renewal (see PezbridgeBot /
* pezkuwi-DKS/tools/usdt-bridge/src/bin/pezbridge_bot.rs, which builds the exact same call).
*/
export function buildRenewalCandidateCall(api: ApiPromise): SubmittableExtrinsic<'promise'> {
return api.tx.assets.approveTransfer(
WUSDT_ASSET_ID,
WUSDT_AUTOMATION_KEY_ADDRESS,
STANDARD_RENEWAL_TOPUP.toString()
) as unknown as SubmittableExtrinsic<'promise'>;
}
/**
* Lists every pending Multisig.Multisigs entry for the bridge's custody account and attaches a
* human-readable description + the real call object wherever it can be determined.
*/
export async function listPendingOperations(
api: ApiPromise,
multisigAddress: string
): Promise<PendingOperation[]> {
const raw = await getPendingMultisigCalls(api, multisigAddress);
const renewalCandidate = buildRenewalCandidateCall(api);
const renewalHash = renewalCandidate.method.hash.toHex().toLowerCase();
return raw.map((entry) => {
const approvals = (entry.approvals ?? []) as string[];
const isRenewal = String(entry.callHash).toLowerCase() === renewalHash;
return {
callHash: entry.callHash,
when: entry.when,
deposit: String(entry.deposit),
depositor: entry.depositor,
approvals,
approvalsCount: approvals.length,
threshold: USDT_MULTISIG_CONFIG.threshold,
description: isRenewal
? `Automation key allowance renewal (${(Number(STANDARD_RENEWAL_TOPUP) / 1e6).toFixed(2)} wUSDT)`
: 'Unknown call - paste the call data below to decode it',
resolvedCall: isRenewal ? renewalCandidate : null,
isAutoMatched: isRenewal,
};
});
}
/**
* Decodes raw hex call data a signer pasted in (the manual fallback for any pending call this
* UI couldn't auto-reconstruct) and verifies its hash actually matches the pending entry before
* returning it - pasted data is never trusted blindly.
*/
export function decodeAndVerifyCallData(
api: ApiPromise,
callDataHex: string,
expectedCallHash: string
): { call: SubmittableExtrinsic<'promise'>; description: string } {
const decoded = api.createType('Call', callDataHex);
const call = api.tx(decoded) as unknown as SubmittableExtrinsic<'promise'>;
const actualHash = call.method.hash.toHex().toLowerCase();
if (actualHash !== expectedCallHash.toLowerCase()) {
throw new Error(
`Call data does not match this operation: decoded hash ${actualHash}, expected ${expectedCallHash.toLowerCase()}`
);
}
const method = call.method as unknown as { section: string; method: string; args: unknown[] };
const argsText = method.args.map((a) => String(a)).join(', ');
return { call, description: `${method.section}.${method.method}(${argsText})` };
}
+25 -16
View File
@@ -6,7 +6,7 @@
import type { ApiPromise } from '@pezkuwi/api'; import type { ApiPromise } from '@pezkuwi/api';
import type { SubmittableExtrinsic } from '@pezkuwi/api/types'; import type { SubmittableExtrinsic } from '@pezkuwi/api/types';
import { Tiki } from './tiki'; import { Tiki } from './tiki';
import { encodeAddress, sortAddresses } from '@pezkuwi/util-crypto'; import { encodeMultiAddress, sortAddresses } from '@pezkuwi/util-crypto';
// ======================================== // ========================================
// MULTISIG CONFIGURATION // MULTISIG CONFIGURATION
@@ -30,6 +30,21 @@ export const USDT_MULTISIG_CONFIG = {
] as MultisigMember[], ] as MultisigMember[],
}; };
/**
* Addresses for the two non-unique Tiki roles (Noter, Berdevk are held by all 21 validators,
* so which specific validator sits on THIS multisig has to be pinned manually - it can't be
* derived from the Tiki on-chain lookup alone). This is the single source of truth: it MUST
* match the real 5 signatories the on-chain bridge multisig was actually derived from (see
* res/validators-tiki.md "USDT Bridge Custody Multisig" and the usdt-bridge-multisig-migration
* memory). Centralized here instead of copy-pasted per-page - a stale copy of these two
* addresses previously sat in ReservesDashboardPage.tsx and didn't match any real signer,
* which silently made every derived multisig address wrong.
*/
export const BRIDGE_MULTISIG_SPECIFIC_ADDRESSES: Record<string, string> = {
Noter: '5ELgySrX5ZyK7EWXjj6bAedyTCcTNWDANbiiipsT5gnpoCEp', // Validator_04
Berdevk: '5HWFZbhkZuTUySXu6ZXYKrTHBnWXHvWRKLozE22zhnwXGGxk', // Validator_02
};
// ======================================== // ========================================
// MULTISIG MEMBER QUERIES // MULTISIG MEMBER QUERIES
// ======================================== // ========================================
@@ -87,21 +102,15 @@ export function calculateMultisigAddress(
threshold: number = USDT_MULTISIG_CONFIG.threshold, threshold: number = USDT_MULTISIG_CONFIG.threshold,
ss58Format: number = 42 ss58Format: number = 42
): string { ): string {
// Sort members (multisig requires sorted order) // Delegates to @pezkuwi/util-crypto's own createKeyMulti/encodeMultiAddress, which correctly
const sortedMembers = sortAddresses(members); // implements pallet_multisig's derivation (blake2_256 of
// SCALE-encode(b"modlpy/utilisuba" ++ compact-len ++ sorted pubkeys ++ u16 threshold)). A
// Create multisig address // previous hand-rolled version here decoded each address with `Buffer.from(addr, 'hex')` -
// Formula: blake2(b"modlpy/utilisuba" + concat(sorted_members) + threshold) // treating an SS58 string as hex, which is simply wrong - and never hashed the preimage at
const multisigId = encodeAddress( // all, so it silently produced an address with no relationship to the real multisig account.
new Uint8Array([ // Caught by directly testing this function against the known real 5 bridge signatories,
...Buffer.from('modlpy/utilisuba'), // which threw immediately instead of quietly returning a bogus address.
...sortedMembers.flatMap((addr) => Array.from(Buffer.from(addr, 'hex'))), return encodeMultiAddress(members, threshold, ss58Format);
threshold,
]),
ss58Format
);
return multisigId;
} }
/** /**
+57
View File
@@ -4,6 +4,7 @@
// Handles wUSDT minting, burning, and reserve management // Handles wUSDT minting, burning, and reserve management
import type { ApiPromise } from '@pezkuwi/api'; import type { ApiPromise } from '@pezkuwi/api';
import { encodeAddress } from '@pezkuwi/util-crypto';
import { ASSET_IDS, ASSET_CONFIGS } from './wallet'; import { ASSET_IDS, ASSET_CONFIGS } from './wallet';
import { getMultisigMembers, createMultisigTx } from './multisig'; import { getMultisigMembers, createMultisigTx } from './multisig';
@@ -14,6 +15,32 @@ import { getMultisigMembers, createMultisigTx } from './multisig';
export const WUSDT_ASSET_ID = ASSET_CONFIGS.WUSDT.id; export const WUSDT_ASSET_ID = ASSET_CONFIGS.WUSDT.id;
export const WUSDT_DECIMALS = ASSET_CONFIGS.WUSDT.decimals; export const WUSDT_DECIMALS = ASSET_CONFIGS.WUSDT.decimals;
// ========================================
// BRIDGE CUSTODY (3-of-5 multisig)
// ========================================
// See res/validators-tiki.md "USDT Bridge Custody Multisig" and the
// usdt-bridge-multisig-migration memory for how these were derived/verified on-chain.
// Deliberately NOT env-overridable like the constants in wallet.ts - these are fixed,
// security-critical addresses, not per-deployment config; an env misconfiguration must
// never be able to silently point this UI at the wrong custody account.
/** The 3-of-5 multisig's own account on Pezkuwi Asset Hub - owns/issues wUSDT (asset 1000). */
export const WUSDT_BRIDGE_CUSTODY_PEZKUWI = '5GvwxmCDp3PC33KHoeWSgj3S7ocE7nzk1jiCCZMPSDBFeNcj';
/** Delegate key the multisig grants a bounded, auto-renewing Assets.approve_transfer allowance
* to (see pezkuwi-DKS/tools/usdt-bridge/src/bin/pezbridge_bot.rs) so small/routine deposits
* don't need a live 3-of-5 signature every time. */
export const WUSDT_AUTOMATION_KEY_ADDRESS = '5GQu4PFUb1f3MTJ7i7c1CtLgDk3TVvpSW1VbQCRmfkMoC8cM';
/** The standard top-up amount the multisig renews the automation key's allowance to (6
* decimals) - used to reconstruct and auto-describe that one deterministic pending call.
* MUST match pezbridge_bot_config.json's topup_amount AND the Android wallet's
* BridgeMultisigConstants.TOPUP_AMOUNT: all three signing channels build the SAME on-chain
* call, so a different amount here computes a different call hash - real renewals would
* show as "Unknown call" instead of auto-matching. 10,000 (the bot's old built-in default)
* was stale; the live config renews to 200,000 (renewal_threshold 40,000). */
export const STANDARD_RENEWAL_TOPUP = 200_000_000_000n; // 200,000 wUSDT
// Withdrawal limits and timeouts // Withdrawal limits and timeouts
export const WITHDRAWAL_LIMITS = { export const WITHDRAWAL_LIMITS = {
instant: { instant: {
@@ -162,6 +189,36 @@ export async function createBurnWUSDTTx(
// WITHDRAWAL HELPERS // WITHDRAWAL HELPERS
// ======================================== // ========================================
/**
* Builds the plain, single-signer transfer a user submits THEMSELVES to initiate a withdrawal:
* sending their own wUSDT to the multisig custody account. This is what usdt-bridge's
* listen_withdrawals() actually watches for (an Assets.Transferred event targeting the custody
* account) - the user never calls Assets.burn directly. Burning is the multisig's job, later,
* once real USDT has actually been released on the Polkadot side; burn requires Admin origin
* (the multisig), so a user-submitted burn call has always failed on-chain with NoPermission -
* see the fixed bug in USDTBridge.tsx's handleWithdrawal, which used to call burn directly and
* report success purely from `status.isFinalized` without checking whether the extrinsic
* actually succeeded (a failed dispatch is still included/finalized, so it always "succeeded").
* @param api - Polkadot API instance
* @param amount - Amount in human-readable format (e.g., 100.50 USDT)
* @returns Plain (non-multisig) transfer extrinsic the user signs with their own key
*/
export function createWithdrawalInitiationTx(api: ApiPromise, amount: number) {
const amountBN = parseWUSDT(amount);
return api.tx.assets.transfer(WUSDT_ASSET_ID, WUSDT_BRIDGE_CUSTODY_PEZKUWI, amountBN.toString());
}
/**
* Where real USDT actually gets released to on Polkadot Asset Hub: usdt-bridge's relayer
* (listen_withdrawals in main.rs) derives it by re-encoding the SENDER's own address under
* Polkadot's SS58 prefix (0) - the same underlying sr25519/ed25519 key, just a different
* network's address format. It does NOT read a separately user-specified destination, so the
* UI must show this derived address rather than pretend an arbitrary one can be entered.
*/
export function deriveWithdrawalDestination(pezkuwiAddress: string): string {
return encodeAddress(pezkuwiAddress, 0);
}
/** /**
* Calculate withdrawal delay based on amount * Calculate withdrawal delay based on amount
* @param amount - Withdrawal amount in USDT * @param amount - Withdrawal amount in USDT
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="noindex, nofollow" />
<title>PezBridge Sign</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+4251
View File
File diff suppressed because it is too large Load Diff
+30
View File
@@ -0,0 +1,30 @@
{
"name": "pezbridge-sign",
"private": true,
"version": "1.0.0",
"type": "module",
"description": "Dedicated, minimal-surface signing portal for PezkuwiChain multisig operations - gated to known signatory addresses only.",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"@pezkuwi/api": "^16.5.36",
"@pezkuwi/extension-dapp": "^0.62.20",
"@pezkuwi/extension-inject": "^0.62.20",
"@pezkuwi/keyring": "^14.0.25",
"@pezkuwi/util": "^14.0.25",
"@pezkuwi/util-crypto": "^14.0.25"
},
"devDependencies": {
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react-swc": "^3.5.0",
"typescript": "^5.6.3",
"vite": "^5.4.10",
"vite-plugin-node-polyfills": "^0.22.0"
}
}
+313
View File
@@ -0,0 +1,313 @@
import React, { useCallback, useEffect, useState } from 'react';
import { web3Accounts } from '@pezkuwi/extension-dapp';
import type { InjectedAccountWithMeta } from '@pezkuwi/extension-inject/types';
import type { ApiPromise } from '@pezkuwi/api';
import {
getMultisigMembers,
calculateMultisigAddress,
approveMultisigTx,
cancelMultisigTx,
isMultisigMember,
USDT_MULTISIG_CONFIG,
} from '@pezkuwi/lib/multisig';
import {
listPendingOperations,
decodeAndVerifyCallData,
type PendingOperation,
} from '@pezkuwi/lib/multisig-operations';
import { connectApi } from './lib/chain';
import { getSigner } from './lib/get-signer';
import { SIGNER_SETS, type SignerSet } from './signer-sets';
const PEX_MOM_URL = 'https://pex.mom';
interface OperationsPanelProps {
api: ApiPromise;
account: InjectedAccountWithMeta;
signerSet: SignerSet;
}
function OperationsPanel({ api, account, signerSet }: OperationsPanelProps) {
const [operations, setOperations] = useState<PendingOperation[]>([]);
const [otherSignatories, setOtherSignatories] = useState<string[]>([]);
const [loading, setLoading] = useState(true);
const [processingHash, setProcessingHash] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
const [manualCallData, setManualCallData] = useState<Record<string, string>>({});
const [decodeErrors, setDecodeErrors] = useState<Record<string, string>>({});
const load = useCallback(async () => {
setLoading(true);
setError(null);
try {
const allMembers = await getMultisigMembers(api, signerSet.specificAddresses);
const multisigAddr = calculateMultisigAddress(allMembers);
setOtherSignatories(allMembers.filter((addr) => addr !== account.address));
const ops = await listPendingOperations(api, multisigAddr);
setOperations(ops);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load pending operations');
} finally {
setLoading(false);
}
}, [api, account.address, signerSet]);
useEffect(() => {
load();
}, [load]);
const handleDecode = (callHash: string) => {
const hex = manualCallData[callHash];
if (!hex) return;
try {
const { call, description } = decodeAndVerifyCallData(api, hex, callHash);
setOperations((prev) =>
prev.map((op) => (op.callHash === callHash ? { ...op, resolvedCall: call, description, isAutoMatched: false } : op))
);
setDecodeErrors((prev) => ({ ...prev, [callHash]: '' }));
} catch (err) {
setDecodeErrors((prev) => ({ ...prev, [callHash]: err instanceof Error ? err.message : 'Decode failed' }));
}
};
const runExtrinsic = async (
callHash: string,
tx: ReturnType<typeof approveMultisigTx> | ReturnType<typeof cancelMultisigTx>,
successMessage: string
) => {
setProcessingHash(callHash);
setError(null);
setSuccess(null);
try {
const injector = await getSigner(account.address);
await new Promise<void>((resolve, reject) => {
tx.signAndSend(account.address, { signer: injector.signer }, ({ status, dispatchError }) => {
if (status.isInBlock || status.isFinalized) {
if (dispatchError) {
if (dispatchError.isModule) {
const decoded = api.registry.findMetaError(dispatchError.asModule);
reject(new Error(`${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`));
} else {
reject(new Error(dispatchError.toString()));
}
} else {
resolve();
}
}
}).catch(reject);
});
setSuccess(successMessage);
await load();
} catch (err) {
setError(err instanceof Error ? err.message : 'Transaction failed');
} finally {
setProcessingHash(null);
}
};
const handleApprove = (op: PendingOperation) => {
if (!op.resolvedCall) return;
const tx = approveMultisigTx(api, op.resolvedCall, otherSignatories, op.when, op.threshold);
runExtrinsic(op.callHash, tx, 'Approval submitted successfully.');
};
const handleReject = (op: PendingOperation) => {
const tx = cancelMultisigTx(api, op.callHash, otherSignatories, op.when, op.threshold);
runExtrinsic(op.callHash, tx, 'Operation cancelled successfully.');
};
return (
<section className="panel">
<div className="panel-header">
<h2>{signerSet.name}</h2>
<button className="btn-outline" onClick={load} disabled={loading}>
Refresh
</button>
</div>
{error && <div className="alert alert-error">{error}</div>}
{success && <div className="alert alert-success">{success}</div>}
{loading ? (
<div className="loading">Loading pending operations</div>
) : operations.length === 0 ? (
<div className="empty">No pending operations right now.</div>
) : (
<div className="ops-list">
{operations.map((op) => {
const alreadyApproved = op.approvals.includes(account.address);
const canReject = op.depositor === account.address;
const isProcessing = processingHash === op.callHash;
return (
<div key={op.callHash} className="op-card">
<div className="op-header">
<div>
<p className="op-desc">{op.description}</p>
<code className="op-hash">
{op.callHash.slice(0, 10)}{op.callHash.slice(-8)}
</code>
</div>
<span className={`badge ${op.approvalsCount >= op.threshold ? 'badge-ready' : ''}`}>
{op.approvalsCount}/{op.threshold} approvals
</span>
</div>
<p className="op-meta">
Proposed by <code>{op.depositor.slice(0, 8)}{op.depositor.slice(-6)}</code>
</p>
{alreadyApproved && <p className="op-approved">You already approved this operation</p>}
{!op.resolvedCall && (
<div className="decode-box">
<p className="hint">This call couldn't be auto-identified from its hash alone - paste the known call data to decode and verify it.</p>
<textarea
placeholder="Paste call data (0x...)"
value={manualCallData[op.callHash] ?? ''}
onChange={(e) => setManualCallData((prev) => ({ ...prev, [op.callHash]: e.target.value }))}
rows={2}
/>
{decodeErrors[op.callHash] && <p className="error-text">{decodeErrors[op.callHash]}</p>}
<button className="btn-outline" onClick={() => handleDecode(op.callHash)} disabled={!manualCallData[op.callHash]}>
Decode
</button>
</div>
)}
<div className="op-actions">
<button
className="btn-primary"
onClick={() => handleApprove(op)}
disabled={!op.resolvedCall || alreadyApproved || isProcessing}
>
{isProcessing ? 'Processing' : 'Approve'}
</button>
{canReject && (
<button className="btn-danger" onClick={() => handleReject(op)} disabled={isProcessing}>
Reject
</button>
)}
</div>
</div>
);
})}
</div>
)}
<p className="threshold-note">
{USDT_MULTISIG_CONFIG.threshold} of {USDT_MULTISIG_CONFIG.members.length} signatures are required to execute any
operation - no single signer, including this site, can move funds alone.
</p>
</section>
);
}
export default function App() {
const [api, setApi] = useState<ApiPromise | null>(null);
const [apiReady, setApiReady] = useState(false);
const [account, setAccount] = useState<InjectedAccountWithMeta | null>(null);
const [accounts, setAccounts] = useState<InjectedAccountWithMeta[]>([]);
const [authorizedSets, setAuthorizedSets] = useState<SignerSet[] | null>(null); // null = not checked yet
const [connecting, setConnecting] = useState(false);
const [connectError, setConnectError] = useState<string | null>(null);
useEffect(() => {
connectApi()
.then((instance) => {
setApi(instance);
setApiReady(true);
})
.catch((err) => setConnectError(err instanceof Error ? err.message : 'Failed to connect to chain'));
}, []);
const handleConnect = useCallback(async () => {
setConnecting(true);
setConnectError(null);
try {
const accs = await web3Accounts();
if (accs.length === 0) {
setConnectError(
'No accounts found. Install a Pezkuwi/Substrate wallet extension, unlock it, and authorize this site.'
);
return;
}
setAccounts(accs);
setAccount(accs[0]);
} catch (err) {
setConnectError(err instanceof Error ? err.message : String(err));
} finally {
setConnecting(false);
}
}, []);
// Gate: once a wallet is connected, check membership across every known signer set. Anyone
// not authorized for at least one is bounced to pex.mom - this page has nothing to show them.
useEffect(() => {
if (!api || !apiReady || !account) return;
let cancelled = false;
Promise.all(SIGNER_SETS.map((set) => isMultisigMember(api, account.address, set.specificAddresses))).then(
(results) => {
if (cancelled) return;
const matched = SIGNER_SETS.filter((_, i) => results[i]);
if (matched.length === 0) {
window.location.href = PEX_MOM_URL;
return;
}
setAuthorizedSets(matched);
}
);
return () => {
cancelled = true;
};
}, [api, apiReady, account]);
return (
<div className="app">
<header className="app-header">
<h1>PezBridge Sign</h1>
<p className="subtitle">Signing portal for PezkuwiChain multisig operations</p>
</header>
{connectError && <div className="alert alert-error">{connectError}</div>}
{!account ? (
<div className="connect-box">
<p>Connect the wallet extension holding one of your authorized signer accounts.</p>
<button className="btn-primary" onClick={handleConnect} disabled={connecting || !apiReady}>
{connecting ? 'Connecting' : !apiReady ? 'Connecting to chain' : 'Connect Wallet'}
</button>
</div>
) : authorizedSets === null ? (
<div className="loading">Verifying signer status on-chain</div>
) : (
<>
{accounts.length > 1 && (
<div className="account-switcher">
<label htmlFor="account-select">Account:</label>
<select
id="account-select"
value={account.address}
onChange={(e) => {
const next = accounts.find((a) => a.address === e.target.value) ?? null;
setAccount(next);
setAuthorizedSets(null);
}}
>
{accounts.map((a) => (
<option key={a.address} value={a.address}>
{a.meta.name ?? a.address}
</option>
))}
</select>
</div>
)}
{api &&
authorizedSets.map((set) => <OperationsPanel key={set.name} api={api} account={account} signerSet={set} />)}
</>
)}
</div>
);
}
+242
View File
@@ -0,0 +1,242 @@
:root {
color-scheme: dark;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
background: #0a0e14;
color: #e6e9ef;
}
.app {
max-width: 720px;
margin: 0 auto;
padding: 48px 20px;
}
.app-header {
text-align: center;
margin-bottom: 32px;
}
.app-header h1 {
margin: 0 0 4px;
font-size: 1.5rem;
}
.subtitle {
color: #8b93a3;
margin: 0;
font-size: 0.9rem;
}
.connect-box {
text-align: center;
padding: 40px 20px;
background: #131a24;
border: 1px solid #232c3a;
border-radius: 12px;
}
.connect-box p {
color: #8b93a3;
margin-bottom: 20px;
}
.account-switcher {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
font-size: 0.85rem;
color: #8b93a3;
}
.account-switcher select {
background: #131a24;
color: #e6e9ef;
border: 1px solid #232c3a;
border-radius: 6px;
padding: 4px 8px;
}
.panel {
background: #131a24;
border: 1px solid #232c3a;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.panel-header h2 {
margin: 0;
font-size: 1.1rem;
}
.loading,
.empty {
text-align: center;
color: #8b93a3;
padding: 24px 0;
}
.ops-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.op-card {
background: #0f1520;
border: 1px solid #232c3a;
border-radius: 8px;
padding: 14px;
}
.op-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 12px;
}
.op-desc {
margin: 0;
font-weight: 600;
}
.op-hash {
font-size: 0.75rem;
color: #6b7385;
}
.op-meta {
font-size: 0.8rem;
color: #8b93a3;
margin: 8px 0 0;
}
.op-approved {
font-size: 0.8rem;
color: #4ade80;
margin: 4px 0 0;
}
.badge {
font-size: 0.75rem;
border: 1px solid #3a4557;
border-radius: 999px;
padding: 2px 10px;
white-space: nowrap;
color: #8b93a3;
}
.badge-ready {
border-color: #4ade80;
color: #4ade80;
}
.decode-box {
margin-top: 10px;
}
.decode-box textarea {
width: 100%;
background: #0a0e14;
color: #e6e9ef;
border: 1px solid #232c3a;
border-radius: 6px;
padding: 8px;
font-family: monospace;
font-size: 0.75rem;
margin: 6px 0;
}
.hint {
font-size: 0.75rem;
color: #e0b04a;
margin: 0;
}
.error-text {
font-size: 0.75rem;
color: #f87171;
}
.op-actions {
display: flex;
gap: 8px;
margin-top: 12px;
}
.threshold-note {
margin-top: 16px;
font-size: 0.75rem;
color: #6b7385;
}
.btn-primary,
.btn-outline,
.btn-danger {
border-radius: 6px;
padding: 8px 16px;
font-size: 0.85rem;
cursor: pointer;
border: 1px solid transparent;
}
.btn-primary {
background: #4ade80;
color: #0a0e14;
font-weight: 600;
}
.btn-primary:disabled,
.btn-outline:disabled,
.btn-danger:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-outline {
background: transparent;
color: #e6e9ef;
border-color: #3a4557;
}
.btn-danger {
background: #f87171;
color: #1a0a0a;
font-weight: 600;
}
.alert {
border-radius: 8px;
padding: 10px 14px;
margin-bottom: 16px;
font-size: 0.85rem;
}
.alert-error {
background: rgba(248, 113, 113, 0.1);
border: 1px solid rgba(248, 113, 113, 0.4);
color: #f87171;
}
.alert-success {
background: rgba(74, 222, 128, 0.1);
border: 1px solid rgba(74, 222, 128, 0.4);
color: #4ade80;
}
+10
View File
@@ -0,0 +1,10 @@
import { ApiPromise, WsProvider } from '@pezkuwi/api';
// Same endpoint the rest of this ecosystem's tooling (pwap-web, PezbridgeBot) uses for Pezkuwi
// Asset Hub - the chain this multisig lives on.
const RPC_ENDPOINT = 'wss://asset-hub-rpc.pezkuwichain.io';
export function connectApi(): Promise<ApiPromise> {
const provider = new WsProvider(RPC_ENDPOINT);
return ApiPromise.create({ provider });
}
+13
View File
@@ -0,0 +1,13 @@
/**
* Extension-only signer helper - deliberately simpler than pwap-web's get-signer.ts (which also
* supports WalletConnect for mobile). The real 5 multisig signatories operate from desktop/
* server contexts, and this site's whole purpose is to be a minimal, easy-to-audit surface -
* not pulling in the WalletConnect session-management stack keeps that true. Add it back here
* only if a signer genuinely needs to sign from a phone.
*/
import { web3Enable, web3FromAddress } from '@pezkuwi/extension-dapp';
export async function getSigner(address: string) {
await web3Enable('PezBridge Sign');
return web3FromAddress(address);
}
+12
View File
@@ -0,0 +1,12 @@
// Production Rollup alias for vite-plugin-node-polyfills/shims/process (mirrors web/'s copy -
// see its comment for why: Rollup can't resolve the plugin's virtual module during a real
// build, only in dev). IMPORTANT: must not reference the `process` identifier at runtime -
// vite-plugin-node-polyfills rewrites it to `__process_polyfill`, creating a circular TDZ. Use
// bracket notation so the plugin leaves this file alone.
const g: Record<string, unknown> =
typeof globalThis !== 'undefined' ? (globalThis as Record<string, unknown>)
: typeof window !== 'undefined' ? (window as unknown as Record<string, unknown>)
: {};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export default (g['process'] ?? { env: {} }) as any;
+10
View File
@@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
+16
View File
@@ -0,0 +1,16 @@
import { BRIDGE_MULTISIG_SPECIFIC_ADDRESSES } from '@pezkuwi/lib/multisig';
export interface SignerSet {
name: string;
specificAddresses: Record<string, string>;
}
/**
* Every known multisig this site gates access for. The connected wallet is authorized if it's
* a member of ANY set below - add new entries here as new signing needs come up (this is the
* single registry the login gate checks against, per the design decision to keep one dedicated
* portal for all future signing needs rather than a bespoke UI per multisig).
*/
export const SIGNER_SETS: SignerSet[] = [
{ name: 'USDT Bridge Treasury', specificAddresses: BRIDGE_MULTISIG_SPECIFIC_ADDRESSES },
];
+32
View File
@@ -0,0 +1,32 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"resolveJsonModule": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
"strict": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitAny": false,
"noFallthroughCasesInSwitch": false,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@pezkuwi/lib": ["../shared/lib"],
"@pezkuwi/lib/*": ["../shared/lib/*"]
},
"types": ["vite/client"]
},
"include": ["src"]
}
+7
View File
@@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.ts"]
}
+41
View File
@@ -0,0 +1,41 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import path from 'path';
import { nodePolyfills } from 'vite-plugin-node-polyfills';
// Deliberately minimal compared to web/vite.config.ts: this app has exactly one job (gate +
// sign multisig operations), so it carries none of web/'s SPA routing, i18n, or UI-kit
// machinery - fewer dependencies is itself a security property for a signing-critical site.
export default defineConfig(({ command }) => ({
server: {
host: '::',
port: 8090,
},
plugins: [
react(),
nodePolyfills({
globals: { Buffer: true, global: true, process: true },
protocolImports: true,
}),
],
resolve: {
mainFields: ['browser', 'module', 'main', 'exports'],
alias: {
// Rollup cannot resolve the plugin's virtual shim module in production - alias to a real
// file (mirrors web/vite.config.ts's identical workaround). Dev mode leaves the plugin's
// own virtual module handling it.
...(command === 'build'
? { 'vite-plugin-node-polyfills/shims/process': path.resolve(__dirname, './src/lib/process-shim.ts') }
: {}),
'@': path.resolve(__dirname, './src'),
'@pezkuwi/lib': path.resolve(__dirname, '../shared/lib'),
},
dedupe: ['react', '@pezkuwi/util-crypto', '@pezkuwi/util', '@pezkuwi/api', '@pezkuwi/extension-dapp', '@pezkuwi/keyring'],
},
optimizeDeps: {
include: ['@pezkuwi/util-crypto', '@pezkuwi/util', '@pezkuwi/api', '@pezkuwi/extension-dapp', '@pezkuwi/keyring', 'buffer'],
},
build: {
chunkSizeWarningLimit: 600,
},
}));
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -42,7 +42,7 @@ VITE_ENABLE_DEMO_MODE=true
# 1. Project URL: Copy from "Project URL" section # 1. Project URL: Copy from "Project URL" section
# 2. Anon key: Copy from "Project API keys" → "anon" → "public" # 2. Anon key: Copy from "Project API keys" → "anon" → "public"
VITE_SUPABASE_URL=https://vbhftvdayqfmcgmzdxfv.supabase.co VITE_SUPABASE_URL=https://supabase.pezkuwichain.io
VITE_SUPABASE_ANON_KEY=your_supabase_anon_key_here VITE_SUPABASE_ANON_KEY=your_supabase_anon_key_here
# ======================================== # ========================================
+53
View File
@@ -0,0 +1,53 @@
# pwap/web — Static SPA build for distribution.
# Build context is the pwap repo ROOT (not web/) because vite aliases like
# @pezkuwi/utils, @shared/* resolve to ../shared/* — both web/ and shared/
# must be in the build context.
# Stage 1: build with Node. Stage 2: pure dist/ in busybox (smallest possible
# attacker surface — no shell, no package manager, no node runtime).
# Tag the resulting image with the git SHA in CI so rollback is just
# "pull pwap-web:<old-sha>".
# ─── Stage 1: Build ────────────────────────────────────────────
FROM node:20-alpine AS builder
WORKDIR /build/web
# Copy package files first to leverage Docker layer cache when only src changes
COPY web/package.json web/package-lock.json ./
RUN npm ci
# Copy shared/ first (less frequently changed), then web/ source
COPY shared/ /build/shared/
COPY web/ /build/web/
# Build args for environment-specific values (passed from CI)
ARG VITE_NETWORK=MAINNET
ARG VITE_WS_ENDPOINT=wss://rpc.pezkuwichain.io
ARG VITE_WS_ENDPOINT_FALLBACK_1=wss://mainnet.pezkuwichain.io
ARG VITE_ASSET_HUB_ENDPOINT=wss://asset-hub-rpc.pezkuwichain.io
ARG VITE_PEOPLE_CHAIN_ENDPOINT=wss://people-rpc.pezkuwichain.io
ARG VITE_WALLETCONNECT_PROJECT_ID=8292a793b7640e8364c378e331e76d04
ARG VITE_SUPABASE_URL
ARG VITE_SUPABASE_ANON_KEY
ENV VITE_NETWORK=$VITE_NETWORK
ENV VITE_WS_ENDPOINT=$VITE_WS_ENDPOINT
ENV VITE_WS_ENDPOINT_FALLBACK_1=$VITE_WS_ENDPOINT_FALLBACK_1
ENV VITE_ASSET_HUB_ENDPOINT=$VITE_ASSET_HUB_ENDPOINT
ENV VITE_PEOPLE_CHAIN_ENDPOINT=$VITE_PEOPLE_CHAIN_ENDPOINT
ENV VITE_WALLETCONNECT_PROJECT_ID=$VITE_WALLETCONNECT_PROJECT_ID
ENV VITE_SUPABASE_URL=$VITE_SUPABASE_URL
ENV VITE_SUPABASE_ANON_KEY=$VITE_SUPABASE_ANON_KEY
RUN npm run build
# ─── Stage 2: Distribution image ───────────────────────────────
# busybox:musl gives us a tiny base (~1.5MB) with a shell for `cp` operations
# during deploy extraction, but no npm/curl/wget/ssh — minimal attack surface
# if the image were ever exposed.
FROM busybox:musl
WORKDIR /dist
COPY --from=builder /build/web/dist /dist
LABEL org.opencontainers.image.source="https://github.com/pezkuwichain/pwap"
LABEL org.opencontainers.image.description="pwap/web static SPA — Pezkuwi wallet/exchange frontend"
LABEL org.opencontainers.image.licenses="proprietary"
CMD ["sh", "-c", "echo 'pwap-web image — extract /dist via: docker create + docker cp'; sleep infinity"]
+300 -271
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -120,7 +120,8 @@
"@pezkuwi/x-textdecoder": "^14.0.25", "@pezkuwi/x-textdecoder": "^14.0.25",
"@pezkuwi/x-textencoder": "^14.0.25", "@pezkuwi/x-textencoder": "^14.0.25",
"@pezkuwi/x-ws": "^14.0.25", "@pezkuwi/x-ws": "^14.0.25",
"@pezkuwi/networks": "^14.0.25" "@pezkuwi/networks": "^14.0.25",
"elliptic": "^6.6.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.9.0", "@eslint/js": "^9.9.0",
@@ -147,6 +148,7 @@
"typescript-eslint": "^8.0.1", "typescript-eslint": "^8.0.1",
"vite": "^7.3.1", "vite": "^7.3.1",
"vite-plugin-node-polyfills": "^0.25.0", "vite-plugin-node-polyfills": "^0.25.0",
"vite-plugin-subresource-integrity": "^0.0.12",
"vitest": "^4.0.10" "vitest": "^4.0.10"
} }
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

+38
View File
@@ -0,0 +1,38 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
<circle cx="100" cy="100" r="100" fill="#161A1E"></circle>
<g opacity="0.92">
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(0 100 100)"></line>
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(17.14 100 100)"></line>
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(34.28 100 100)"></line>
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(51.43 100 100)"></line>
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(68.57 100 100)"></line>
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(85.71 100 100)"></line>
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(102.86 100 100)"></line>
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(120 100 100)"></line>
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(137.14 100 100)"></line>
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(154.28 100 100)"></line>
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(171.43 100 100)"></line>
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(188.57 100 100)"></line>
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(205.71 100 100)"></line>
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(222.86 100 100)"></line>
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(240 100 100)"></line>
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(257.14 100 100)"></line>
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(274.28 100 100)"></line>
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(291.43 100 100)"></line>
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(308.57 100 100)"></line>
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(325.71 100 100)"></line>
<line x1="100" y1="100" x2="100" y2="30" stroke="rgba(255,215,0,0.95)" stroke-width="3.5" stroke-linecap="round" transform="rotate(342.86 100 100)"></line>
</g>
<circle cx="100" cy="100" r="74" fill="none" stroke="#00CC00" stroke-width="3" stroke-dasharray="116 116" stroke-dashoffset="0"></circle>
<circle cx="100" cy="100" r="74" fill="none" stroke="#FF2222" stroke-width="3" stroke-dasharray="116 116" stroke-dashoffset="116"></circle>
<circle cx="100" cy="100" r="32" fill="#FFD700"></circle>
<circle cx="100" cy="100" r="26" fill="white"></circle>
<circle cx="100" cy="100" r="20" fill="#FFD700"></circle>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

+33
View File
@@ -0,0 +1,33 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 600">
<rect width="900" height="200" fill="#ED2024"></rect>
<rect y="200" width="900" height="200" fill="#FFFFFF"></rect>
<rect y="400" width="900" height="200" fill="#21A038"></rect>
<circle cx="450" cy="300" r="100" fill="#FECC02"></circle>
<g fill="#FECC02">
<polygon points="450,145 440,200 460,200" transform="rotate(0,450,300)"></polygon>
<polygon points="450,145 440,200 460,200" transform="rotate(17.14,450,300)"></polygon>
<polygon points="450,145 440,200 460,200" transform="rotate(34.29,450,300)"></polygon>
<polygon points="450,145 440,200 460,200" transform="rotate(51.43,450,300)"></polygon>
<polygon points="450,145 440,200 460,200" transform="rotate(68.57,450,300)"></polygon>
<polygon points="450,145 440,200 460,200" transform="rotate(85.71,450,300)"></polygon>
<polygon points="450,145 440,200 460,200" transform="rotate(102.86,450,300)"></polygon>
<polygon points="450,145 440,200 460,200" transform="rotate(120,450,300)"></polygon>
<polygon points="450,145 440,200 460,200" transform="rotate(137.14,450,300)"></polygon>
<polygon points="450,145 440,200 460,200" transform="rotate(154.29,450,300)"></polygon>
<polygon points="450,145 440,200 460,200" transform="rotate(171.43,450,300)"></polygon>
<polygon points="450,145 440,200 460,200" transform="rotate(188.57,450,300)"></polygon>
<polygon points="450,145 440,200 460,200" transform="rotate(205.71,450,300)"></polygon>
<polygon points="450,145 440,200 460,200" transform="rotate(222.86,450,300)"></polygon>
<polygon points="450,145 440,200 460,200" transform="rotate(240,450,300)"></polygon>
<polygon points="450,145 440,200 460,200" transform="rotate(257.14,450,300)"></polygon>
<polygon points="450,145 440,200 460,200" transform="rotate(274.29,450,300)"></polygon>
<polygon points="450,145 440,200 460,200" transform="rotate(291.43,450,300)"></polygon>
<polygon points="450,145 440,200 460,200" transform="rotate(308.57,450,300)"></polygon>
<polygon points="450,145 440,200 460,200" transform="rotate(325.71,450,300)"></polygon>
<polygon points="450,145 440,200 460,200" transform="rotate(342.86,450,300)"></polygon>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

-32
View File
@@ -1,32 +0,0 @@
{
"Getting Started": {
"Introduction": "introduction.md",
"Whitepaper": "whitepaper/whitepaper.md"
},
"SDK Reference": {
"📚 Rust SDK Docs": "sdk://open",
"Runtimes & Pallets": "runtimes-pallets.md"
},
"General Docs": {
"AUDIT": "AUDIT.md",
"BACKPORT": "BACKPORT.md",
"RELEASE": "RELEASE.md",
"Workflow Rebranding": "workflow_rebranding.md"
},
"Contributor": {
"CODE OF CONDUCT": "contributor/CODE_OF_CONDUCT.md",
"Commands Readme": "contributor/commands-readme.md",
"Container": "contributor/container.md",
"CONTRIBUTING": "contributor/CONTRIBUTING.md",
"DEPRECATION CHECKLIST": "contributor/DEPRECATION_CHECKLIST.md",
"Docker": "contributor/docker.md",
"DOCUMENTATION GUIDELINES": "contributor/DOCUMENTATION_GUIDELINES.md",
"Markdown Linting": "contributor/markdown_linting.md",
"Prdoc": "contributor/prdoc.md",
"PULL REQUEST TEMPLATE": "contributor/PULL_REQUEST_TEMPLATE.md",
"SECURITY": "contributor/SECURITY.md",
"STYLE GUIDE": "contributor/STYLE_GUIDE.md",
"Weight Generation": "contributor/weight-generation.md"
},
"README": "README.md"
}
-139
View File
@@ -1,139 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.*
!.env.example
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Sveltekit cache directory
.svelte-kit/
# vitepress build output
**/.vitepress/dist
# vitepress cache directory
**/.vitepress/cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# Firebase cache directory
.firebase/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v3
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# Vite logs files
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
-201
View File
@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-2
View File
@@ -1,2 +0,0 @@
# docs.pezkuwichain.io
A sovereign blockchain parachain built for the Kurdish Nation and Culturel Nations of the world, on blockchain
Binary file not shown.

Before

Width:  |  Height:  |  Size: 634 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 601 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 742 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 750 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 429 KiB

-12
View File
@@ -1,12 +0,0 @@
flowchart TD
dot[pezkuwichain.io] --> devhub[pezkuwi_sdk_docs]
devhub --> pezkuwi_sdk
devhub --> reference_docs
devhub --> guides
devhub --> external_resources
pezkuwi_sdk --> bizinikiwi
pezkuwi_sdk --> frame
pezkuwi_sdk --> xcm
pezkuwi_sdk --> templates
@@ -1,12 +0,0 @@
graph TB
subgraph Bizinikiwi
direction LR
subgraph Node
end
subgraph Runtime
end
Node --runtime-api--> Runtime
Runtime --host-functions--> Node
end
@@ -1,2 +0,0 @@
flowchart LR
T[Using a Template] --> P[Writing Your Own FRAME-Based Pallet] --> C[Custom Node]
@@ -1,8 +0,0 @@
graph TB
subgraph Bizinikiwi
direction LR
subgraph Node
end
subgraph Runtime
end
end
@@ -1,20 +0,0 @@
graph TB
subgraph Bizinikiwi
direction LR
subgraph Node
Database
Networking
Consensus
end
subgraph Runtime
subgraph FRAME
direction LR
Governance
Currency
Staking
Identity
end
end
Node --runtime-api--> Runtime
Runtime --host-functions--> Node
end
-5
View File
@@ -1,5 +0,0 @@
flowchart TD
E(Extrinsic) ---> I(Inherent);
E --> T(Transaction)
T --> ST("Signed (aka. Transaction)")
T --> UT(Unsigned)
@@ -1,3 +0,0 @@
flowchart LR
RuntimeCall --"TryInto"--> PalletCall
PalletCall --"Into"--> RuntimeCall
@@ -1,8 +0,0 @@
flowchart LR
subgraph PezkuwiSDKChain[A Pezkuwi SDK-based blockchain]
Node
Runtime
end
FRAME -.-> Runtime
PezkuwiSDK[Pezkuwi SDK Node Libraries] -.-> Node
@@ -1,10 +0,0 @@
flowchart LR
subgraph Pezkuwi[The Pezkuwi Relay Chain]
PezkuwiNode[Pezkuwi Node]
PezkuwiRuntime[Pezkuwi Runtime]
end
FRAME -.-> PezkuwiRuntime
PezkuwiSDK[Pezkuwi SDK Node Libraries] -.-> PezkuwiNode
@@ -1,11 +0,0 @@
flowchart LR
subgraph TeyrChain[A Pezkuwi TeyrChain]
TeyrChainNode[TeyrChain Node]
TeyrChainRuntime[TeyrChain Runtime]
end
FRAME -.-> TeyrChainRuntime
PezkuwiSDK[Pezkuwi SDK Node Libraries] -.-> TeyrChainNode
CumulusC[Pezcumulus Node Libraries] -.-> TeyrChainNode
CumulusR[Pezcumulus Runtime Libraries] -.-> TeyrChainRuntime
-16
View File
@@ -1,16 +0,0 @@
flowchart TB
subgraph Node[Node's View Of The State 🙈]
direction LR
0x1234 --> 0x2345
0x3456 --> 0x4567
0x5678 --> 0x6789
:code --> code[wasm code]
end
subgraph Runtime[Runtime's View Of The State 🙉]
direction LR
ab[alice's balance] --> abv[known value]
bb[bob's balance] --> bbv[known value]
cb[charlie's balance] --> cbv[known value]
c2[:code] --> c22[wasm code]
end
-21
View File
@@ -1,21 +0,0 @@
flowchart LR
%%{init: {'flowchart' : {'curve' : 'linear'}}}%%
subgraph BData[Blockchain Database]
direction LR
BN[Block N] -.-> BN1[Block N+1]
end
subgraph SData[State Database]
direction LR
SN[State N] -.-> SN1[State N+1] -.-> SN2[State N+2]
end
BN --> STFN[STF]
SN --> STFN[STF]
STFN[STF] --> SN1
BN1 --> STFN1[STF]
SN1 --> STFN1[STF]
STFN1[STF] --> SN2
-4
View File
@@ -1,4 +0,0 @@
flowchart LR
B[Block] --> STF
S[State] --> STF
STF --> NS[New State]
-107
View File
@@ -1,107 +0,0 @@
#!/usr/bin/env python3
"""
Slow Crate Publisher - 6 dakikada bir 1 crate publish eder
Rate limit'e takilmamak icin yavas yavas publish yapar.
Kullanim:
nohup python3 publish_crates_slow.py > publish_log.txt 2>&1 &
"""
import subprocess
import os
import time
from datetime import datetime
PLACEHOLDER_DIR = '/home/mamostehp/kurdistan-sdk/crate_placeholders'
LOG_FILE = '/home/mamostehp/kurdistan-sdk/publish_log.txt'
INTERVAL_SECONDS = 360 # 6 dakika
def log(msg):
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
line = f"[{timestamp}] {msg}"
print(line, flush=True)
with open(LOG_FILE, 'a') as f:
f.write(line + '\n')
def is_published(name):
"""crates.io'da mevcut mu kontrol et"""
result = subprocess.run(
['cargo', 'search', name, '--limit', '1'],
capture_output=True, text=True, timeout=30
)
return f'{name} = ' in result.stdout
def publish_crate(name):
"""Tek bir crate publish et"""
crate_dir = os.path.join(PLACEHOLDER_DIR, name)
manifest = os.path.join(crate_dir, 'Cargo.toml')
if not os.path.exists(manifest):
return False, "Cargo.toml not found"
result = subprocess.run(
['cargo', 'publish', '--manifest-path', manifest],
capture_output=True, text=True, cwd=crate_dir, timeout=180
)
if result.returncode == 0:
return True, "Success"
elif 'already uploaded' in result.stderr or 'already exists' in result.stderr:
return True, "Already exists"
elif '429' in result.stderr or 'Too Many Requests' in result.stderr:
return False, "Rate limited"
else:
return False, result.stderr[:200]
def get_unpublished_crates():
"""Henuz publish edilmemis crate'leri bul"""
crates = sorted([d for d in os.listdir(PLACEHOLDER_DIR)
if os.path.isdir(os.path.join(PLACEHOLDER_DIR, d))])
unpublished = []
for crate in crates:
if not is_published(crate):
unpublished.append(crate)
return unpublished
def main():
log("=" * 60)
log("Slow Crate Publisher baslatildi")
log(f"Interval: {INTERVAL_SECONDS} saniye (6 dakika)")
log("=" * 60)
unpublished = get_unpublished_crates()
total = len(unpublished)
log(f"Toplam {total} crate publish edilecek")
success_count = 0
fail_count = 0
for i, crate in enumerate(unpublished, 1):
log(f"[{i}/{total}] Publishing: {crate}")
success, msg = publish_crate(crate)
if success:
log(f"{msg}")
success_count += 1
else:
log(f"{msg}")
fail_count += 1
# Rate limit durumunda ekstra bekle
if "Rate limited" in msg:
log(" Rate limited! 10 dakika bekleniyor...")
time.sleep(600)
# Sonraki crate icin bekle
if i < total:
log(f" Sonraki crate icin {INTERVAL_SECONDS}s bekleniyor...")
time.sleep(INTERVAL_SECONDS)
log("=" * 60)
log(f"Tamamlandi! Basarili: {success_count}, Basarisiz: {fail_count}")
log("=" * 60)
if __name__ == "__main__":
main()
-214
View File
@@ -1,214 +0,0 @@
import os
import sys
# Rebranding haritası
REBRAND_MAP = [
("asset-test-utils", "asset-test-pezutils"),
("chain-spec-guide-runtime", "pez-chain-spec-guide-runtime"),
("equivocation-detector", "pez-equivocation-detector"),
("erasure-coding-fuzzer", "pez-erasure-coding-fuzzer"),
("ethereum-standards", "pez-ethereum-standards"),
("finality-relay", "pez-finality-relay"),
("fork-tree", "pez-fork-tree"),
("generate-bags", "pez-generate-bags"),
("kitchensink-runtime", "pez-kitchensink-runtime"),
("messages-relay", "pez-messages-relay"),
("minimal-template-node", "pez-minimal-template-node"),
("minimal-template-runtime", "pez-minimal-template-runtime"),
("node-bench", "pez-node-bench"),
("node-primitives", "pez-node-primitives"),
("node-rpc", "pez-node-rpc"),
("node-runtime-generate-bags", "pez-node-runtime-generate-bags"),
("node-template-release", "pez-node-template-release"),
("node-testing", "pez-node-testing"),
("penpal-emulated-chain", "pez-penpal-emulated-chain"),
("penpal-runtime", "pez-penpal-runtime"),
("remote-ext-tests-bags-list", "pez-remote-ext-tests-bags-list"),
("revive-dev-node", "pez-revive-dev-node"),
("revive-dev-runtime", "pez-revive-dev-runtime"),
("slot-range-helper", "pez-slot-range-helper"),
("solochain-template-node", "pez-solochain-template-node"),
("solochain-template-runtime", "pez-solochain-template-runtime"),
("subkey", "pez-subkey"),
("template-zombienet-tests", "pez-template-zombienet-tests"),
("test-runtime-constants", "peztest-runtime-constants"),
("tracing-gum", "pez-tracing-gum"),
("tracing-gum-proc-macro", "pez-tracing-gum-proc-macro"),
("bp-header-chain", "bp-header-pez-chain"),
("bp-runtime", "pezbp-runtime"),
("bridge-hub-pezkuwichain-emulated-chain", "pezbridge-hub-pezkuwichain-emulated-chain"),
("bridge-hub-pezkuwichain-integration-tests", "pezbridge-hub-pezkuwichain-integration-tests"),
("bridge-hub-pezkuwichain-runtime", "pezbridge-hub-pezkuwichain-runtime"),
("bridge-hub-test-utils", "pezbridge-hub-test-utils"),
("bridge-hub-zagros-emulated-chain", "pezbridge-hub-zagros-emulated-chain"),
("bridge-hub-zagros-integration-tests", "pezbridge-hub-zagros-integration-tests"),
("bridge-hub-zagros-runtime", "pezbridge-hub-zagros-runtime"),
("bridge-runtime-common", "pezbridge-runtime-common"),
("mmr-gadget", "pezmmr-gadget"),
("mmr-rpc", "pezmmr-rpc"),
("snowbridge-beacon-primitives", "pezsnowbridge-beacon-primitives"),
("snowbridge-core", "pezsnowbridge-core"),
("snowbridge-ethereum", "pezsnowbridge-ethereum"),
("snowbridge-inbound-queue-primitives", "pezsnowbridge-inbound-queue-primitives"),
("snowbridge-merkle-tree", "pezsnowbridge-merkle-tree"),
("snowbridge-outbound-queue-primitives", "pezsnowbridge-outbound-queue-primitives"),
("snowbridge-outbound-queue-runtime-api", "pezsnowbridge-outbound-queue-runtime-api"),
("snowbridge-outbound-queue-v2-runtime-api", "pezsnowbridge-outbound-queue-v2-runtime-api"),
("snowbridge-pezpallet-ethereum-client", "snowbridge-pezpallet-ethereum-client"),
("snowbridge-pezpallet-ethereum-client-fixtures", "snowbridge-pezpallet-ethereum-client-fixtures"),
("snowbridge-pezpallet-inbound-queue", "snowbridge-pezpallet-inbound-queue"),
("snowbridge-pezpallet-inbound-queue-fixtures", "snowbridge-pezpallet-inbound-queue-fixtures"),
("snowbridge-pezpallet-inbound-queue-v2", "snowbridge-pezpallet-inbound-queue-v2"),
("snowbridge-pezpallet-inbound-queue-v2-fixtures", "snowbridge-pezpallet-inbound-queue-v2-fixtures"),
("snowbridge-pezpallet-outbound-queue", "snowbridge-pezpallet-outbound-queue"),
("snowbridge-pezpallet-outbound-queue-v2", "snowbridge-pezpallet-outbound-queue-v2"),
("snowbridge-pezpallet-system", "snowbridge-pezpallet-system"),
("snowbridge-pezpallet-system-frontend", "snowbridge-pezpallet-system-frontend"),
("snowbridge-pezpallet-system-v2", "snowbridge-pezpallet-system-v2"),
("snowbridge-runtime-common", "pezsnowbridge-runtime-common"),
("snowbridge-runtime-test-common", "pezsnowbridge-runtime-test-common"),
("snowbridge-system-runtime-api", "pezsnowbridge-system-runtime-api"),
("snowbridge-system-v2-runtime-api", "pezsnowbridge-system-v2-runtime-api"),
("snowbridge-test-utils", "pezsnowbridge-test-utils"),
("snowbridge-verification-primitives", "pezsnowbridge-verification-primitives"),
("xcm-docs", "xcm-pez-docs"),
("xcm-emulator", "xcm-pez-emulator"),
("xcm-executor-integration-tests", "xcm-pez-executor-integration-tests"),
("xcm-procedural", "xcm-pez-procedural"),
("xcm-runtime-apis", "xcm-runtime-pezapis"),
("xcm-simulator", "xcm-pez-simulator"),
("xcm-simulator-example", "xcm-pez-simulator-example"),
("xcm-simulator-fuzzer", "xcm-pez-simulator-fuzzer"),
]
# Hedef dosya uzantıları
TARGET_EXTENSIONS = ('.rs', '.toml', '.md', '.txt', '.yml', '.yaml', '.json', '.py')
# HARİÇ TUTULACAK KLASÖRLER (KESİN LİSTE)
EXCLUDE_DIRS = {'crate_placeholders', '.git', 'target', 'node_modules', '__pycache__'}
def is_path_excluded(path):
"""Verilen yolun yasaklı bir klasörün içinde olup olmadığını kontrol eder."""
parts = path.split(os.sep)
# Eğer path'in herhangi bir parçası EXCLUDE_DIRS içindeyse True döner
return any(excluded in parts for excluded in EXCLUDE_DIRS)
def replace_in_file(filepath):
# Kendi kendimizi değiştirmeyelim
if os.path.basename(filepath) == os.path.basename(__file__):
return
try:
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
original_content = content
for old_name, new_name in REBRAND_MAP:
# 1. Normal (tireli)
content = content.replace(old_name, new_name)
# 2. Snake case (alt çizgili)
old_snake = old_name.replace('-', '_')
new_snake = new_name.replace('-', '_')
content = content.replace(old_snake, new_snake)
if content != original_content:
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
print(f" [GÜNCELLENDİ] Dosya içeriği: {filepath}")
except Exception as e:
print(f" [HATA] Dosya okunamadı: {filepath} -> {e}")
def rename_directories_and_files(root_dir):
# topdown=True kullanarak yukarıdan aşağıya iniyoruz, böylece dirs listesini modifiye edebiliriz
for dirpath, dirnames, filenames in os.walk(root_dir, topdown=True):
# GÜVENLİK: Yasaklı klasörleri yerinde (in-place) listeden silerek os.walk'un oraya girmesini engelle
# Bu en güvenli yöntemdir.
dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS]
# Eğer şu anki dizin zaten yasaklı bir yolun altındaysa (üstteki koruma kaçırdıysa) atla
if is_path_excluded(dirpath):
continue
# 1. Dosya isimlerini değiştir
for filename in filenames:
if filename == os.path.basename(__file__):
continue
for old_name, new_name in REBRAND_MAP:
if old_name in filename:
old_file_path = os.path.join(dirpath, filename)
new_filename = filename.replace(old_name, new_name)
new_file_path = os.path.join(dirpath, new_filename)
if os.path.exists(old_file_path):
try:
os.rename(old_file_path, new_file_path)
print(f" [RENAME] Dosya: {filename} -> {new_filename}")
except OSError as e:
print(f" [HATA] Dosya adlandırılamadı {filename}: {e}")
# 2. Klasör isimlerini değiştir
# Not: dirnames listesi üzerinde iterasyon yapıyoruz ama rename işlemi riskli olabilir
# O yüzden sadece şu anki seviyedeki klasörleri kontrol ediyoruz
# Ancak os.walk çalışırken klasör adı değişirse alt dizin taraması sapıtabilir.
# Bu yüzden klasör yeniden adlandırmayı en sona, ayrı bir "bottom-up" geçişe bırakmak daha iyidir
# ama basitlik adına burada dikkatli yapıyoruz.
# İkinci Geçiş: Sadece Klasör İsimleri (Bottom-Up)
# Klasör isimlerini değiştirirken path bozulmasın diye en alttan başlıyoruz
for dirpath, dirnames, filenames in os.walk(root_dir, topdown=False):
if is_path_excluded(dirpath):
continue
for dirname in dirnames:
if dirname in EXCLUDE_DIRS:
continue
for old_name, new_name in REBRAND_MAP:
if old_name == dirname:
old_dir_path = os.path.join(dirpath, dirname)
new_dir_path = os.path.join(dirpath, new_name)
if os.path.exists(old_dir_path):
try:
os.rename(old_dir_path, new_dir_path)
print(f" [RENAME] Klasör: {dirname} -> {new_name}")
except OSError as e:
print(f" [HATA] Klasör adlandırılamadı {dirname}: {e}")
def process_content_updates(root_dir):
for dirpath, dirnames, filenames in os.walk(root_dir, topdown=True):
# Yasaklı klasörlere girme
dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS]
if is_path_excluded(dirpath):
continue
for filename in filenames:
if filename.endswith(TARGET_EXTENSIONS) or filename == 'Cargo.lock':
filepath = os.path.join(dirpath, filename)
replace_in_file(filepath)
def main():
root_dir = os.getcwd()
print("==================================================")
print(f"⚠️ DİKKAT: Çalışma dizini: {root_dir}")
print(f"⚠️ HARİÇ TUTULANLAR: {EXCLUDE_DIRS}")
print("==================================================")
# Otomatik onay veya soru
# confirm = input("Emin misin? (evet/hayir): ")
# if confirm.lower() != "evet": return
print("İşlem başlatılıyor...")
print("\n--- Adım 1: Dosya İçeriklerinin Güncellenmesi ---")
process_content_updates(root_dir)
print("\n--- Adım 2: Klasör ve Dosya İsimlerinin Değiştirilmesi ---")
rename_directories_and_files(root_dir)
print("\n✅ Rebranding işlemi tamamlandı.")
if __name__ == "__main__":
main()
-320
View File
@@ -1,320 +0,0 @@
#!/usr/bin/env python3
"""
crates.io İsim Rezervasyon Script'i (Gelişmiş Versiyon)
Özellikler:
- Kaldığı yerden devam etme (--start-from)
- Ayarlanabilir bekleme süresi (--interval)
- Workspace izolasyonu (üst dizindeki Cargo.toml ile çakışmaz)
- "Already exists" durumunu akıllıca yönetir (bekleme yapmaz)
"""
import subprocess
import os
import sys
import json
import time
from pathlib import Path
import argparse
WORKSPACE_ROOT = Path(__file__).parent.resolve()
PLACEHOLDER_DIR = WORKSPACE_ROOT / "crate_placeholders"
# Yeni isim listesi
NEW_CRATE_NAMES = [
"asset-hub-pezkuwichain-emulated-chain",
"asset-hub-pezkuwichain-integration-tests",
"asset-hub-pezkuwichain-runtime",
"asset-hub-zagros-emulated-chain",
"asset-hub-zagros-integration-tests",
"asset-hub-zagros-runtime",
"asset-test-pezutils",
"pez-binary-merkle-tree",
"pez-chain-spec-guide-runtime",
"collectives-zagros-emulated-chain",
"collectives-zagros-integration-tests",
"collectives-zagros-runtime",
"coretime-pezkuwichain-emulated-chain",
"coretime-pezkuwichain-integration-tests",
"coretime-pezkuwichain-runtime",
"coretime-zagros-emulated-chain",
"coretime-zagros-integration-tests",
"coretime-zagros-runtime",
"emulated-integration-tests-common",
"pez-equivocation-detector",
"pez-erasure-coding-fuzzer",
"pez-ethereum-standards",
"pez-finality-relay",
"pez-fork-tree",
"pezframe-election-solution-type-fuzzer",
"pezframe-omni-bencher",
"pezframe-remote-externalities",
"pezframe-storage-access-test-runtime",
"pez-generate-bags",
"glutton-zagros-runtime",
"governance-zagros-integration-tests",
"pez-kitchensink-runtime",
"pez-messages-relay",
"pez-minimal-template-node",
"pez-minimal-template-runtime",
"pez-node-bench",
"pez-node-primitives",
"pez-node-rpc",
"pez-node-runtime-pez-generate-bags",
"pez-node-template-release",
"pez-node-testing",
"pez-penpal-emulated-chain",
"pez-penpal-runtime",
"people-pezkuwichain-emulated-chain",
"people-pezkuwichain-integration-tests",
"people-pezkuwichain-runtime",
"people-zagros-emulated-chain",
"people-zagros-integration-tests",
"people-zagros-runtime",
"pezkuwi",
"pezkuwichain-emulated-chain",
"pezkuwichain-runtime",
"pezkuwichain-runtime-constants",
"pezkuwichain-system-emulated-network",
"pezkuwichain-teyrchain-runtime",
"pezkuwichain-zagros-system-emulated-network",
"relay-bizinikiwi-client",
"relay-pezutils",
"pez-remote-ext-tests-bags-list",
"pez-revive-dev-node",
"pez-revive-dev-runtime",
"pez-slot-range-helper",
"pez-solochain-template-node",
"pez-solochain-template-runtime",
"pez-pez_subkey",
"pez-template-zombienet-tests",
"peztest-runtime-constants",
"test-teyrchain-adder",
"test-teyrchain-adder-collator",
"test-teyrchain-halt",
"test-teyrchain-undying",
"test-teyrchain-undying-collator",
"testnet-teyrchains-constants",
"teyrchain-template",
"teyrchain-template-node",
"teyrchain-template-runtime",
"teyrchains-common",
"teyrchains-relay",
"teyrchains-runtimes-test-utils",
"pez-tracing-gum",
"pez-pez-tracing-gum-proc-macro",
"yet-another-teyrchain-runtime",
"zagros-emulated-chain",
"zagros-runtime",
"zagros-runtime-constants",
"zagros-system-emulated-network",
"pez-zombienet-backchannel",
"pezassets-common",
"bp-asset-hub-pezkuwichain",
"bp-asset-hub-zagros",
"bp-pezbeefy",
"bp-bridge-hub-pezcumulus",
"bp-bridge-hub-pezkuwichain",
"bp-bridge-hub-zagros",
"bp-header-pez-chain",
"bp-pez-messages",
"bp-pezkuwi-bulletin",
"bp-pezkuwi-core",
"bp-pezkuwichain",
"bp-pez-relayers",
"pezbp-runtime",
"bp-test-pezutils",
"bp-teyrchains",
"bp-xcm-pezbridge-hub",
"bp-xcm-pezbridge-hub-router",
"bp-zagros",
"pezbridge-hub-common",
"pezbridge-hub-pezkuwichain-emulated-chain",
"pezbridge-hub-pezkuwichain-integration-tests",
"pezbridge-hub-pezkuwichain-runtime",
"pezbridge-hub-test-utils",
"pezbridge-hub-zagros-emulated-chain",
"pezbridge-hub-zagros-integration-tests",
"pezbridge-hub-zagros-runtime",
"pezbridge-runtime-common",
"pezmmr-gadget",
"pezmmr-rpc",
"pezsnowbridge-beacon-primitives",
"pezsnowbridge-core",
"pezsnowbridge-ethereum",
"pezsnowbridge-inbound-queue-primitives",
"pezsnowbridge-merkle-tree",
"pezsnowbridge-outbound-queue-primitives",
"pezsnowbridge-outbound-queue-runtime-api",
"pezsnowbridge-outbound-queue-v2-runtime-api",
"pezsnowbridge-pezpallet-ethereum-client",
"pezsnowbridge-pezpallet-ethereum-client-fixtures",
"pezsnowbridge-pezpallet-inbound-queue",
"pezsnowbridge-pezpallet-inbound-queue-fixtures",
"pezsnowbridge-pezpallet-inbound-queue-v2",
"pezsnowbridge-pezpallet-inbound-queue-v2-fixtures",
"pezsnowbridge-pezpallet-outbound-queue",
"pezsnowbridge-pezpallet-outbound-queue-v2",
"pezsnowbridge-pezpallet-system",
"pezsnowbridge-pezpallet-system-frontend",
"pezsnowbridge-pezpallet-system-v2",
"pezsnowpezbridge-runtime-common",
"pezsnowbridge-runtime-test-common",
"pezsnowbridge-system-runtime-api",
"pezsnowbridge-system-v2-runtime-api",
"pezsnowbridge-test-utils",
"pezsnowbridge-verification-primitives",
"xcm-pez-docs",
"xcm-pez-emulator",
"xcm-pez-executor-integration-tests",
"xcm-pez-procedural",
"xcm-runtime-pezapis",
"xcm-pez-simulator",
"xcm-pez-simulator-example",
"xcm-pez-simulator-fuzzer",
]
def check_crate_available(name: str) -> bool:
"""crates.io'da isim müsait mi kontrol et"""
result = subprocess.run(
["cargo", "search", name, "--limit", "1"],
capture_output=True, text=True
)
return f'{name} = "' not in result.stdout
def create_placeholder(name: str) -> Path:
"""Placeholder crate oluştur"""
crate_dir = PLACEHOLDER_DIR / name
crate_dir.mkdir(parents=True, exist_ok=True)
# [workspace] ekleyerek parent workspace ile ilişkisini kesiyoruz
cargo_toml = f'''[package]
name = "{name}"
version = "0.1.0"
edition = "2021"
description = "PezkuwiChain SDK component - placeholder for name reservation"
license = "Apache-2.0"
repository = "https://github.com/pezkuwichain/pezkuwi-sdk"
homepage = "https://pezkuwichain.io"
documentation = "https://docs.pezkuwichain.io/sdk/"
authors = ["Kurdistan Tech Institute <info@pezkuwichain.io>"]
keywords = ["pezkuwichain", "blockchain", "sdk"]
categories = ["cryptography::cryptocurrencies"]
[workspace]
[dependencies]
'''
(crate_dir / "Cargo.toml").write_text(cargo_toml)
src_dir = crate_dir / "src"
src_dir.mkdir(exist_ok=True)
lib_rs = f'''//! {name}
//! This crate is part of the PezkuwiChain SDK.
//! Full implementation coming soon.
#![doc = include_str!("../README.md")]
'''
(src_dir / "lib.rs").write_text(lib_rs)
readme = f'''# {name}
Part of [PezkuwiChain SDK](https://github.com/pezkuwichain/pezkuwi-sdk).
## About
This crate is a component of the PezkuwiChain blockchain SDK.
'''
(crate_dir / "README.md").write_text(readme)
return crate_dir
def publish_placeholder(crate_dir: Path, dry_run: bool = True):
"""Placeholder'ı crates.io'ya publish et.
Dönüş: (başarılı_mı, bekleme_gerekli_mi)
"""
args = ["cargo", "publish"]
if dry_run:
args.append("--dry-run")
args.extend(["--manifest-path", str(crate_dir / "Cargo.toml")])
result = subprocess.run(args, capture_output=True, text=True, cwd=crate_dir)
if result.returncode == 0:
return True, True # Başarılı, bekleme yap
# "already exists" hatasını kontrol et
if "already exists" in result.stderr:
return True, False # Zaten var, bekleme yapma
print(f"\n[HATA] {crate_dir.name} publish edilemedi:\n{result.stderr}")
return False, False
def main():
parser = argparse.ArgumentParser(description="crates.io isim rezervasyonu")
parser.add_argument("--list", action="store_true", help="İsimleri listele")
parser.add_argument("--check", action="store_true", help="crates.io'da müsaitlik kontrol et")
parser.add_argument("--create", action="store_true", help="Placeholder crate'leri oluştur")
parser.add_argument("--publish", action="store_true", help="crates.io'ya publish et")
parser.add_argument("--dry-run", action="store_true", help="Publish dry-run")
parser.add_argument("--start-from", type=str, help="İşleme bu crate isminden başla (öncekileri atlar)")
parser.add_argument("--interval", type=int, default=360, help="Publish arası bekleme süresi (saniye). Varsayılan: 360")
args = parser.parse_args()
if args.list:
for name in sorted(NEW_CRATE_NAMES):
print(f" {name}")
return
# Create/Publish işlemleri
if args.create or args.publish:
# Placeholder klasörünü oluştur
PLACEHOLDER_DIR.mkdir(exist_ok=True)
start_processing = False
if not args.start_from:
start_processing = True
print(f"Toplam Crate Sayısı: {len(NEW_CRATE_NAMES)}")
print(f"Bekleme Süresi: {args.interval} saniye")
if args.start_from:
print(f"Başlangıç: {args.start_from} (Öncekiler atlanacak)")
success = 0
failed = 0
skipped = 0
for i, name in enumerate(NEW_CRATE_NAMES, 1):
# Resume mantığı
if not start_processing:
if name == args.start_from:
start_processing = True
else:
skipped += 1
continue
print(f"[{i}/{len(NEW_CRATE_NAMES)}] {name}...", end=" ", flush=True)
# 1. Create
crate_dir = create_placeholder(name)
# 2. Publish (Eğer istenmişse)
if args.publish:
success_status, needs_wait = publish_placeholder(crate_dir, args.dry_run)
if success_status:
if needs_wait:
print("✓ PUBLISHED")
success += 1
if not args.dry_run:
print(f" -> Bekleniyor {args.interval}sn...")
time.sleep(args.interval)
else:
print("✓ ZATEN VAR (Atlandı)")
success += 1
else:
print("✗ FAILED")
failed += 1
else:
print("✓ CREATED")
print(f"\nSonuç: {success} başarılı, {failed} başarısız, {skipped} atlandı.")
if __name__ == "__main__":
main()
-192
View File
@@ -1,192 +0,0 @@
#!/usr/bin/env python3
"""
Eski (rebrand edilmemiş) kelimeleri tarayan script.
Her crate için çalıştırılır ve kalan eski kelimeleri tespit eder.
Kullanım:
python3 scan_old_words.py <crate_path>
python3 scan_old_words.py /home/mamostehp/kurdistan-sdk/bizinikiwi/primitives/core
"""
import os
import sys
import re
from pathlib import Path
# Rebrand kuralları: (eski_pattern, yeni_kelime, açıklama)
# Sıralama önemli - daha spesifik olanlar önce
REBRAND_RULES = [
# Terminoloji
(r'\bparachain\b', 'teyrchain', 'parachain → teyrchain'),
(r'\bParachain\b', 'Teyrchain', 'Parachain → Teyrchain'),
(r'\bPARACHAIN\b', 'TEYRCHAIN', 'PARACHAIN → TEYRCHAIN'),
(r'\brococo\b', 'pezkuwichain', 'rococo → pezkuwichain'),
(r'\bRococo\b', 'Pezkuwichain', 'Rococo → Pezkuwichain'),
(r'\bROCOCO\b', 'PEZKUWICHAIN', 'ROCOCO → PEZKUWICHAIN'),
(r'\bwestend\b', 'zagros', 'westend → zagros'),
(r'\bWestend\b', 'Zagros', 'Westend → Zagros'),
(r'\bWESTEND\b', 'ZAGROS', 'WESTEND → ZAGROS'),
(r'\bkusama\b', 'zagros', 'kusama → zagros'),
(r'\bKusama\b', 'Zagros', 'Kusama → Zagros'),
(r'\bKUSAMA\b', 'ZAGROS', 'KUSAMA → ZAGROS'),
# Crate prefix'leri (Cargo.toml name ve use statement'larda)
# Dikkat: Bunlar sadece crate isimlerinde geçerli, rastgele "sp_" değil
(r'\bsp-core\b', 'pezsp-core', 'sp-core → pezsp-core'),
(r'\bsp-runtime\b', 'pezsp-runtime', 'sp-runtime → pezsp-runtime'),
(r'\bsp-io\b', 'pezsp-io', 'sp-io → pezsp-io'),
(r'\bsp-std\b', 'pezsp-std', 'sp-std → pezsp-std'),
(r'\bsp-api\b', 'pezsp-api', 'sp-api → pezsp-api'),
(r'\bsc-client\b', 'pezsc-client', 'sc-client → pezsc-client'),
(r'\bsc-service\b', 'pezsc-service', 'sc-service → pezsc-service'),
(r'\bframe-support\b', 'pezframe-support', 'frame-support → pezframe-support'),
(r'\bframe-system\b', 'pezframe-system', 'frame-system → pezframe-system'),
(r'\bpallet-balances\b', 'pezpallet-balances', 'pallet-balances → pezpallet-balances'),
(r'\bcumulus-client\b', 'pezcumulus-client', 'cumulus-client → pezcumulus-client'),
(r'\bcumulus-primitives\b', 'pezcumulus-primitives', 'cumulus-primitives → pezcumulus-primitives'),
# Snowbridge (pezsnowbridge-pezpallet önce, sonra genel snowbridge)
(r'\bsnowbridge-pezpallet-', 'pezsnowbridge-pezpallet-', 'snowbridge-pezpallet- → pezsnowbridge-pezpallet-'),
(r'\bsnowbridge-pallet-', 'pezsnowbridge-pezpallet-', 'snowbridge-pallet- → pezsnowbridge-pezpallet-'),
(r'\bsnowbridge-', 'pezsnowbridge-', 'snowbridge- → pezsnowbridge-'),
(r'\bsnowbridge_pallet_', 'pezsnowbridge_pezpallet_', 'snowbridge_pallet_ → pezsnowbridge_pezpallet_'),
(r'\bsnowbridge_pezpallet_', 'pezsnowbridge_pezpallet_', 'snowbridge_pezpallet_ → pezsnowbridge_pezpallet_'),
# Bridge
(r'\bbridge-hub-rococo\b', 'pezbridge-hub-pezkuwichain', 'bridge-hub-rococo → pezbridge-hub-pezkuwichain'),
(r'\bbridge-hub-westend\b', 'pezbridge-hub-zagros', 'bridge-hub-westend → pezbridge-hub-zagros'),
(r'\bbridge-runtime-common\b', 'pezbridge-runtime-common', 'bridge-runtime-common → pezbridge-runtime-common'),
# MMR
(r'\bmmr-gadget\b', 'pezmmr-gadget', 'mmr-gadget → pezmmr-gadget'),
(r'\bmmr-rpc\b', 'pezmmr-rpc', 'mmr-rpc → pezmmr-rpc'),
# Substrate (dikkatli - sadece proje referanslarında)
(r'\bsubstrate-wasm-builder\b', 'bizinikiwi-wasm-builder', 'substrate-wasm-builder → bizinikiwi-wasm-builder'),
(r'\bsubstrate-build-script-utils\b', 'bizinikiwi-build-script-utils', 'substrate-build-script-utils → bizinikiwi-build-script-utils'),
# Polkadot referansları
(r'\bpolkadot-sdk\b', 'pezkuwi-sdk', 'polkadot-sdk → pezkuwi-sdk'),
(r'\bpolkadot-runtime\b', 'pezkuwichain-runtime', 'polkadot-runtime → pezkuwichain-runtime'),
(r'\bpolkadot-primitives\b', 'pezkuwi-primitives', 'polkadot-primitives → pezkuwi-primitives'),
# Rust module isimleri (underscore versiyonları)
(r'\bsp_core\b', 'pezsp_core', 'sp_core → pezsp_core'),
(r'\bsp_runtime\b', 'pezsp_runtime', 'sp_runtime → pezsp_runtime'),
(r'\bsp_io\b', 'pezsp_io', 'sp_io → pezsp_io'),
(r'\bsc_client\b', 'pezsc_client', 'sc_client → pezsc_client'),
(r'\bframe_support\b', 'pezframe_support', 'frame_support → pezframe_support'),
(r'\bframe_system\b', 'pezframe_system', 'frame_system → pezframe_system'),
(r'\bpallet_balances\b', 'pezpallet_balances', 'pallet_balances → pezpallet_balances'),
(r'\bcumulus_client\b', 'pezcumulus_client', 'cumulus_client → pezcumulus_client'),
(r'\bcumulus_primitives\b', 'pezcumulus_primitives', 'cumulus_primitives → pezcumulus_primitives'),
]
# Taranacak dosya uzantıları
SCAN_EXTENSIONS = {'.rs', '.toml', '.md', '.json', '.yaml', '.yml'}
# Atlanacak dizinler
SKIP_DIRS = {'target', '.git', 'node_modules', 'crate_placeholders'}
def scan_file(file_path: Path) -> list:
"""Tek bir dosyayı tarar ve bulunan eski kelimeleri döndürür."""
findings = []
try:
content = file_path.read_text(encoding='utf-8', errors='ignore')
except Exception as e:
return [(str(file_path), 0, f"OKUMA HATASI: {e}", "", "")]
lines = content.split('\n')
for line_num, line in enumerate(lines, 1):
for pattern, replacement, description in REBRAND_RULES:
matches = re.finditer(pattern, line)
for match in matches:
findings.append({
'file': str(file_path),
'line': line_num,
'column': match.start() + 1,
'found': match.group(),
'replacement': replacement,
'description': description,
'context': line.strip()[:100]
})
return findings
def scan_crate(crate_path: str) -> list:
"""Bir crate dizinini tarar."""
crate_dir = Path(crate_path)
if not crate_dir.exists():
print(f"HATA: Dizin bulunamadı: {crate_path}")
return []
all_findings = []
for root, dirs, files in os.walk(crate_dir):
# Skip directories
dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
for file in files:
file_path = Path(root) / file
if file_path.suffix not in SCAN_EXTENSIONS:
continue
findings = scan_file(file_path)
all_findings.extend(findings)
return all_findings
def print_report(findings: list, crate_path: str):
"""Bulunan eski kelimelerin raporunu yazdırır."""
print(f"\n{'='*60}")
print(f"TARAMA RAPORU: {crate_path}")
print(f"{'='*60}\n")
if not findings:
print("✅ ESKİ KELİME BULUNAMADI - Crate temiz!")
return
print(f"{len(findings)} adet eski kelime bulundu:\n")
# Dosyaya göre grupla
by_file = {}
for f in findings:
if f['file'] not in by_file:
by_file[f['file']] = []
by_file[f['file']].append(f)
for file_path, file_findings in sorted(by_file.items()):
rel_path = file_path.replace(crate_path, '.')
print(f"\n📄 {rel_path}")
print(f" {'-'*50}")
for finding in file_findings:
print(f" Satır {finding['line']}: {finding['found']}{finding['replacement']}")
print(f" Bağlam: {finding['context']}")
print()
def main():
if len(sys.argv) < 2:
print("Kullanım: python3 scan_old_words.py <crate_path>")
print("Örnek: python3 scan_old_words.py ./bizinikiwi/primitives/core")
sys.exit(1)
crate_path = sys.argv[1]
findings = scan_crate(crate_path)
print_report(findings, crate_path)
# Çıkış kodu: bulgu varsa 1, yoksa 0
sys.exit(1 if findings else 0)
if __name__ == "__main__":
main()
-322
View File
@@ -1,322 +0,0 @@
[package]
name = "pezkuwi-sdk-docs"
description = "The one stop shop for developers of the pezkuwi-sdk"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
homepage = "https://docs.pezkuwichain.io/sdk/"
repository.workspace = true
authors.workspace = true
edition.workspace = true
# This crate is not publish-able to crates.io for now because of docify.
publish = false
version = "0.0.2"
documentation.workspace = true
[lints]
workspace = true
[dependencies]
# Needed for all FRAME-based code
codec = { workspace = true }
pezframe = { features = [
"experimental",
"runtime",
], workspace = true, default-features = true }
pezpallet-contracts = { workspace = true }
pezpallet-default-config-example = { workspace = true, default-features = true }
pezpallet-example-offchain-worker = { workspace = true, default-features = true }
pezpallet-examples = { workspace = true }
scale-info = { workspace = true }
# How we build docs in rust-docs
docify = { workspace = true }
serde_json = { workspace = true }
simple-mermaid = { workspace = true }
# Pezkuwi SDK deps, typically all should only be in scope such that we can link to their doc item.
chain-spec-builder = { workspace = true, default-features = true }
log = { workspace = true, default-features = true }
node-cli = { workspace = true }
pez-kitchensink-runtime = { workspace = true }
pez-subkey = { workspace = true, default-features = true }
pezframe-benchmarking = { workspace = true }
pezframe-executive = { workspace = true }
pezframe-metadata-hash-extension = { workspace = true, default-features = true }
pezframe-support = { workspace = true }
pezframe-system = { workspace = true }
pezkuwi-sdk = { features = [
"runtime-full",
], workspace = true, default-features = true }
pezpallet-example-authorization-tx-extension = { workspace = true, default-features = true }
pezpallet-example-single-block-migrations = { workspace = true, default-features = true }
# Bizinikiwi Client
pezsc-chain-spec = { workspace = true, default-features = true }
pezsc-cli = { workspace = true, default-features = true }
pezsc-client-db = { workspace = true, default-features = true }
pezsc-consensus-aura = { workspace = true, default-features = true }
pezsc-consensus-babe = { workspace = true, default-features = true }
pezsc-consensus-beefy = { workspace = true, default-features = true }
pezsc-consensus-grandpa = { workspace = true, default-features = true }
pezsc-consensus-manual-seal = { workspace = true, default-features = true }
pezsc-consensus-pow = { workspace = true, default-features = true }
pezsc-executor = { workspace = true, default-features = true }
pezsc-network = { workspace = true, default-features = true }
pezsc-rpc = { workspace = true, default-features = true }
pezsc-rpc-api = { workspace = true, default-features = true }
pezsc-service = { workspace = true, default-features = true }
bizinikiwi-wasm-builder = { workspace = true, default-features = true }
# Pezcumulus
pezcumulus-client-service = { workspace = true, default-features = true }
pezcumulus-pezpallet-aura-ext = { workspace = true, default-features = true }
pezcumulus-pezpallet-teyrchain-system = { workspace = true, default-features = true }
pezcumulus-pezpallet-weight-reclaim = { workspace = true, default-features = true }
pezcumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true }
teyrchain-info = { workspace = true, default-features = true }
# Omni Node
pezkuwi-omni-node-lib = { workspace = true, default-features = true }
# Pallets and FRAME internals
pezpallet-asset-conversion-tx-payment = { workspace = true, default-features = true }
pezpallet-asset-tx-payment = { workspace = true, default-features = true }
pezpallet-assets = { workspace = true, default-features = true }
pezpallet-aura = { workspace = true, default-features = true }
pezpallet-babe = { workspace = true, default-features = true }
pezpallet-balances = { workspace = true, default-features = true }
pezpallet-collective = { workspace = true, default-features = true }
pezpallet-democracy = { workspace = true, default-features = true }
pezpallet-grandpa = { workspace = true, default-features = true }
pezpallet-nfts = { workspace = true, default-features = true }
pezpallet-preimage = { workspace = true, default-features = true }
pezpallet-scheduler = { workspace = true, default-features = true }
pezpallet-skip-feeless-payment = { workspace = true, default-features = true }
pezpallet-timestamp = { workspace = true, default-features = true }
pezpallet-transaction-payment = { workspace = true, default-features = true }
pezpallet-uniques = { workspace = true, default-features = true }
# Primitives
pezsp-api = { workspace = true, default-features = true }
pezsp-arithmetic = { workspace = true, default-features = true }
pezsp-core = { workspace = true, default-features = true }
pezsp-genesis-builder = { workspace = true, default-features = true }
pezsp-io = { workspace = true, default-features = true }
pezsp-keyring = { workspace = true, default-features = true }
pezsp-offchain = { workspace = true, default-features = true }
pezsp-runtime = { workspace = true, default-features = true }
pezsp-runtime-interface = { workspace = true, default-features = true }
pezsp-std = { workspace = true, default-features = true }
pezsp-storage = { workspace = true, default-features = true }
pezsp-tracing = { workspace = true, default-features = true }
pezsp-version = { workspace = true, default-features = true }
pezsp-weights = { workspace = true, default-features = true }
# XCM
pezpallet-xcm = { workspace = true }
xcm = { workspace = true, default-features = true }
xcm-builder = { workspace = true }
xcm-executor = { workspace = true }
xcm-pez-docs = { workspace = true }
xcm-pez-simulator = { workspace = true }
# Runtime guides
pez-chain-spec-guide-runtime = { workspace = true, default-features = true }
# Templates
pez-minimal-template-runtime = { workspace = true, default-features = true }
pez-solochain-template-runtime = { workspace = true, default-features = true }
# local packages
first-runtime = { workspace = true, default-features = true }
[dev-dependencies]
assert_cmd = { workspace = true }
cmd_lib = { workspace = true }
rand = { workspace = true, default-features = true }
tokio = { workspace = true }
[features]
runtime-benchmarks = [
"bizinikiwi-wasm-builder/runtime-benchmarks",
"chain-spec-builder/runtime-benchmarks",
"first-runtime/runtime-benchmarks",
"node-cli/runtime-benchmarks",
"pez-chain-spec-guide-runtime/runtime-benchmarks",
"pez-kitchensink-runtime/runtime-benchmarks",
"pez-minimal-template-runtime/runtime-benchmarks",
"pez-solochain-template-runtime/runtime-benchmarks",
"pez-subkey/runtime-benchmarks",
"pezcumulus-client-service/runtime-benchmarks",
"pezcumulus-pezpallet-aura-ext/runtime-benchmarks",
"pezcumulus-pezpallet-teyrchain-system/runtime-benchmarks",
"pezcumulus-pezpallet-weight-reclaim/runtime-benchmarks",
"pezcumulus-primitives-proof-size-hostfunction/runtime-benchmarks",
"pezframe-benchmarking/runtime-benchmarks",
"pezframe-executive/runtime-benchmarks",
"pezframe-metadata-hash-extension/runtime-benchmarks",
"pezframe-support/runtime-benchmarks",
"pezframe-system/runtime-benchmarks",
"pezframe/runtime-benchmarks",
"pezkuwi-omni-node-lib/runtime-benchmarks",
"pezkuwi-sdk/runtime-benchmarks",
"pezpallet-asset-conversion-tx-payment/runtime-benchmarks",
"pezpallet-asset-tx-payment/runtime-benchmarks",
"pezpallet-assets/runtime-benchmarks",
"pezpallet-aura/runtime-benchmarks",
"pezpallet-babe/runtime-benchmarks",
"pezpallet-balances/runtime-benchmarks",
"pezpallet-collective/runtime-benchmarks",
"pezpallet-contracts/runtime-benchmarks",
"pezpallet-default-config-example/runtime-benchmarks",
"pezpallet-democracy/runtime-benchmarks",
"pezpallet-example-authorization-tx-extension/runtime-benchmarks",
"pezpallet-example-offchain-worker/runtime-benchmarks",
"pezpallet-example-single-block-migrations/runtime-benchmarks",
"pezpallet-examples/runtime-benchmarks",
"pezpallet-grandpa/runtime-benchmarks",
"pezpallet-nfts/runtime-benchmarks",
"pezpallet-preimage/runtime-benchmarks",
"pezpallet-scheduler/runtime-benchmarks",
"pezpallet-skip-feeless-payment/runtime-benchmarks",
"pezpallet-timestamp/runtime-benchmarks",
"pezpallet-transaction-payment/runtime-benchmarks",
"pezpallet-uniques/runtime-benchmarks",
"pezpallet-xcm/runtime-benchmarks",
"pezsc-chain-spec/runtime-benchmarks",
"pezsc-cli/runtime-benchmarks",
"pezsc-client-db/runtime-benchmarks",
"pezsc-consensus-aura/runtime-benchmarks",
"pezsc-consensus-babe/runtime-benchmarks",
"pezsc-consensus-beefy/runtime-benchmarks",
"pezsc-consensus-grandpa/runtime-benchmarks",
"pezsc-consensus-manual-seal/runtime-benchmarks",
"pezsc-consensus-pow/runtime-benchmarks",
"pezsc-executor/runtime-benchmarks",
"pezsc-network/runtime-benchmarks",
"pezsc-rpc-api/runtime-benchmarks",
"pezsc-rpc/runtime-benchmarks",
"pezsc-service/runtime-benchmarks",
"pezsp-api/runtime-benchmarks",
"pezsp-genesis-builder/runtime-benchmarks",
"pezsp-io/runtime-benchmarks",
"pezsp-keyring/runtime-benchmarks",
"pezsp-offchain/runtime-benchmarks",
"pezsp-runtime-interface/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"pezsp-version/runtime-benchmarks",
"teyrchain-info/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
"xcm-pez-docs/runtime-benchmarks",
"xcm-pez-simulator/runtime-benchmarks",
"xcm/runtime-benchmarks",
]
std = [
"bizinikiwi-wasm-builder/std",
"chain-spec-builder/std",
"codec/std",
"log/std",
"node-cli/std",
"pez-subkey/std",
"pezcumulus-client-service/std",
"pezframe-benchmarking/std",
"pezframe-executive/std",
"pezframe-support/std",
"pezframe-system/std",
"pezkuwi-omni-node-lib/std",
"pezpallet-contracts/std",
"pezpallet-xcm/std",
"pezsc-chain-spec/std",
"pezsc-cli/std",
"pezsc-client-db/std",
"pezsc-consensus-aura/std",
"pezsc-consensus-babe/std",
"pezsc-consensus-beefy/std",
"pezsc-consensus-grandpa/std",
"pezsc-consensus-manual-seal/std",
"pezsc-consensus-pow/std",
"pezsc-network/std",
"pezsc-rpc-api/std",
"pezsc-rpc/std",
"pezsc-service/std",
"scale-info/std",
"serde_json/std",
"xcm-builder/std",
"xcm-executor/std",
"xcm-pez-docs/std",
"xcm-pez-simulator/std",
]
try-runtime = [
"first-runtime/try-runtime",
"node-cli/try-runtime",
"pez-chain-spec-guide-runtime/try-runtime",
"pez-kitchensink-runtime/try-runtime",
"pez-minimal-template-runtime/try-runtime",
"pez-solochain-template-runtime/try-runtime",
"pezcumulus-client-service/try-runtime",
"pezcumulus-pezpallet-aura-ext/try-runtime",
"pezcumulus-pezpallet-teyrchain-system/try-runtime",
"pezcumulus-pezpallet-weight-reclaim/try-runtime",
"pezframe-benchmarking/try-runtime",
"pezframe-executive/try-runtime",
"pezframe-metadata-hash-extension/try-runtime",
"pezframe-support/try-runtime",
"pezframe-system/try-runtime",
"pezframe/try-runtime",
"pezkuwi-omni-node-lib/try-runtime",
"pezkuwi-sdk/try-runtime",
"pezpallet-asset-conversion-tx-payment/try-runtime",
"pezpallet-asset-tx-payment/try-runtime",
"pezpallet-assets/try-runtime",
"pezpallet-aura/try-runtime",
"pezpallet-babe/try-runtime",
"pezpallet-balances/try-runtime",
"pezpallet-collective/try-runtime",
"pezpallet-contracts/try-runtime",
"pezpallet-default-config-example/try-runtime",
"pezpallet-democracy/try-runtime",
"pezpallet-example-authorization-tx-extension/try-runtime",
"pezpallet-example-offchain-worker/try-runtime",
"pezpallet-example-single-block-migrations/try-runtime",
"pezpallet-examples/try-runtime",
"pezpallet-grandpa/try-runtime",
"pezpallet-nfts/try-runtime",
"pezpallet-preimage/try-runtime",
"pezpallet-scheduler/try-runtime",
"pezpallet-skip-feeless-payment/try-runtime",
"pezpallet-timestamp/try-runtime",
"pezpallet-transaction-payment/try-runtime",
"pezpallet-uniques/try-runtime",
"pezpallet-xcm/try-runtime",
"pezsc-chain-spec/try-runtime",
"pezsc-cli/try-runtime",
"pezsc-client-db/try-runtime",
"pezsc-consensus-aura/try-runtime",
"pezsc-consensus-babe/try-runtime",
"pezsc-consensus-beefy/try-runtime",
"pezsc-consensus-grandpa/try-runtime",
"pezsc-consensus-manual-seal/try-runtime",
"pezsc-consensus-pow/try-runtime",
"pezsc-executor/try-runtime",
"pezsc-network/try-runtime",
"pezsc-rpc-api/try-runtime",
"pezsc-rpc/try-runtime",
"pezsc-service/try-runtime",
"pezsp-api/try-runtime",
"pezsp-genesis-builder/try-runtime",
"pezsp-keyring/try-runtime",
"pezsp-offchain/try-runtime",
"pezsp-runtime/try-runtime",
"pezsp-version/try-runtime",
"teyrchain-info/try-runtime",
"xcm-builder/try-runtime",
"xcm-executor/try-runtime",
"xcm-pez-docs/try-runtime",
"xcm-pez-simulator/try-runtime",
"xcm/try-runtime",
]
serde = []
experimental = []
with-tracing = []
tuples-96 = []
@@ -1,2 +0,0 @@
<script> mermaid.init({ startOnLoad: true, theme: "dark" }, "pre.language-mermaid > code");</script>
-147
View File
@@ -1,147 +0,0 @@
<script>
function createToC() {
let sidebar = document.querySelector(".sidebar");
let headers = document.querySelectorAll("#main-content h2, #main-content h3, #main-content h4");
console.log(`detected polkadot_sdk_docs: headers: ${headers.length}`);
let toc = document.createElement("div");
toc.classList.add("sidebar-table-of-contents");
toc.appendChild(document.createElement("h2").appendChild(document.createTextNode("Table of Contents")).parentNode);
let modules = document.querySelectorAll("main .item-table a.mod");
// the first two headers are always junk
headers.forEach(header => {
let link = document.createElement("a");
link.href = "#" + header.id;
const headerTextContent = header.textContent.replace("§", "")
link.textContent = headerTextContent;
link.className = header.tagName.toLowerCase();
toc.appendChild(link);
if (header.id == "modules" && headerTextContent == "Modules") {
modules.forEach(module => {
let link = document.createElement("a");
link.href = module.href;
link.textContent = module.textContent;
link.className = "h3";
toc.appendChild(link);
});
}
});
// insert toc as the second child in sidebar
let sidebar_children = sidebar.children;
if (sidebar_children.length > 1) {
sidebar.insertBefore(toc, sidebar_children[1]);
} else {
sidebar.appendChild(toc);
}
}
function hideSidebarElements() {
// Create the 'Expand for More' button
var expandButton = document.createElement('button');
expandButton.innerText = 'Expand More Items';
expandButton.classList.add('expand-button');
// Insert the button at the top of the sidebar or before the '.sidebar-elems'
var sidebarElems = document.querySelector('.sidebar-elems');
sidebarElems.parentNode.insertBefore(expandButton, sidebarElems);
// Initially hide the '.sidebar-elems'
sidebarElems.style.display = 'none';
// Add click event listener to the button
expandButton.addEventListener('click', function () {
// Toggle the display of the '.sidebar-elems'
if (sidebarElems.style.display === 'none') {
sidebarElems.style.display = 'block';
expandButton.innerText = 'Collapse';
} else {
sidebarElems.style.display = 'none';
expandButton.innerText = 'Expand for More';
}
});
}
window.addEventListener("DOMContentLoaded", (event) => {
// if the crate is one that starts with `polkadot_sdk_docs`
let crate_name = document.querySelector("#main-content > div > h1 > a:nth-child(1)");
if (!crate_name.textContent.startsWith("polkadot_sdk_docs")) {
console.log("skipping -- not `polkadot_sdk_docs`");
return;
} else {
// insert class 'sdk-docs' to the body, so it enables the custom css rules.
document.body.classList.add("sdk-docs");
}
createToC();
hideSidebarElements();
console.log("updating page based on being `polkadot_sdk_docs` crate");
});
</script>
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<style>
body.sdk-docs {
nav.side-bar {
width: 300px;
}
.sidebar-table-of-contents {
margin-bottom: 1em;
padding: 0.5em;
}
.sidebar-table-of-contents a {
display: block;
margin: 0.2em 0;
}
.sidebar-table-of-contents .h2 {
font-weight: bold;
margin-left: 0;
}
.sidebar-table-of-contents .h3 {
margin-left: 1em;
}
.sidebar-table-of-contents .h4 {
margin-left: 2em;
}
.sidebar h2.location {
display: none;
}
.sidebar-elems {
display: none;
}
/* Center the 'Expand for More' button */
.expand-button {
display: inline-block;
/* Use inline-block for sizing */
margin: 10px auto;
/* Auto margins for horizontal centering */
padding: 5px 10px;
background-color: #007bff;
color: white;
text-align: center;
cursor: pointer;
border: none;
border-radius: 5px;
width: auto;
/* Centering the button within its parent container */
position: relative;
left: 50%;
transform: translateX(-50%);
}
}
</style>
-57
View File
@@ -1,57 +0,0 @@
:root {
--polkadot-pink: #E6007A;
--polkadot-green: #56F39A;
--polkadot-lime: #D3FF33;
--polkadot-cyan: #00B2FF;
--polkadot-purple: #552BBF;
}
/* Light theme */
html[data-theme="light"] {
--quote-background: #f9f9f9;
--quote-border: #ccc;
--quote-text: #333;
}
/* Dark theme */
html[data-theme="dark"] {
--quote-background: #333;
--quote-border: #555;
--quote-text: #f9f9f9;
}
/* Ayu theme */
html[data-theme="ayu"] {
--quote-background: #272822;
--quote-border: #383830;
--quote-text: #f8f8f2;
}
body.sdk-docs {
nav.sidebar>div.sidebar-crate>a>img {
width: 190px;
height: 52px;
}
nav.sidebar {
flex: 0 0 250px;
}
}
html[data-theme="light"] .sidebar-crate > .logo-container > img {
content: url("https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/docs/images/Polkadot_Logo_Horizontal_Pink_Black.png");
}
/* Custom styles for blockquotes */
blockquote {
background-color: var(--quote-background);
border-left: 5px solid var(--quote-border);
color: var(--quote-text);
margin: 1em 0;
padding: 1em 1.5em;
/* font-style: italic; */
}
blockquote p {
margin: 0;
}
@@ -1,48 +0,0 @@
[package]
name = "pezkuwi-sdk-docs-first-pezpallet"
description = "A simple pezpallet created for the pezkuwi-sdk-docs guides"
version = "0.0.0"
license = "MIT-0"
authors.workspace = true
homepage.workspace = true
repository.workspace = true
edition.workspace = true
publish = false
documentation.workspace = true
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true }
docify = { workspace = true }
pezframe = { workspace = true, features = ["runtime"] }
pezframe-support = { workspace = true }
pezframe-system = { workspace = true }
scale-info = { workspace = true }
[features]
default = ["std"]
std = [
"codec/std",
"pezframe-support/std",
"pezframe-system/std",
"pezframe/std",
"scale-info/std",
]
runtime-benchmarks = [
"pezframe-support/runtime-benchmarks",
"pezframe-system/runtime-benchmarks",
"pezframe/runtime-benchmarks",
]
try-runtime = [
"pezframe-support/try-runtime",
"pezframe-system/try-runtime",
"pezframe/try-runtime",
]
serde = []
experimental = []
tuples-96 = []
@@ -1,485 +0,0 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Pallets used in the `your_first_pallet` guide.
#![cfg_attr(not(feature = "std"), no_std)]
#[docify::export]
#[pezframe::pezpallet(dev_mode)]
pub mod shell_pallet {
use pezframe::prelude::*;
#[pezpallet::config]
pub trait Config: pezframe_system::Config {}
#[pezpallet::pezpallet]
pub struct Pezpallet<T>(_);
}
#[pezframe::pezpallet(dev_mode)]
pub mod pezpallet {
use pezframe::prelude::*;
#[docify::export]
pub type Balance = u128;
#[pezpallet::config]
pub trait Config: pezframe_system::Config {}
#[pezpallet::pezpallet]
pub struct Pezpallet<T>(_);
#[docify::export]
/// Single storage item, of type `Balance`.
#[pezpallet::storage]
pub type TotalIssuance<T: Config> = StorageValue<_, Balance>;
#[docify::export]
/// A mapping from `T::AccountId` to `Balance`
#[pezpallet::storage]
pub type Balances<T: Config> = StorageMap<_, _, T::AccountId, Balance>;
#[docify::export(impl_pallet)]
#[pezpallet::call]
impl<T: Config> Pezpallet<T> {
/// An unsafe mint that can be called by anyone. Not a great idea.
pub fn mint_unsafe(
origin: T::RuntimeOrigin,
dest: T::AccountId,
amount: Balance,
) -> DispatchResult {
// ensure that this is a signed account, but we don't really check `_anyone`.
let _anyone = ensure_signed(origin)?;
// update the balances map. Notice how all `<T: Config>` remains as `<T>`.
Balances::<T>::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount));
// update total issuance.
TotalIssuance::<T>::mutate(|t| *t = Some(t.unwrap_or(0) + amount));
Ok(())
}
/// Transfer `amount` from `origin` to `dest`.
pub fn transfer(
origin: T::RuntimeOrigin,
dest: T::AccountId,
amount: Balance,
) -> DispatchResult {
let sender = ensure_signed(origin)?;
// ensure sender has enough balance, and if so, calculate what is left after `amount`.
let sender_balance = Balances::<T>::get(&sender).ok_or("NonExistentAccount")?;
if sender_balance < amount {
return Err("InsufficientBalance".into());
}
let remainder = sender_balance - amount;
// update sender and dest balances.
Balances::<T>::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount));
Balances::<T>::insert(&sender, remainder);
Ok(())
}
}
#[allow(unused)]
impl<T: Config> Pezpallet<T> {
#[docify::export]
pub fn transfer_better(
origin: T::RuntimeOrigin,
dest: T::AccountId,
amount: Balance,
) -> DispatchResult {
let sender = ensure_signed(origin)?;
let sender_balance = Balances::<T>::get(&sender).ok_or("NonExistentAccount")?;
ensure!(sender_balance >= amount, "InsufficientBalance");
let remainder = sender_balance - amount;
// .. snip
Ok(())
}
#[docify::export]
/// Transfer `amount` from `origin` to `dest`.
pub fn transfer_better_checked(
origin: T::RuntimeOrigin,
dest: T::AccountId,
amount: Balance,
) -> DispatchResult {
let sender = ensure_signed(origin)?;
let sender_balance = Balances::<T>::get(&sender).ok_or("NonExistentAccount")?;
let remainder = sender_balance.checked_sub(amount).ok_or("InsufficientBalance")?;
// .. snip
Ok(())
}
}
#[cfg(any(test, doc))]
pub(crate) mod tests {
use crate::pezpallet::*;
#[docify::export(testing_prelude)]
use pezframe::testing_prelude::*;
pub(crate) const ALICE: u64 = 1;
pub(crate) const BOB: u64 = 2;
pub(crate) const CHARLIE: u64 = 3;
#[docify::export]
// This runtime is only used for testing, so it should be somewhere like `#[cfg(test)] mod
// tests { .. }`
mod runtime {
use super::*;
// we need to reference our `mod pezpallet` as an identifier to pass to
// `construct_runtime`.
// YOU HAVE TO CHANGE THIS LINE BASED ON YOUR TEMPLATE
use crate::pezpallet as pezpallet_currency;
construct_runtime!(
pub enum Runtime {
// ---^^^^^^ This is where `enum Runtime` is defined.
System: pezframe_system,
Currency: pezpallet_currency,
}
);
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
impl pezframe_system::Config for Runtime {
type Block = MockBlock<Runtime>;
// within pezpallet we just said `<T as pezframe_system::Config>::AccountId`, now we
// finally specified it.
type AccountId = u64;
}
// our simple pezpallet has nothing to be configured.
impl pezpallet_currency::Config for Runtime {}
}
pub(crate) use runtime::*;
#[allow(unused)]
#[docify::export]
fn new_test_state_basic() -> TestState {
let mut state = TestState::new_empty();
let accounts = vec![(ALICE, 100), (BOB, 100)];
state.execute_with(|| {
for (who, amount) in &accounts {
Balances::<Runtime>::insert(who, amount);
TotalIssuance::<Runtime>::mutate(|b| *b = Some(b.unwrap_or(0) + amount));
}
});
state
}
#[docify::export]
pub(crate) struct StateBuilder {
balances: Vec<(<Runtime as pezframe_system::Config>::AccountId, Balance)>,
}
#[docify::export(default_state_builder)]
impl Default for StateBuilder {
fn default() -> Self {
Self { balances: vec![(ALICE, 100), (BOB, 100)] }
}
}
#[docify::export(impl_state_builder_add)]
impl StateBuilder {
fn add_balance(
mut self,
who: <Runtime as pezframe_system::Config>::AccountId,
amount: Balance,
) -> Self {
self.balances.push((who, amount));
self
}
}
#[docify::export(impl_state_builder_build)]
impl StateBuilder {
pub(crate) fn build_and_execute(self, test: impl FnOnce() -> ()) {
let mut ext = TestState::new_empty();
ext.execute_with(|| {
for (who, amount) in &self.balances {
Balances::<Runtime>::insert(who, amount);
TotalIssuance::<Runtime>::mutate(|b| *b = Some(b.unwrap_or(0) + amount));
}
});
ext.execute_with(test);
// assertions that must always hold
ext.execute_with(|| {
assert_eq!(
Balances::<Runtime>::iter().map(|(_, x)| x).sum::<u128>(),
TotalIssuance::<Runtime>::get().unwrap_or_default()
);
})
}
}
#[docify::export]
#[test]
fn first_test() {
TestState::new_empty().execute_with(|| {
// We expect Alice's account to have no funds.
assert_eq!(Balances::<Runtime>::get(&ALICE), None);
assert_eq!(TotalIssuance::<Runtime>::get(), None);
// mint some funds into Alice's account.
assert_ok!(Pezpallet::<Runtime>::mint_unsafe(
RuntimeOrigin::signed(ALICE),
ALICE,
100
));
// re-check the above
assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
assert_eq!(TotalIssuance::<Runtime>::get(), Some(100));
})
}
#[docify::export]
#[test]
fn state_builder_works() {
StateBuilder::default().build_and_execute(|| {
assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
assert_eq!(Balances::<Runtime>::get(&BOB), Some(100));
assert_eq!(Balances::<Runtime>::get(&CHARLIE), None);
assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
});
}
#[docify::export]
#[test]
fn state_builder_add_balance() {
StateBuilder::default().add_balance(CHARLIE, 42).build_and_execute(|| {
assert_eq!(Balances::<Runtime>::get(&CHARLIE), Some(42));
assert_eq!(TotalIssuance::<Runtime>::get(), Some(242));
})
}
#[test]
#[should_panic]
fn state_builder_duplicate_genesis_fails() {
StateBuilder::default()
.add_balance(CHARLIE, 42)
.add_balance(CHARLIE, 43)
.build_and_execute(|| {
assert_eq!(Balances::<Runtime>::get(&CHARLIE), None);
assert_eq!(TotalIssuance::<Runtime>::get(), Some(242));
})
}
#[docify::export]
#[test]
fn mint_works() {
StateBuilder::default().build_and_execute(|| {
// given the initial state, when:
assert_ok!(Pezpallet::<Runtime>::mint_unsafe(
RuntimeOrigin::signed(ALICE),
BOB,
100
));
// then:
assert_eq!(Balances::<Runtime>::get(&BOB), Some(200));
assert_eq!(TotalIssuance::<Runtime>::get(), Some(300));
// given:
assert_ok!(Pezpallet::<Runtime>::mint_unsafe(
RuntimeOrigin::signed(ALICE),
CHARLIE,
100
));
// then:
assert_eq!(Balances::<Runtime>::get(&CHARLIE), Some(100));
assert_eq!(TotalIssuance::<Runtime>::get(), Some(400));
});
}
#[docify::export]
#[test]
fn transfer_works() {
StateBuilder::default().build_and_execute(|| {
// given the initial state, when:
assert_ok!(Pezpallet::<Runtime>::transfer(RuntimeOrigin::signed(ALICE), BOB, 50));
// then:
assert_eq!(Balances::<Runtime>::get(&ALICE), Some(50));
assert_eq!(Balances::<Runtime>::get(&BOB), Some(150));
assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
// when:
assert_ok!(Pezpallet::<Runtime>::transfer(RuntimeOrigin::signed(BOB), ALICE, 50));
// then:
assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
assert_eq!(Balances::<Runtime>::get(&BOB), Some(100));
assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
});
}
#[docify::export]
#[test]
fn transfer_from_non_existent_fails() {
StateBuilder::default().build_and_execute(|| {
// given the initial state, when:
assert_err!(
Pezpallet::<Runtime>::transfer(RuntimeOrigin::signed(CHARLIE), ALICE, 10),
"NonExistentAccount"
);
// then nothing has changed.
assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
assert_eq!(Balances::<Runtime>::get(&BOB), Some(100));
assert_eq!(Balances::<Runtime>::get(&CHARLIE), None);
assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
});
}
}
}
#[pezframe::pezpallet(dev_mode)]
pub mod pezpallet_v2 {
use super::pezpallet::Balance;
use pezframe::prelude::*;
#[docify::export(config_v2)]
#[pezpallet::config]
pub trait Config: pezframe_system::Config {
/// The overarching event type of the runtime.
#[allow(deprecated)]
type RuntimeEvent: From<Event<Self>>
+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>
+ TryInto<Event<Self>>;
}
#[pezpallet::pezpallet]
pub struct Pezpallet<T>(_);
#[pezpallet::storage]
pub type Balances<T: Config> = StorageMap<_, _, T::AccountId, Balance>;
#[pezpallet::storage]
pub type TotalIssuance<T: Config> = StorageValue<_, Balance>;
#[docify::export]
#[pezpallet::error]
pub enum Error<T> {
/// Account does not exist.
NonExistentAccount,
/// Account does not have enough balance.
InsufficientBalance,
}
#[docify::export]
#[pezpallet::event]
#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// A transfer succeeded.
Transferred { from: T::AccountId, to: T::AccountId, amount: Balance },
}
#[pezpallet::call]
impl<T: Config> Pezpallet<T> {
#[docify::export(transfer_v2)]
pub fn transfer(
origin: T::RuntimeOrigin,
dest: T::AccountId,
amount: Balance,
) -> DispatchResult {
let sender = ensure_signed(origin)?;
// ensure sender has enough balance, and if so, calculate what is left after `amount`.
let sender_balance =
Balances::<T>::get(&sender).ok_or(Error::<T>::NonExistentAccount)?;
let remainder =
sender_balance.checked_sub(amount).ok_or(Error::<T>::InsufficientBalance)?;
Balances::<T>::mutate(&dest, |b| *b = Some(b.unwrap_or(0) + amount));
Balances::<T>::insert(&sender, remainder);
Self::deposit_event(Event::<T>::Transferred { from: sender, to: dest, amount });
Ok(())
}
}
#[cfg(any(test, doc))]
pub mod tests {
use super::{super::pezpallet::tests::StateBuilder, *};
use pezframe::testing_prelude::*;
const ALICE: u64 = 1;
const BOB: u64 = 2;
#[docify::export]
pub mod runtime_v2 {
use super::*;
use crate::pezpallet_v2 as pezpallet_currency;
construct_runtime!(
pub enum Runtime {
System: pezframe_system,
Currency: pezpallet_currency,
}
);
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
impl pezframe_system::Config for Runtime {
type Block = MockBlock<Runtime>;
type AccountId = u64;
}
impl pezpallet_currency::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
}
}
pub(crate) use runtime_v2::*;
#[docify::export(transfer_works_v2)]
#[test]
fn transfer_works() {
StateBuilder::default().build_and_execute(|| {
// skip the genesis block, as events are not deposited there and we need them for
// the final assertion.
System::set_block_number(ALICE);
// given the initial state, when:
assert_ok!(Pezpallet::<Runtime>::transfer(RuntimeOrigin::signed(ALICE), BOB, 50));
// then:
assert_eq!(Balances::<Runtime>::get(&ALICE), Some(50));
assert_eq!(Balances::<Runtime>::get(&BOB), Some(150));
assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
// now we can also check that an event has been deposited:
assert_eq!(
System::read_events_for_pallet::<Event<Runtime>>(),
vec![Event::Transferred { from: ALICE, to: BOB, amount: 50 }]
);
});
}
}
}
@@ -1,123 +0,0 @@
[package]
name = "pezkuwi-sdk-docs-first-runtime"
description = "A simple runtime created for the pezkuwi-sdk-docs guides"
version = "0.0.0"
license = "MIT-0"
authors.workspace = true
homepage.workspace = true
repository.workspace = true
edition.workspace = true
publish = false
documentation.workspace = true
[lints]
workspace = true
[dependencies]
codec = { workspace = true }
scale-info = { workspace = true }
serde_json = { workspace = true }
# this is a frame-based runtime, thus importing `frame` with runtime feature enabled.
pezframe = { workspace = true, features = ["runtime"] }
pezframe-support = { workspace = true }
pezframe-system = { workspace = true }
# pallets that we want to use
pezpallet-balances = { workspace = true }
pezpallet-sudo = { workspace = true }
pezpallet-timestamp = { workspace = true }
pezpallet-transaction-payment = { workspace = true }
pezpallet-transaction-payment-rpc-runtime-api = { workspace = true }
# other pezkuwi-sdk-deps
pezframe-system-rpc-runtime-api = { workspace = true }
pezsp-api = { workspace = true }
pezsp-block-builder = { workspace = true }
pezsp-core = { workspace = true }
pezsp-genesis-builder = { workspace = true }
pezsp-keyring = { workspace = true }
pezsp-offchain = { workspace = true }
pezsp-runtime = { workspace = true }
pezsp-session = { workspace = true }
pezsp-transaction-pool = { workspace = true }
# local pezpallet templates
first-pezpallet = { workspace = true }
docify = { workspace = true }
[build-dependencies]
bizinikiwi-wasm-builder = { workspace = true, optional = true }
[features]
default = ["std"]
std = [
"bizinikiwi-wasm-builder",
"bizinikiwi-wasm-builder?/std",
"codec/std",
"first-pezpallet/std",
"pezframe-support/std",
"pezframe-system-rpc-runtime-api/std",
"pezframe-system/std",
"pezframe/std",
"pezpallet-balances/std",
"pezpallet-sudo/std",
"pezpallet-timestamp/std",
"pezpallet-transaction-payment-rpc-runtime-api/std",
"pezpallet-transaction-payment/std",
"pezsp-api/std",
"pezsp-block-builder/std",
"pezsp-core/std",
"pezsp-genesis-builder/std",
"pezsp-keyring/std",
"pezsp-offchain/std",
"pezsp-runtime/std",
"pezsp-session/std",
"pezsp-transaction-pool/std",
"scale-info/std",
"serde_json/std",
]
runtime-benchmarks = [
"bizinikiwi-wasm-builder?/runtime-benchmarks",
"first-pezpallet/runtime-benchmarks",
"pezframe-support/runtime-benchmarks",
"pezframe-system-rpc-runtime-api/runtime-benchmarks",
"pezframe-system/runtime-benchmarks",
"pezframe/runtime-benchmarks",
"pezpallet-balances/runtime-benchmarks",
"pezpallet-sudo/runtime-benchmarks",
"pezpallet-timestamp/runtime-benchmarks",
"pezpallet-transaction-payment-rpc-runtime-api/runtime-benchmarks",
"pezpallet-transaction-payment/runtime-benchmarks",
"pezsp-api/runtime-benchmarks",
"pezsp-block-builder/runtime-benchmarks",
"pezsp-genesis-builder/runtime-benchmarks",
"pezsp-keyring/runtime-benchmarks",
"pezsp-offchain/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"pezsp-session/runtime-benchmarks",
"pezsp-transaction-pool/runtime-benchmarks",
]
try-runtime = [
"first-pezpallet/try-runtime",
"pezframe-support/try-runtime",
"pezframe-system/try-runtime",
"pezframe/try-runtime",
"pezpallet-balances/try-runtime",
"pezpallet-sudo/try-runtime",
"pezpallet-timestamp/try-runtime",
"pezpallet-transaction-payment-rpc-runtime-api/try-runtime",
"pezpallet-transaction-payment/try-runtime",
"pezsp-api/try-runtime",
"pezsp-block-builder/try-runtime",
"pezsp-genesis-builder/try-runtime",
"pezsp-keyring/try-runtime",
"pezsp-offchain/try-runtime",
"pezsp-runtime/try-runtime",
"pezsp-session/try-runtime",
"pezsp-transaction-pool/try-runtime",
]
serde = []
experimental = []
tuples-96 = []
@@ -1,27 +0,0 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
fn main() {
#[cfg(feature = "std")]
{
bizinikiwi_wasm_builder::WasmBuilder::new()
.with_current_project()
.export_heap_base()
.import_memory()
.build();
}
}
@@ -1,302 +0,0 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Runtime used in `your_first_runtime`.
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use alloc::{vec, vec::Vec};
use first_pezpallet::pezpallet_v2 as our_first_pallet;
use pezframe::{deps::pezsp_genesis_builder::DEV_RUNTIME_PRESET, prelude::*, runtime::prelude::*};
use pezpallet_transaction_payment_rpc_runtime_api::{FeeDetails, RuntimeDispatchInfo};
use pezsp_keyring::Sr25519Keyring;
use pezsp_runtime::{
traits::Block as BlockT,
transaction_validity::{TransactionSource, TransactionValidity},
ApplyExtrinsicResult,
};
#[docify::export]
#[runtime_version]
pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: alloc::borrow::Cow::Borrowed("first-runtime"),
impl_name: alloc::borrow::Cow::Borrowed("first-runtime"),
authoring_version: 1,
spec_version: 0,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
system_version: 1,
};
#[docify::export(cr)]
construct_runtime!(
pub struct Runtime {
// Mandatory for all runtimes
System: pezframe_system,
// A number of other pallets from FRAME.
Timestamp: pezpallet_timestamp,
Balances: pezpallet_balances,
Sudo: pezpallet_sudo,
TransactionPayment: pezpallet_transaction_payment,
// Our local pezpallet
FirstPallet: our_first_pallet,
}
);
#[docify::export_content]
mod runtime_types {
use super::*;
pub(super) type SignedExtra = (
// `frame` already provides all the signed extensions from `pezframe-system`. We just add
// the one related to tx-payment here.
pezframe::runtime::types_common::SystemTransactionExtensionsOf<Runtime>,
pezpallet_transaction_payment::ChargeTransactionPayment<Runtime>,
);
pub(super) type Block = pezframe::runtime::types_common::BlockOf<Runtime, SignedExtra>;
pub(super) type _Header = HeaderFor<Runtime>;
pub(super) type RuntimeExecutive = Executive<
Runtime,
Block,
pezframe_system::ChainContext<Runtime>,
Runtime,
AllPalletsWithSystem,
>;
}
use runtime_types::*;
#[docify::export_content]
mod config_impls {
use super::*;
parameter_types! {
pub const Version: RuntimeVersion = VERSION;
}
#[derive_impl(pezframe_system::config_preludes::SolochainDefaultConfig)]
impl pezframe_system::Config for Runtime {
type Block = Block;
type Version = Version;
type AccountData =
pezpallet_balances::AccountData<<Runtime as pezpallet_balances::Config>::Balance>;
}
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
impl pezpallet_balances::Config for Runtime {
type AccountStore = System;
}
#[derive_impl(pezpallet_sudo::config_preludes::TestDefaultConfig)]
impl pezpallet_sudo::Config for Runtime {}
#[derive_impl(pezpallet_timestamp::config_preludes::TestDefaultConfig)]
impl pezpallet_timestamp::Config for Runtime {}
#[derive_impl(pezpallet_transaction_payment::config_preludes::TestDefaultConfig)]
impl pezpallet_transaction_payment::Config for Runtime {
type OnChargeTransaction = pezpallet_transaction_payment::FungibleAdapter<Balances, ()>;
// We specify a fixed length to fee here, which essentially means all transactions charge
// exactly 1 unit of fee.
type LengthToFee = FixedFee<1, <Self as pezpallet_balances::Config>::Balance>;
type WeightToFee = NoFee<<Self as pezpallet_balances::Config>::Balance>;
}
}
#[docify::export(our_config_impl)]
impl our_first_pallet::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
}
/// Provides getters for genesis configuration presets.
pub mod genesis_config_presets {
use super::*;
use crate::{
interface::{Balance, MinimumBalance},
BalancesConfig, RuntimeGenesisConfig, SudoConfig,
};
use pezframe::deps::pezframe_support::build_struct_json_patch;
use serde_json::Value;
/// Returns a development genesis config preset.
#[docify::export]
pub fn development_config_genesis() -> Value {
let endowment = <MinimumBalance as Get<Balance>>::get().max(1) * 1000;
build_struct_json_patch!(RuntimeGenesisConfig {
balances: BalancesConfig {
balances: Sr25519Keyring::iter()
.map(|a| (a.to_account_id(), endowment))
.collect::<Vec<_>>(),
},
sudo: SudoConfig { key: Some(Sr25519Keyring::Alice.to_account_id()) },
})
}
/// Get the set of the available genesis config presets.
#[docify::export]
pub fn get_preset(id: &PresetId) -> Option<Vec<u8>> {
let patch = match id.as_ref() {
DEV_RUNTIME_PRESET => development_config_genesis(),
_ => return None,
};
Some(
serde_json::to_string(&patch)
.expect("serialization to json is expected to work. qed.")
.into_bytes(),
)
}
/// List of supported presets.
#[docify::export]
pub fn preset_names() -> Vec<PresetId> {
vec![PresetId::from(DEV_RUNTIME_PRESET)]
}
}
impl_runtime_apis! {
impl pezsp_api::Core<Block> for Runtime {
fn version() -> RuntimeVersion {
VERSION
}
fn execute_block(block: <Block as BlockT>::LazyBlock) {
RuntimeExecutive::execute_block(block)
}
fn initialize_block(header: &<Block as BlockT>::Header) -> ExtrinsicInclusionMode {
RuntimeExecutive::initialize_block(header)
}
}
impl pezsp_api::Metadata<Block> for Runtime {
fn metadata() -> OpaqueMetadata {
OpaqueMetadata::new(Runtime::metadata().into())
}
fn metadata_at_version(version: u32) -> Option<OpaqueMetadata> {
Runtime::metadata_at_version(version)
}
fn metadata_versions() -> Vec<u32> {
Runtime::metadata_versions()
}
}
impl pezsp_block_builder::BlockBuilder<Block> for Runtime {
fn apply_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResult {
RuntimeExecutive::apply_extrinsic(extrinsic)
}
fn finalize_block() -> <Block as BlockT>::Header {
RuntimeExecutive::finalize_block()
}
fn inherent_extrinsics(data: InherentData) -> Vec<<Block as BlockT>::Extrinsic> {
data.create_extrinsics()
}
fn check_inherents(
block: <Block as BlockT>::LazyBlock,
data: InherentData,
) -> CheckInherentsResult {
data.check_extrinsics(&block)
}
}
impl pezsp_transaction_pool::runtime_api::TaggedTransactionQueue<Block> for Runtime {
fn validate_transaction(
source: TransactionSource,
tx: <Block as BlockT>::Extrinsic,
block_hash: <Block as BlockT>::Hash,
) -> TransactionValidity {
RuntimeExecutive::validate_transaction(source, tx, block_hash)
}
}
impl pezsp_offchain::OffchainWorkerApi<Block> for Runtime {
fn offchain_worker(header: &<Block as BlockT>::Header) {
RuntimeExecutive::offchain_worker(header)
}
}
impl pezsp_session::SessionKeys<Block> for Runtime {
fn generate_session_keys(_seed: Option<Vec<u8>>) -> Vec<u8> {
Default::default()
}
fn decode_session_keys(
_encoded: Vec<u8>,
) -> Option<Vec<(Vec<u8>, pezsp_core::crypto::KeyTypeId)>> {
Default::default()
}
}
impl pezframe_system_rpc_runtime_api::AccountNonceApi<Block, interface::AccountId, interface::Nonce> for Runtime {
fn account_nonce(account: interface::AccountId) -> interface::Nonce {
System::account_nonce(account)
}
}
impl pezsp_genesis_builder::GenesisBuilder<Block> for Runtime {
fn build_state(config: Vec<u8>) -> GenesisBuilderResult {
build_state::<RuntimeGenesisConfig>(config)
}
fn get_preset(id: &Option<PresetId>) -> Option<Vec<u8>> {
get_preset::<RuntimeGenesisConfig>(id, self::genesis_config_presets::get_preset)
}
fn preset_names() -> Vec<PresetId> {
crate::genesis_config_presets::preset_names()
}
}
impl pezpallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi<
Block,
interface::Balance,
> for Runtime {
fn query_info(uxt: <Block as BlockT>::Extrinsic, len: u32) -> RuntimeDispatchInfo<interface::Balance> {
TransactionPayment::query_info(uxt, len)
}
fn query_fee_details(uxt: <Block as BlockT>::Extrinsic, len: u32) -> FeeDetails<interface::Balance> {
TransactionPayment::query_fee_details(uxt, len)
}
fn query_weight_to_fee(weight: Weight) -> interface::Balance {
TransactionPayment::weight_to_fee(weight)
}
fn query_length_to_fee(length: u32) -> interface::Balance {
TransactionPayment::length_to_fee(length)
}
}
}
/// Just a handy re-definition of some types based on what is already provided to the pezpallet
/// configs.
pub mod interface {
use super::Runtime;
use pezframe::prelude::pezframe_system;
pub type AccountId = <Runtime as pezframe_system::Config>::AccountId;
pub type Nonce = <Runtime as pezframe_system::Config>::Nonce;
pub type Hash = <Runtime as pezframe_system::Config>::Hash;
pub type Balance = <Runtime as pezpallet_balances::Config>::Balance;
pub type MinimumBalance = <Runtime as pezpallet_balances::Config>::ExistentialDeposit;
}
@@ -1,14 +0,0 @@
//! # External Resources
//!
//! A non-exhaustive, un-opinionated list of external resources about Pezkuwi SDK.
//!
//! Unlike [`crate::guides`], or [`crate::pezkuwi_sdk::templates`] that contain material directly
//! maintained in the `pezkuwi-sdk` repository, the list of resources here are maintained by
//! third-parties, and are therefore subject to more variability. Any further resources may be added
//! by opening a pull request to the `pezkuwi-sdk` repository.
//!
//! - [Pezkuwi NFT Marketplace Tutorial by Pezkuwi Fellow Shawn Tabrizi](https://www.shawntabrizi.com/substrate-collectables-workshop/)
//! - [HEZ Code School](https://pezkuwichain.io/docs/introduction)
//! - [Pezkuwi Developers Github Organization](https://github.com/polkadot-developers/)
//! - [Pezkuwi Blockchain Academy](https://github.com/pezkuwichain/kurdistan_blockchain-akademy)
//! - [Pezkuwi Wiki](https://wiki.network.pezkuwichain.io/)
@@ -1,254 +0,0 @@
//! # Upgrade Teyrchain for Asynchronous Backing Compatibility
//!
//! This guide is relevant for pezcumulus based teyrchain projects started in 2023 or before, whose
//! backing process is synchronous where parablocks can only be built on the latest Relay Chain
//! block. Async Backing allows collators to build parablocks on older Relay Chain blocks and create
//! pipelines of multiple pending parablocks. This parallel block generation increases efficiency
//! and throughput. For more information on Async backing and its terminology, refer to the document
//! on [the Pezkuwi SDK docs.](https://docs.pezkuwichain.io/sdk/master/polkadot_sdk_docs/guides/async_backing_guide/index.html)
//!
//! > If starting a new teyrchain project, please use an async backing compatible template such as
//! > the
//! > [teyrchain template](https://github.com/pezkuwichain/pezkuwi-sdk/tree/main/templates/teyrchain).
//! The rollout process for Async Backing has three phases. Phases 1 and 2 below put new
//! infrastructure in place. Then we can simply turn on async backing in phase 3.
//!
//! ## Prerequisite
//!
//! The relay chain needs to have async backing enabled so double-check that the relay-chain
//! configuration contains the following three parameters (especially when testing locally e.g. with
//! zombienet):
//!
//! ```json
//! "async_backing_params": {
//! "max_candidate_depth": 3,
//! "allowed_ancestry_len": 2
//! },
//! "scheduling_lookahead": 2
//! ```
//!
//! <div class="warning"><code>scheduling_lookahead</code> must be set to 2, otherwise teyrchain
//! block times will degrade to worse than with sync backing!</div>
//!
//! ## Phase 1 - Update Teyrchain Runtime
//!
//! This phase involves configuring your teyrchains runtime `/runtime/src/lib.rs` to make use of
//! async backing system.
//!
//! 1. Establish and ensure constants for `capacity` and `velocity` are both set to 1 in the
//! runtime.
//! 2. Establish and ensure the constant relay chain slot duration measured in milliseconds equal to
//! `6000` in the runtime.
//! ```rust
//! // Maximum number of blocks simultaneously accepted by the Runtime, not yet included into the
//! // relay chain.
//! pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 1;
//! // How many teyrchain blocks are processed by the relay chain per parent. Limits the number of
//! // blocks authored per slot.
//! pub const BLOCK_PROCESSING_VELOCITY: u32 = 1;
//! // Relay chain slot duration, in milliseconds.
//! pub const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000;
//! ```
//!
//! 3. Establish constants `MILLISECS_PER_BLOCK` and `SLOT_DURATION` if not already present in the
//! runtime.
//! ```ignore
//! // `SLOT_DURATION` is picked up by `pezpallet_timestamp` which is in turn picked
//! // up by `pezpallet_aura` to implement `fn slot_duration()`.
//! //
//! // Change this to adjust the block time.
//! pub const MILLISECS_PER_BLOCK: u64 = 12000;
//! pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK;
//! ```
//!
//! 4. Configure `pezcumulus_pezpallet_teyrchain_system` in the runtime.
//!
//! - Define a `FixedVelocityConsensusHook` using our capacity, velocity, and relay slot duration
//! constants. Use this to set the teyrchain system `ConsensusHook` property.
#![doc = docify::embed!("../../templates/teyrchain/runtime/src/lib.rs", ConsensusHook)]
//! ```ignore
//! impl pezcumulus_pezpallet_teyrchain_system::Config for Runtime {
//! ..
//! type ConsensusHook = ConsensusHook;
//! ..
//! }
//! ```
//! - Set the teyrchain system property `CheckAssociatedRelayNumber` to
//! `RelayNumberMonotonicallyIncreases`
//! ```ignore
//! impl pezcumulus_pezpallet_teyrchain_system::Config for Runtime {
//! ..
//! type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases;
//! ..
//! }
//! ```
//!
//! 5. Configure `pezpallet_aura` in the runtime.
//!
//! - Set `AllowMultipleBlocksPerSlot` to `false` (don't worry, we will set it to `true` when we
//! activate async backing in phase 3).
//!
//! - Define `pezpallet_aura::SlotDuration` using our constant `SLOT_DURATION`
//! ```ignore
//! impl pezpallet_aura::Config for Runtime {
//! ..
//! type AllowMultipleBlocksPerSlot = ConstBool<false>;
//! #[cfg(feature = "experimental")]
//! type SlotDuration = ConstU64<SLOT_DURATION>;
//! ..
//! }
//! ```
//!
//! 6. Update `pezsp_consensus_aura::AuraApi::slot_duration` in `pezsp_api::impl_runtime_apis` to
//! match the constant `SLOT_DURATION`
#![doc = docify::embed!("../../templates/teyrchain/runtime/src/apis.rs", impl_slot_duration)]
//!
//! 7. Implement the `AuraUnincludedSegmentApi`, which allows the collator client to query its
//! runtime to determine whether it should author a block.
//!
//! - Add the dependency `pezcumulus-primitives-aura` to the `runtime/Cargo.toml` file for your
//! runtime
//! ```ignore
//! ..
//! pezcumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false }
//! ..
//! ```
//!
//! - In the same file, add `"pezcumulus-primitives-aura/std",` to the `std` feature.
//!
//! - Inside the `impl_runtime_apis!` block for your runtime, implement the
//! `pezcumulus_primitives_aura::AuraUnincludedSegmentApi` as shown below.
#![doc = docify::embed!("../../templates/teyrchain/runtime/src/apis.rs", impl_can_build_upon)]
//!
//! **Note:** With a capacity of 1 we have an effective velocity of ½ even when velocity is
//! configured to some larger value. This is because capacity will be filled after a single block is
//! produced and will only be freed up after that block is included on the relay chain, which takes
//! 2 relay blocks to accomplish. Thus with capacity 1 and velocity 1 we get the customary 12 second
//! teyrchain block time.
//!
//! 8. If your `runtime/src/lib.rs` provides a `CheckInherents` type to `register_validate_block`,
//! remove it. `FixedVelocityConsensusHook` makes it unnecessary. The following example shows how
//! `register_validate_block` should look after removing `CheckInherents`.
#![doc = docify::embed!("../../templates/teyrchain/runtime/src/lib.rs", register_validate_block)]
//!
//!
//! ## Phase 2 - Update Teyrchain Nodes
//!
//! This phase consists of plugging in the new lookahead collator node.
//!
//! 1. Import `pezcumulus_primitives_core::ValidationCode` to `node/src/service.rs`.
#![doc = docify::embed!("../../templates/teyrchain/node/src/service.rs", pezcumulus_primitives)]
//!
//! 2. In `node/src/service.rs`, modify `pezsc_service::spawn_tasks` to use a clone of `Backend`
//! rather than the original
//! ```ignore
//! pezsc_service::spawn_tasks(pezsc_service::SpawnTasksParams {
//! ..
//! backend: backend.clone(),
//! ..
//! })?;
//! ```
//!
//! 3. Add `backend` as a parameter to `start_consensus()` in `node/src/service.rs`
//! ```text
//! fn start_consensus(
//! ..
//! backend: Arc<TeyrchainBackend>,
//! ..
//! ```
//! ```ignore
//! if validator {
//! start_consensus(
//! ..
//! backend.clone(),
//! ..
//! )?;
//! }
//! ```
//!
//! 4. In `node/src/service.rs` import the lookahead collator rather than the basic collator
#![doc = docify::embed!("../../templates/teyrchain/node/src/service.rs", lookahead_collator)]
//!
//! 5. In `start_consensus()` replace the `BasicAuraParams` struct with `AuraParams`
//! - Change the struct type from `BasicAuraParams` to `AuraParams`
//! - In the `para_client` field, pass in a cloned para client rather than the original
//! - Add a `para_backend` parameter after `para_client`, passing in our para backend
//! - Provide a `code_hash_provider` closure like that shown below
//! - Increase `authoring_duration` from 500 milliseconds to 2000
//! ```ignore
//! let params = AuraParams {
//! ..
//! para_client: client.clone(),
//! para_backend: backend.clone(),
//! ..
//! code_hash_provider: move |block_hash| {
//! client.code_at(block_hash).ok().map(|c| ValidationCode::from(c).hash())
//! },
//! ..
//! authoring_duration: Duration::from_millis(2000),
//! ..
//! };
//! ```
//!
//! **Note:** Set `authoring_duration` to whatever you want, taking your own hardware into account.
//! But if the backer who should be slower than you due to reading from disk, times out at two
//! seconds your candidates will be rejected.
//!
//! 6. In `start_consensus()` replace `basic_aura::run` with `aura::run`
//! ```ignore
//! let fut =
//! aura::run::<Block, pezsp_consensus_aura::sr25519::AuthorityPair, _, _, _, _, _, _, _, _, _>(
//! params,
//! );
//! task_manager.spawn_essential_handle().spawn("aura", None, fut);
//! ```
//!
//! ## Phase 3 - Activate Async Backing
//!
//! This phase consists of changes to your teyrchains runtime that activate async backing feature.
//!
//! 1. Configure `pezpallet_aura`, setting `AllowMultipleBlocksPerSlot` to true in
//! `runtime/src/lib.rs`.
#![doc = docify::embed!("../../templates/teyrchain/runtime/src/configs/mod.rs", aura_config)]
//!
//! 2. Increase the maximum `UNINCLUDED_SEGMENT_CAPACITY` in `runtime/src/lib.rs`.
#![doc = docify::embed!("../../templates/teyrchain/runtime/src/lib.rs", async_backing_params)]
//!
//! 3. Decrease `MILLISECS_PER_BLOCK` to 6000.
//!
//! - Note: For a teyrchain which measures time in terms of its own block number rather than by
//! relay block number it may be preferable to increase velocity. Changing block time may cause
//! complications, requiring additional changes. See the section “Timing by Block Number”.
#![doc = docify::embed!("../../templates/teyrchain/runtime/src/lib.rs", block_times)]
//!
//! 4. Update `MAXIMUM_BLOCK_WEIGHT` to reflect the increased time available for block production.
#![doc = docify::embed!("../../templates/teyrchain/runtime/src/lib.rs", max_block_weight)]
//!
//! 5. Add a feature flagged alternative for `MinimumPeriod` in `pezpallet_timestamp`. The type
//! should be `ConstU64<0>` with the feature flag experimental, and `ConstU64<{SLOT_DURATION /
//! 2}>` without.
//! ```ignore
//! impl pezpallet_timestamp::Config for Runtime {
//! ..
//! #[cfg(feature = "experimental")]
//! type MinimumPeriod = ConstU64<0>;
//! #[cfg(not(feature = "experimental"))]
//! type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>;
//! ..
//! }
//! ```
//!
//! ## Timing by Block Number
//!
//! With asynchronous backing it will be possible for teyrchains to opt for a block time of 6
//! seconds rather than 12 seconds. But modifying block duration isnt so simple for a teyrchain
//! which was measuring time in terms of its own block number. It could result in expected and
//! actual time not matching up, stalling the teyrchain.
//!
//! One strategy to deal with this issue is to instead rely on relay chain block numbers for timing.
//! Relay block number is kept track of by each teyrchain in `pezpallet-teyrchain-system` with the
//! storage value `LastRelayChainBlockNumber`. This value can be obtained and used wherever timing
//! based on block number is needed.
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(rustdoc::private_intra_doc_links)]
@@ -1 +0,0 @@
//! # Changing Consensus
@@ -1,182 +0,0 @@
//! # Enable elastic scaling for a teyrchain
//!
//! <div class="warning">This guide assumes full familiarity with Asynchronous Backing and its
//! terminology, as defined in <a href="https://docs.pezkuwichain.io/sdk/master/polkadot_sdk_docs/guides/async_backing_guide/index.html">the Pezkuwi SDK Docs</a>.
//! </div>
//!
//! ## Quick introduction to Elastic Scaling
//!
//! [Elastic scaling](https://www.parity.io/blog/polkadot-web3-cloud) is a feature that enables teyrchains (rollups) to use multiple cores.
//! Teyrchains can adjust their usage of core resources on the fly to increase TPS and decrease
//! latency.
//!
//! ### When do you need Elastic Scaling?
//!
//! Depending on their use case, applications might have an increased need for the following:
//! - compute (CPU weight)
//! - bandwidth (proof size)
//! - lower latency (block time)
//!
//! ### High throughput (TPS) and lower latency
//!
//! If the main bottleneck is the CPU, then your teyrchain needs to maximize the compute usage of
//! each core while also achieving a lower latency.
//! 3 cores provide the best balance between CPU, bandwidth and latency: up to 6s of execution,
//! 5MB/s of DA bandwidth and fast block time of just 2 seconds.
//!
//! ### High bandwidth
//!
//! Useful for applications that are bottlenecked by bandwidth.
//! By using 6 cores, applications can make use of up to 6s of compute, 10MB/s of bandwidth
//! while also achieving 1 second block times.
//!
//! ### Ultra low latency
//!
//! When latency is the primary requirement, Elastic scaling is currently the only solution. The
//! caveat is the efficiency of core time usage decreases as more cores are used.
//!
//! For example, using 12 cores enables fast transaction confirmations with 500ms blocks and up to
//! 20 MB/s of DA bandwidth.
//!
//! ## Dependencies
//!
//! Prerequisites: Pezkuwi-SDK `2509` or newer.
//!
//! To ensure the security and reliability of your chain when using this feature you need the
//! following:
//! - An omni-node based collator. This has already become the default choice for collators.
//! - UMP signal support.
//! [RFC103](https://github.com/polkadot-fellows/RFCs/blob/main/text/0103-introduce-core-index-commitment.md).
//! This is mandatory protection against PoV replay attacks.
//! - Enabling the relay parent offset feature. This is required to ensure the teyrchain block times
//! and transaction in-block confidence are not negatively affected by relay chain forks. Read
//! [`crate::guides::handling_teyrchain_forks`] for more information.
//! - Block production configuration adjustments.
//!
//! ### Upgrade to Pezkuwi Omni node
//!
//! Your collators need to run `pezkuwi-teyrchain` or `pezkuwi-omni-node` with the `--authoring
//! slot-based` CLI argument.
//! To avoid potential issues and get best performance it is recommeneded to always run the
//! latest release on all of the collators.
//!
//! Further information about omni-node and how to upgrade is available:
//! - [high level docs](https://docs.pezkuwichain.io/develop/toolkit/parachains/polkadot-omni-node/)
//! - [`crate::reference_docs::omni_node`]
//!
//! ### UMP signals
//!
//! UMP signals are now enabled by default in the `teyrchain-system` pezpallet and are used for
//! elastic scaling. You can find more technical details about UMP signals and their usage for
//! elastic scaling
//! [here](https://github.com/polkadot-fellows/RFCs/blob/main/text/0103-introduce-core-index-commitment.md).
//!
//! ### Enable the relay parent offset feature
//!
//! It is recommended to use an offset of `1`, which is sufficient to eliminate any issues
//! with relay chain forks.
//!
//! Configure the relay parent offset like this:
//! ```ignore
//! /// Build with an offset of 1 behind the relay chain best block.
//! const RELAY_PARENT_OFFSET: u32 = 1;
//!
//! impl pezcumulus_pezpallet_teyrchain_system::Config for Runtime {
//! // ...
//! type RelayParentOffset = ConstU32<RELAY_PARENT_OFFSET>;
//! }
//! ```
//!
//! Implement the runtime API to retrieve the offset on the client side.
//! ```ignore
//! impl pezcumulus_primitives_core::RelayParentOffsetApi<Block> for Runtime {
//! fn relay_parent_offset() -> u32 {
//! RELAY_PARENT_OFFSET
//! }
//! }
//! ```
//!
//! ### Block production configuration
//!
//! This configuration directly controls the minimum block time and maximum number of cores
//! the teyrchain can use.
//!
//! Example configuration for a 3 core teyrchain:
//! ```ignore
//! /// The upper limit of how many teyrchain blocks are processed by the relay chain per
//! /// parent. Limits the number of blocks authored per slot. This determines the minimum
//! /// block time of the teyrchain:
//! /// `RELAY_CHAIN_SLOT_DURATION_MILLIS/BLOCK_PROCESSING_VELOCITY`
//! const BLOCK_PROCESSING_VELOCITY: u32 = 3;
//!
//! /// Maximum number of blocks simultaneously accepted by the Runtime, not yet included
//! /// into the relay chain.
//! const UNINCLUDED_SEGMENT_CAPACITY: u32 = (2 + RELAY_PARENT_OFFSET) *
//! BLOCK_PROCESSING_VELOCITY + 1;
//!
//! /// Relay chain slot duration, in milliseconds.
//! const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000;
//!
//! type ConsensusHook = pezcumulus_pezpallet_aura_ext::FixedVelocityConsensusHook<
//! Runtime,
//! RELAY_CHAIN_SLOT_DURATION_MILLIS,
//! BLOCK_PROCESSING_VELOCITY,
//! UNINCLUDED_SEGMENT_CAPACITY,
//! >;
//!
//! ```
//!
//! ### Teyrchain Slot Duration
//!
//! A common source of confusion is the correct configuration of the `SlotDuration` that is passed
//! to `pezpallet-aura`.
//! ```ignore
//! impl pezpallet_aura::Config for Runtime {
//! // ...
//! type SlotDuration = ConstU64<SLOT_DURATION>;
//! }
//! ```
//!
//! The slot duration determines the length of each author's turn and is decoupled from the block
//! production interval. During their slot, authors are allowed to produce multiple blocks. **The
//! slot duration is required to be at least 6s (same as on the relay chain).**
//!
//! **Configuration recommendations:**
//! - For new teyrchains starting from genesis: use a slot duration of 24 seconds
//! - For existing live teyrchains: leave the slot duration unchanged
//!
//!
//! ## Current limitations
//!
//! ### Maximum execution time per relay chain block.
//!
//! Since teyrchain block authoring is sequential, the next block can only be built after
//! the previous one has been imported.
//! At present, a core allows up to 2 seconds of execution per relay chain block.
//!
//! If we assume a 6s teyrchain slot, and each block takes the full 2 seconds to execute,
//! the teyrchain will not be able to fully utilize the compute resources of all 3 cores.
//!
//! If the collator hardware is faster, it can author and import full blocks more quickly,
//! making it possible to utilize even more than 3 cores efficiently.
//!
//! #### Why?
//!
//! Within a 6-second teyrchain slot, collators can author multiple teyrchain blocks.
//! Before building the first block in a slot, the new block author must import the last
//! block produced by the previous author.
//! If the import of the last block is not completed before the next relay chain slot starts,
//! the new author will build on its parent (assuming it was imported). This will create a fork
//! which degrades the teyrchain block confidence and block times.
//!
//! This means that, on reference hardware, a teyrchain with a slot time of 6s can
//! effectively utilize up to 4 seconds of execution per relay chain block, because it needs to
//! ensure the next block author has enough time to import the last block.
//! Hardware with higher single-core performance can enable a teyrchain to fully utilize more
//! cores.
//!
//! ### Fixed factor scaling.
//!
//! For true elasticity, a teyrchain needs to acquire more cores when needed in an automated
//! manner. This functionality is not yet available in the SDK, thus acquiring additional
//! on-demand or bulk cores has to be managed externally.
@@ -1,89 +0,0 @@
//! # Enable metadata hash verification
//!
//! This guide will teach you how to enable the metadata hash verification in your runtime.
//!
//! ## What is metadata hash verification?
//!
//! Each FRAME based runtime exposes metadata about itself. This metadata is used by consumers of
//! the runtime to interpret the state, to construct transactions etc. Part of this metadata are the
//! type information. These type information can be used to e.g. decode storage entries or to decode
//! a transaction. So, the metadata is quite useful for wallets to interact with a FRAME based
//! chain. Online wallets can fetch the metadata directly from any node of the chain they are
//! connected to, but offline wallets can not do this. So, for the offline wallet to have access to
//! the metadata it needs to be transferred and stored on the device. The problem is that the
//! metadata has a size of several hundreds of kilobytes, which takes quite a while to transfer to
//! these offline wallets and the internal storage of these devices is also not big enough to store
//! the metadata for one or more networks. The next problem is that the offline wallet/user can not
//! trust the metadata to be correct. It is very important for the metadata to be correct or
//! otherwise an attacker could change them in a way that the offline wallet decodes a transaction
//! in a different way than what it will be decoded to on chain. So, the user may sign an incorrect
//! transaction leading to unexpected behavior.
//!
//! The metadata hash verification circumvents the issues of the huge metadata and the need to trust
//! some metadata blob to be correct. To generate a hash for the metadata, the metadata is chunked,
//! these chunks are put into a merkle tree and then the root of this merkle tree is the "metadata
//! hash". For a more technical explanation on how it works, see
//! [RFC78](https://polkadot-fellows.github.io/RFCs/approved/0078-merkleized-metadata.html). At compile
//! time the metadata hash is generated and "baked" into the runtime. This makes it extremely cheap
//! for the runtime to verify on chain that the metadata hash is correct. By having the runtime
//! verify the hash on chain, the user also doesn't need to trust the offchain metadata. If the
//! metadata hash doesn't match the on chain metadata hash the transaction will be rejected. The
//! metadata hash itself is added to the data of the transaction that is signed, this means the
//! actual hash does not appear in the transaction. On chain the same procedure is repeated with the
//! metadata hash that is known by the runtime and if the metadata hash doesn't match the signature
//! verification will fail. As the metadata hash is actually the root of a merkle tree, the offline
//! wallet can get proofs of individual types to decode a transaction. This means that the offline
//! wallet does not require the entire metadata to be present on the device.
//!
//! ## Integrating metadata hash verification into your runtime
//!
//! The integration of the metadata hash verification is split into two parts, first the actual
//! integration into the runtime and secondly the enabling of the metadata hash generation at
//! compile time.
//!
//! ### Runtime integration
//!
//! From the runtime side only the
//! [`CheckMetadataHash`](pezframe_metadata_hash_extension::CheckMetadataHash) needs to be added to
//! the list of signed extension:
#![doc = docify::embed!("../../templates/teyrchain/runtime/src/lib.rs", template_signed_extra)]
//!
//! > **Note:**
//! >
//! > Adding the signed extension changes the encoding of the transaction and adds one extra byte
//! > per transaction!
//!
//! This signed extension will make sure to decode the requested `mode` and will add the metadata
//! hash to the signed data depending on the requested `mode`. The `mode` gives the user/wallet
//! control over deciding if the metadata hash should be verified or not. The metadata hash itself
//! is drawn from the `RUNTIME_METADATA_HASH` environment variable. If the environment variable is
//! not set, any transaction that requires the metadata hash is rejected with the error
//! `CannotLookup`. This is a security measurement to prevent including invalid transactions.
//!
//! <div class="warning">
//!
//! The extension does not work with the native runtime, because the
//! `RUNTIME_METADATA_HASH` environment variable is not set when building the
//! `pezframe-metadata-hash-extension` crate.
//!
//! </div>
//!
//! ### Enable metadata hash generation
//!
//! The metadata hash generation needs to be enabled when building the wasm binary. The
//! `bizinikiwi-wasm-builder` supports this out of the box:
#![doc = docify::embed!("../../templates/teyrchain/runtime/build.rs", template_enable_metadata_hash)]
//!
//! > **Note:**
//! >
//! > The `metadata-hash` feature needs to be enabled for the `bizinikiwi-wasm-builder` to enable
//! > the
//! > code for being able to generate the metadata hash. It is also recommended to put the metadata
//! > hash generation behind a feature in the runtime as shown above. The reason behind is that it
//! > adds a lot of code which increases the compile time and the generation itself also increases
//! > the compile time. Thus, it is recommended to enable the feature only when the metadata hash is
//! > required (e.g. for an on-chain build).
//!
//! The two parameters to `enable_metadata_hash` are the token symbol and the number of decimals of
//! the primary token of the chain. These information are included for the wallets to show token
//! related operations in a more user friendly way.
@@ -1,88 +0,0 @@
//! # Enable storage weight reclaiming
//!
//! This guide will teach you how to enable storage weight reclaiming for a teyrchain. The
//! explanations in this guide assume a project structure similar to the one detailed in
//! the [bizinikiwi documentation](crate::pezkuwi_sdk::bizinikiwi#anatomy-of-a-binary-crate). Full
//! technical details are available in the original [pull request](https://github.com/pezkuwichain/pezkuwi-sdk/issues/257).
//!
//! # What is PoV reclaim?
//! When a teyrchain submits a block to a relay chain like Pezkuwi or Dicle, it sends the block
//! itself and a storage proof. Together they form the Proof-of-Validity (PoV). The PoV allows the
//! relay chain to validate the teyrchain block by re-executing it. Relay chain
//! validators distribute this PoV among themselves over the network. This distribution is costly
//! and limits the size of the storage proof. The storage weight dimension of FRAME weights reflects
//! this cost and limits the size of the storage proof. However, the storage weight determined
//! during [benchmarking](crate::reference_docs::pezframe_benchmarking_weight) represents the worst
//! case. In reality, runtime operations often consume less space in the storage proof. PoV reclaim
//! offers a mechanism to reclaim the difference between the benchmarked worst-case and the real
//! proof-size consumption.
//!
//!
//! # How to enable PoV reclaim
//! ## 1. Add the host function to your node
//!
//! To reclaim excess storage weight, a teyrchain runtime needs the
//! ability to fetch the size of the storage proof from the node. The reclaim
//! mechanism uses the
//! [`storage_proof_size`](pezcumulus_primitives_proof_size_hostfunction::storage_proof_size)
//! host function for this purpose. For convenience, pezcumulus provides
//! [`TeyrchainHostFunctions`](pezcumulus_client_service::TeyrchainHostFunctions), a set of
//! host functions typically used by pezcumulus-based teyrchains. In the binary crate of your
//! teyrchain, find the instantiation of the [`WasmExecutor`](pezsc_executor::WasmExecutor) and set
//! the correct generic type.
//!
//! This example from the teyrchain-template shows a type definition that includes the correct
//! host functions.
#![doc = docify::embed!("../../templates/teyrchain/node/src/service.rs", wasm_executor)]
//!
//! > **Note:**
//! >
//! > If you see error `runtime requires function imports which are not present on the host:
//! > 'env:ext_storage_proof_size_storage_proof_size_version_1'`, it is likely
//! > that this step in the guide was not set up correctly.
//!
//! ## 2. Enable storage proof recording during import
//!
//! The reclaim mechanism reads the size of the currently recorded storage proof multiple times
//! during block authoring and block import. Proof recording during authoring is already enabled on
//! teyrchains. You must also ensure that storage proof recording is enabled during block import.
//! Find where your node builds the fundamental bizinikiwi components by calling
//! [`new_full_parts`](pezsc_service::new_full_parts). Replace this
//! with [`new_full_parts_record_import`](pezsc_service::new_full_parts_record_import) and
//! pass `true` as the last parameter to enable import recording.
#![doc = docify::embed!("../../templates/teyrchain/node/src/service.rs", component_instantiation)]
//!
//! > **Note:**
//! >
//! > If you see error `Storage root must match that calculated.` during block import, it is likely
//! > that this step in the guide was not
//! > set up correctly.
//!
//! ## 3. Add the TransactionExtension to your runtime
//!
//! In your runtime, you will find a list of TransactionExtensions.
//! To enable the reclaiming,
//! set [`StorageWeightReclaim`](pezcumulus_pezpallet_weight_reclaim::StorageWeightReclaim)
//! as a warpper of that list.
//! It is necessary that this extension wraps all the other transaction extensions in order to catch
//! the whole PoV size of the transactions.
//! The extension will check the size of the storage proof before and after an extrinsic execution.
//! It reclaims the difference between the calculated size and the benchmarked size.
#![doc = docify::embed!("../../templates/teyrchain/runtime/src/lib.rs", template_signed_extra)]
//!
//! ## Optional: Verify that reclaim works
//!
//! Start your node with the log target `runtime::storage_reclaim` set to `trace` to enable full
//! logging for `StorageWeightReclaim`. The following log is an example from a local testnet. To
//! trigger the log, execute any extrinsic on the network.
//!
//! ```ignore
//! ...
//! 2024-04-22 17:31:48.014 TRACE runtime::storage_reclaim: [ferdie] Reclaiming storage weight. benchmarked: 3593, consumed: 265 unspent: 0
//! ...
//! ```
//!
//! In the above example we see a benchmarked size of 3593 bytes, while the extrinsic only consumed
//! 265 bytes of proof size. This results in 3328 bytes of reclaim.
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(rustdoc::private_intra_doc_links)]
@@ -1,90 +0,0 @@
//! # Teyrchain forks
//!
//! In this guide, we will examine how AURA-based teyrchains handle forks. AURA (Authority Round) is
//! a consensus mechanism where block authors rotate at fixed time intervals. Each author gets a
//! predetermined time slice during which they are allowed to author a block. On its own, this
//! mechanism is fork-free.
//!
//! However, since the relay chain provides security and serves as the source of truth for
//! teyrchains, the teyrchain is dependent on it. This relationship can introduce complexities that
//! lead to forking scenarios.
//!
//! ## Background
//! Each teyrchain block has a relay parent, which is a relay chain block that provides context to
//! our teyrchain block. The constraints the relay chain imposes on our teyrchain can cause forks
//! under certain conditions. With asynchronous-backing enabled chains, the node side is building
//! blocks on all relay chain forks. This means that no matter which fork of the relay chain
//! ultimately progressed, the teyrchain would have a block ready for that fork. The situation
//! changes when teyrchains want to produce blocks at a faster cadence. In a scenario where a
//! teyrchain might author on 3 cores with elastic scaling, it is not possible to author on all
//! relay chain forks. The time constraints do not allow it. Building on two forks would result in 6
//! blocks. The authoring of these blocks would consume more time than we have available before the
//! next relay chain block arrives. This limitation requires a more fork-resistant approach to
//! block-building.
//!
//! ## Impact of Forks
//! When a relay chain fork occurs and the teyrchain builds on a fork that will not be extended in
//! the future, the blocks built on that fork are lost and need to be rebuilt. This increases
//! latency and reduces throughput, affecting the overall performance of the teyrchain.
//!
//! # Building on Older Pelay Parents
//! Pezcumulus offers a way to mitigate the occurence of forks. Instead of picking a block at the
//! tip of the relay chain to build blocks, the node side can pick a relay chain block that is
//! older. By building on 12s old relay chain blocks, forks will already have settled and the
//! teyrchain can build fork-free.
//!
//! ```text
//! Without offset:
//! Relay Chain: A --- B --- C --- D --- E
//! \
//! --- D' --- E'
//! Teyrchain: X --- Y --- ? (builds on both D and D', wasting resources)
//!
//! With offset (2 blocks):
//! Relay Chain: A --- B --- C --- D --- E
//! \
//! --- D' --- E'
//! Teyrchain: X(A) - Y (B) - Z (on C, fork already resolved)
//! ```
//! **Note:** It is possible that relay chain forks extend over more than 1-2 blocks. However, it is
//! unlikely.
//! ## Tradeoffs
//! Fork-free teyrchains come with a few tradeoffs:
//! - The latency of incoming XCM messages will be delayed by `N * 6s`, where `N` is the number of
//! relay chain blocks we want to offset by. For example, by building 2 relay chain blocks behind
//! the tip, the XCM latency will be increased by 12 seconds.
//! - The available PoV space will be slightly reduced. Assuming a 10mb PoV, teyrchains need to be
//! ready to sacrifice around 0.5% of PoV space.
//!
//! ## Enabling Guide
//! The decision whether the teyrchain should build on older relay parents is embedded into the
//! runtime. After the changes are implemented, the runtime will enforce that no author can build
//! with an offset smaller than the desired offset. If you wish to keep your current teyrchain
//! behaviour and do not want aforementioned tradeoffs, set the offset to 0.
//!
//! **Note:** The APIs mentioned here are available in `pezkuwi-sdk` versions after `stable-2506`.
//!
//! 1. Define the relay parent offset your teyrchain should respect in the runtime.
//! ```ignore
//! const RELAY_PARENT_OFFSET = 2;
//! ```
//! 2. Pass this constant to the `teyrchain-system` pezpallet.
//!
//! ```ignore
//! impl pezcumulus_pezpallet_teyrchain_system::Config for Runtime {
//! // Other config items here
//! ...
//! type RelayParentOffset = ConstU32<RELAY_PARENT_OFFSET>;
//! }
//! ```
//! 3. Implement the `RelayParentOffsetApi` runtime API for your runtime.
//!
//! ```ignore
//! impl pezcumulus_primitives_core::RelayParentOffsetApi<Block> for Runtime {
//! fn relay_parent_offset() -> u32 {
//! RELAY_PARENT_OFFSET
//! }
//! }
//! ```
//! 4. Increase the `UNINCLUDED_SEGMENT_CAPICITY` for your runtime. It needs to be increased by
//! `RELAY_PARENT_OFFSET * BLOCK_PROCESSING_VELOCITY`.
-50
View File
@@ -1,50 +0,0 @@
//! # Pezkuwi SDK Docs Guides
//!
//! This crate contains a collection of guides that are foundational to the developers of
//! Pezkuwi SDK. They are common user-journeys that are traversed in the Pezkuwi ecosystem.
//!
//! The main user-journey covered by these guides is:
//!
//! * [`your_first_pallet`], where you learn what a FRAME pezpallet is, and write your first
//! application logic.
//! * [`your_first_runtime`], where you learn how to compile your pallets into a WASM runtime.
//! * [`your_first_node`], where you learn how to run the said runtime in a node.
//!
//! > By this step, you have already launched a full Pezkuwi-SDK-based blockchain!
//!
//! Once done, feel free to step up into one of our templates: [`crate::pezkuwi_sdk::templates`].
//!
//! [`your_first_pallet`]: crate::guides::your_first_pallet
//! [`your_first_runtime`]: crate::guides::your_first_runtime
//! [`your_first_node`]: crate::guides::your_first_node
//!
//! Other guides are related to other miscellaneous topics and are listed as modules below.
/// Write your first simple pezpallet, learning the most most basic features of FRAME along the way.
pub mod your_first_pallet;
/// Write your first real [runtime](`crate::reference_docs::wasm_meta_protocol`),
/// compiling it to [WASM](crate::pezkuwi_sdk::bizinikiwi#wasm-build).
pub mod your_first_runtime;
/// Running the given runtime with a node. No specific consensus mechanism is used at this stage.
pub mod your_first_node;
/// How to enhance a given runtime and node to be pezcumulus-enabled, run it as a teyrchain
/// and connect it to a relay-chain.
// pub mod your_first_teyrchain;
/// How to enable storage weight reclaiming in a teyrchain node and runtime.
pub mod enable_pov_reclaim;
/// How to enable Async Backing on teyrchain projects that started in 2023 or before.
pub mod async_backing_guide;
/// How to enable metadata hash verification in the runtime.
pub mod enable_metadata_hash;
/// How to enable elastic scaling on a teyrchain.
pub mod enable_elastic_scaling;
/// How to parameterize teyrchain forking in relation to relay chain forking.
pub mod handling_teyrchain_forks;
@@ -1 +0,0 @@
//! # Pezcumulus Enabled Teyrchain
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More