Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dd58fe9164 | |||
| fe3c65a706 | |||
| ed07878646 | |||
| 7dddae633a | |||
| a92d61db8a | |||
| 6e55418703 | |||
| 56b442fdff | |||
| 35d7ab38fa | |||
| 070d682759 | |||
| cd56ab8fb6 | |||
| b012fcaaac | |||
| 7a1d3e7917 | |||
| 2ee3caac0d | |||
| 78e93e9766 | |||
| 83d66feacc | |||
| d6ace14e70 | |||
| 2cbfd21539 | |||
| f7c070e45b | |||
| 06ed9734c6 | |||
| d93d4c6cd0 | |||
| faba2dee5d | |||
| ca3976fe62 | |||
| 7fea37eb5d | |||
| 68379dcf3a | |||
| 56f276af1b | |||
| f024d21cf5 | |||
| 67bc28cff4 | |||
| d7fa9dd570 | |||
| 428b058cbc | |||
| 0b5e318538 | |||
| 568507ab98 | |||
| 198f53b96f | |||
| 9babb94e07 | |||
| ef6a7b2583 | |||
| d446d711ba | |||
| d1af76f444 | |||
| 914d914b74 | |||
| 8f57224700 | |||
| 1e047eba91 | |||
| 14d6da24db | |||
| 346a30fcbb | |||
| bac4148020 | |||
| 709d408983 | |||
| 18d41743e8 | |||
| 0d71433cc1 |
@@ -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/
|
||||
@@ -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
|
||||
@@ -6,14 +6,25 @@ on:
|
||||
pull_request:
|
||||
branches: [ main, develop ]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
rollback_to:
|
||||
description: 'Rollback to git SHA (skips build, redeploys old image). Empty = normal deploy.'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: write # version bump commit
|
||||
packages: write # GHCR push
|
||||
|
||||
env:
|
||||
VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL }}
|
||||
VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }}
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: pezkuwichain/pwap-web
|
||||
|
||||
jobs:
|
||||
# ========================================
|
||||
@@ -21,7 +32,7 @@ jobs:
|
||||
# ========================================
|
||||
web:
|
||||
name: Web App
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: pwap-runner
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -75,13 +86,164 @@ jobs:
|
||||
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:
|
||||
name: Deploy Web
|
||||
runs-on: ubuntu-latest
|
||||
build-image:
|
||||
name: Build & Push Image
|
||||
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]
|
||||
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:
|
||||
- name: Checkout code
|
||||
@@ -101,61 +263,416 @@ jobs:
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Bump version
|
||||
id: bump
|
||||
working-directory: ./web
|
||||
run: |
|
||||
npm version patch --no-git-tag-version
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
echo "NEW_VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
cd ..
|
||||
git add web/package.json
|
||||
git commit -m "chore(web): bump version to $VERSION [skip ci]" || echo "No version change"
|
||||
git push || echo "Nothing to push"
|
||||
|
||||
- name: Download build artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: web-dist
|
||||
path: dist/
|
||||
# ========================================
|
||||
# DEPLOY TO app.pezkuwichain.io (DEV VPS)
|
||||
# Pulls SHA-tagged image from GHCR, extracts /dist, scp to VPS.
|
||||
# Health check + auto-rollback to .deploy-tag-prev on failure.
|
||||
# ========================================
|
||||
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
|
||||
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: Post-deploy notification
|
||||
- name: Health check (60s window)
|
||||
id: healthcheck
|
||||
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)
|
||||
# npm audit (high + critical) + TruffleHog secret scan
|
||||
# ========================================
|
||||
security-audit:
|
||||
name: Security Audit
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: pwap-runner
|
||||
needs: [web]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: ${{ github.event_name == 'pull_request' && 0 || 1 }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Web - npm audit (critical only)
|
||||
- name: Web — npm audit (high + critical, production deps only)
|
||||
working-directory: ./web
|
||||
run: |
|
||||
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
|
||||
with:
|
||||
path: ./
|
||||
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')
|
||||
"
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# Internal resources (never commit)
|
||||
res/
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
@@ -159,3 +162,11 @@ mobile/credentials.json
|
||||
|
||||
# Backup files
|
||||
*.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
|
||||
|
||||
@@ -26,7 +26,7 @@ pwap/
|
||||
|
||||
**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:**
|
||||
- React 18 + TypeScript
|
||||
@@ -166,9 +166,10 @@ RTL support for CKB, AR, FA.
|
||||
|
||||
## Links
|
||||
|
||||
- **Website:** https://pezkuwichain.app
|
||||
- **SDK UI:** https://pezkuwichain.app/sdk
|
||||
- **Documentation:** https://docs.pezkuwichain.app
|
||||
- **Website:** https://app.pezkuwichain.io
|
||||
- **Website (alt):** https://pex.mom
|
||||
- **Exchange:** https://pex.network
|
||||
- **Documentation:** https://docs.pezkuwichain.io
|
||||
|
||||
## License
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 677 KiB |
@@ -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
|
||||
|
After Width: | Height: | Size: 2.1 MiB |
@@ -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})` };
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
import type { ApiPromise } from '@pezkuwi/api';
|
||||
import type { SubmittableExtrinsic } from '@pezkuwi/api/types';
|
||||
import { Tiki } from './tiki';
|
||||
import { encodeAddress, sortAddresses } from '@pezkuwi/util-crypto';
|
||||
import { encodeMultiAddress, sortAddresses } from '@pezkuwi/util-crypto';
|
||||
|
||||
// ========================================
|
||||
// MULTISIG CONFIGURATION
|
||||
@@ -30,6 +30,21 @@ export const USDT_MULTISIG_CONFIG = {
|
||||
] 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
|
||||
// ========================================
|
||||
@@ -87,21 +102,15 @@ export function calculateMultisigAddress(
|
||||
threshold: number = USDT_MULTISIG_CONFIG.threshold,
|
||||
ss58Format: number = 42
|
||||
): string {
|
||||
// Sort members (multisig requires sorted order)
|
||||
const sortedMembers = sortAddresses(members);
|
||||
|
||||
// Create multisig address
|
||||
// Formula: blake2(b"modlpy/utilisuba" + concat(sorted_members) + threshold)
|
||||
const multisigId = encodeAddress(
|
||||
new Uint8Array([
|
||||
...Buffer.from('modlpy/utilisuba'),
|
||||
...sortedMembers.flatMap((addr) => Array.from(Buffer.from(addr, 'hex'))),
|
||||
threshold,
|
||||
]),
|
||||
ss58Format
|
||||
);
|
||||
|
||||
return multisigId;
|
||||
// Delegates to @pezkuwi/util-crypto's own createKeyMulti/encodeMultiAddress, which correctly
|
||||
// implements pallet_multisig's derivation (blake2_256 of
|
||||
// SCALE-encode(b"modlpy/utilisuba" ++ compact-len ++ sorted pubkeys ++ u16 threshold)). A
|
||||
// previous hand-rolled version here decoded each address with `Buffer.from(addr, 'hex')` -
|
||||
// treating an SS58 string as hex, which is simply wrong - and never hashed the preimage at
|
||||
// all, so it silently produced an address with no relationship to the real multisig account.
|
||||
// Caught by directly testing this function against the known real 5 bridge signatories,
|
||||
// which threw immediately instead of quietly returning a bogus address.
|
||||
return encodeMultiAddress(members, threshold, ss58Format);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// Handles wUSDT minting, burning, and reserve management
|
||||
|
||||
import type { ApiPromise } from '@pezkuwi/api';
|
||||
import { encodeAddress } from '@pezkuwi/util-crypto';
|
||||
import { ASSET_IDS, ASSET_CONFIGS } from './wallet';
|
||||
import { getMultisigMembers, createMultisigTx } from './multisig';
|
||||
|
||||
@@ -14,6 +15,27 @@ import { getMultisigMembers, createMultisigTx } from './multisig';
|
||||
export const WUSDT_ASSET_ID = ASSET_CONFIGS.WUSDT.id;
|
||||
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. */
|
||||
export const STANDARD_RENEWAL_TOPUP = 10_000_000_000n; // 10,000 wUSDT
|
||||
|
||||
// Withdrawal limits and timeouts
|
||||
export const WITHDRAWAL_LIMITS = {
|
||||
instant: {
|
||||
@@ -162,6 +184,36 @@ export async function createBurnWUSDTTx(
|
||||
// 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
|
||||
* @param amount - Withdrawal amount in USDT
|
||||
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
@@ -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 },
|
||||
];
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}));
|
||||
@@ -42,7 +42,7 @@ VITE_ENABLE_DEMO_MODE=true
|
||||
# 1. Project URL: Copy from "Project URL" section
|
||||
# 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
|
||||
|
||||
# ========================================
|
||||
|
||||
@@ -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"]
|
||||
@@ -120,7 +120,8 @@
|
||||
"@pezkuwi/x-textdecoder": "^14.0.25",
|
||||
"@pezkuwi/x-textencoder": "^14.0.25",
|
||||
"@pezkuwi/x-ws": "^14.0.25",
|
||||
"@pezkuwi/networks": "^14.0.25"
|
||||
"@pezkuwi/networks": "^14.0.25",
|
||||
"elliptic": "^6.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.0",
|
||||
@@ -147,6 +148,7 @@
|
||||
"typescript-eslint": "^8.0.1",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-node-polyfills": "^0.25.0",
|
||||
"vite-plugin-subresource-integrity": "^0.0.12",
|
||||
"vitest": "^4.0.10"
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 502 KiB |
@@ -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 |
@@ -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 |
@@ -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"
|
||||
}
|
||||
@@ -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-*
|
||||
@@ -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.
|
||||
@@ -1,2 +0,0 @@
|
||||
# docs.pezkuwichain.io
|
||||
A sovereign blockchain parachain built for the Kurdish Nation and Culturel Nations of the world, on blockchain
|
||||
|
Before Width: | Height: | Size: 634 KiB |
|
Before Width: | Height: | Size: 5.0 MiB |
|
Before Width: | Height: | Size: 5.0 MiB |
|
Before Width: | Height: | Size: 601 KiB |
|
Before Width: | Height: | Size: 208 KiB |
|
Before Width: | Height: | Size: 208 KiB |
|
Before Width: | Height: | Size: 355 KiB |
|
Before Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 586 KiB |
|
Before Width: | Height: | Size: 742 KiB |
|
Before Width: | Height: | Size: 750 KiB |
|
Before Width: | Height: | Size: 269 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 265 KiB |
|
Before Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 429 KiB |
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
flowchart LR
|
||||
B[Block] --> STF
|
||||
S[State] --> STF
|
||||
STF --> NS[New State]
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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 teyrchain’s 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 teyrchain’s 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 isn’t 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`.
|
||||
@@ -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
|
||||