Compare commits
48 Commits
18d41743e8
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 0651b80eb7 | |||
| 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 | |||
| 69789548e7 | |||
| 86ff43e206 | |||
| 09da6e80b7 | |||
| d3362173df |
@@ -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:
|
pull_request:
|
||||||
branches: [ main, develop ]
|
branches: [ main, develop ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
rollback_to:
|
||||||
|
description: 'Rollback to git SHA (skips build, redeploys old image). Empty = normal deploy.'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write # version bump commit
|
||||||
|
packages: write # GHCR push
|
||||||
|
|
||||||
env:
|
env:
|
||||||
VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL }}
|
VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL }}
|
||||||
VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }}
|
VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }}
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: pezkuwichain/pwap-web
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# ========================================
|
# ========================================
|
||||||
@@ -21,7 +32,7 @@ jobs:
|
|||||||
# ========================================
|
# ========================================
|
||||||
web:
|
web:
|
||||||
name: Web App
|
name: Web App
|
||||||
runs-on: ubuntu-latest
|
runs-on: pwap-runner
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -75,13 +86,164 @@ jobs:
|
|||||||
path: web/dist/
|
path: web/dist/
|
||||||
|
|
||||||
# ========================================
|
# ========================================
|
||||||
# DEPLOY WEB APP TO VPS
|
# BUILD & PUSH DOCKER IMAGE TO GHCR
|
||||||
|
# Immutable artifact for audit + rollback (vs ephemeral GHA artifact).
|
||||||
|
# Tagged with git SHA so any commit can be redeployed by SHA.
|
||||||
# ========================================
|
# ========================================
|
||||||
deploy:
|
build-image:
|
||||||
name: Deploy Web
|
name: Build & Push Image
|
||||||
runs-on: ubuntu-latest
|
runs-on: pwap-runner
|
||||||
|
needs: [web, telegram-gate]
|
||||||
|
if: |
|
||||||
|
github.ref == 'refs/heads/main' &&
|
||||||
|
(github.event_name == 'push' ||
|
||||||
|
(github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_to == ''))
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
id-token: write # cosign keyless signing via Sigstore OIDC
|
||||||
|
outputs:
|
||||||
|
image_sha: ${{ steps.meta.outputs.image_sha }}
|
||||||
|
image_digest: ${{ steps.build.outputs.digest }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Install cosign
|
||||||
|
uses: sigstore/cosign-installer@v3
|
||||||
|
with:
|
||||||
|
cosign-release: 'v2.4.1'
|
||||||
|
|
||||||
|
- name: Log in to GHCR
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract image metadata
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
SHORT_SHA="${GITHUB_SHA:0:7}"
|
||||||
|
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||||
|
echo "image_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||||
|
echo "image=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
id: build
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: ./
|
||||||
|
file: ./web/Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ steps.meta.outputs.image }}:${{ steps.meta.outputs.short_sha }}
|
||||||
|
${{ steps.meta.outputs.image }}:latest
|
||||||
|
build-args: |
|
||||||
|
VITE_NETWORK=MAINNET
|
||||||
|
VITE_WS_ENDPOINT=wss://rpc.pezkuwichain.io
|
||||||
|
VITE_WS_ENDPOINT_FALLBACK_1=wss://mainnet.pezkuwichain.io
|
||||||
|
VITE_ASSET_HUB_ENDPOINT=wss://asset-hub-rpc.pezkuwichain.io
|
||||||
|
VITE_PEOPLE_CHAIN_ENDPOINT=wss://people-rpc.pezkuwichain.io
|
||||||
|
VITE_WALLETCONNECT_PROJECT_ID=8292a793b7640e8364c378e331e76d04
|
||||||
|
VITE_SUPABASE_URL=${{ secrets.VITE_SUPABASE_URL }}
|
||||||
|
VITE_SUPABASE_ANON_KEY=${{ secrets.VITE_SUPABASE_ANON_KEY }}
|
||||||
|
cache-from: type=registry,ref=${{ steps.meta.outputs.image }}:cache
|
||||||
|
cache-to: type=registry,ref=${{ steps.meta.outputs.image }}:cache,mode=max
|
||||||
|
provenance: false
|
||||||
|
|
||||||
|
- name: Sign image with cosign (keyless, Sigstore Fulcio)
|
||||||
|
env:
|
||||||
|
COSIGN_EXPERIMENTAL: '1'
|
||||||
|
run: |
|
||||||
|
IMAGE_DIGEST="${{ steps.meta.outputs.image }}@${{ steps.build.outputs.digest }}"
|
||||||
|
# cosign needs its own registry auth — docker/login-action only writes
|
||||||
|
# ~/.docker/config.json which cosign on self-hosted runner can't read
|
||||||
|
echo "${{ secrets.GITHUB_TOKEN }}" | cosign login ghcr.io -u "${{ github.actor }}" --password-stdin
|
||||||
|
echo "Signing $IMAGE_DIGEST"
|
||||||
|
cosign sign --yes "$IMAGE_DIGEST"
|
||||||
|
echo "✅ Image signed (transparency log: rekor.sigstore.dev)"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# TELEGRAM CEO APPROVAL GATE
|
||||||
|
# Runs on self-hosted pwap-runner (DEV VPS) where pexsec-bot.service
|
||||||
|
# writes the gate file to /tmp/pexsec-gates/<sha> when CEO clicks
|
||||||
|
# Approve/Cancel in Telegram. 30-minute timeout = deploy cancelled.
|
||||||
|
# ========================================
|
||||||
|
telegram-gate:
|
||||||
|
name: Telegram deploy approval
|
||||||
|
runs-on: pwap-runner
|
||||||
needs: [web, security-audit]
|
needs: [web, security-audit]
|
||||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Send approval request and wait
|
||||||
|
env:
|
||||||
|
BOT_TOKEN: ${{ secrets.PEXSEC_BOT_TOKEN }}
|
||||||
|
CEO_CHAT_ID: ${{ secrets.TELEGRAM_CEO_CHAT_ID }}
|
||||||
|
SHA: ${{ github.sha }}
|
||||||
|
ACTOR: ${{ github.actor }}
|
||||||
|
MESSAGE: ${{ github.event.head_commit.message }}
|
||||||
|
run: |
|
||||||
|
SHORT="${SHA:0:7}"
|
||||||
|
GATE_DIR="/tmp/pexsec-gates"
|
||||||
|
mkdir -p "$GATE_DIR" 2>/dev/null || true
|
||||||
|
rm -f "$GATE_DIR/$SHORT" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Strip Markdown special chars to prevent Telegram parse errors
|
||||||
|
SAFE_MSG=$(echo "${MESSAGE}" | head -1 | tr -d '_*`[]()#|{}!' | cut -c1-120)
|
||||||
|
|
||||||
|
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"chat_id\": \"${CEO_CHAT_ID}\",
|
||||||
|
\"parse_mode\": \"Markdown\",
|
||||||
|
\"text\": \"🚀 *pwap/web Deploy Approval*\\n\\n\`${SHORT}\` — ${ACTOR}\\n\\n_${SAFE_MSG}_\\n\\nTargets: app.pezkuwichain.io + pex.mom\",
|
||||||
|
\"reply_markup\": {
|
||||||
|
\"inline_keyboard\": [[
|
||||||
|
{\"text\": \"✅ Approve\", \"callback_data\": \"deploy_approve:${SHORT}\"},
|
||||||
|
{\"text\": \"❌ Cancel\", \"callback_data\": \"deploy_cancel:${SHORT}\"}
|
||||||
|
]]
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
|
||||||
|
echo "Waiting for Telegram approval (max 30 min)..."
|
||||||
|
TIMEOUT=1800
|
||||||
|
ELAPSED=0
|
||||||
|
while [ $ELAPSED -lt $TIMEOUT ]; do
|
||||||
|
if [ -f "$GATE_DIR/$SHORT" ]; then
|
||||||
|
DECISION=$(cat "$GATE_DIR/$SHORT")
|
||||||
|
rm -f "$GATE_DIR/$SHORT" 2>/dev/null || true
|
||||||
|
if [ "$DECISION" = "approved" ]; then
|
||||||
|
echo "Deploy approved."
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "Deploy cancelled."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
sleep 10
|
||||||
|
ELAPSED=$((ELAPSED + 10))
|
||||||
|
done
|
||||||
|
echo "No approval received within 30 minutes — deploy cancelled."
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# VERSION BUMP (RUNS BEFORE BOTH DEPLOYS)
|
||||||
|
# ========================================
|
||||||
|
bump-version:
|
||||||
|
name: Bump Version
|
||||||
|
runs-on: pwap-runner
|
||||||
|
needs: [web, security-audit, telegram-gate, build-image]
|
||||||
|
# Skip on rollback (workflow_dispatch with rollback_to set)
|
||||||
|
if: |
|
||||||
|
github.ref == 'refs/heads/main' &&
|
||||||
|
(github.event_name == 'push' ||
|
||||||
|
(github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_to == ''))
|
||||||
|
outputs:
|
||||||
|
new_version: ${{ steps.bump.outputs.version }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -101,61 +263,416 @@ jobs:
|
|||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
|
||||||
- name: Bump version
|
- name: Bump version
|
||||||
|
id: bump
|
||||||
working-directory: ./web
|
working-directory: ./web
|
||||||
run: |
|
run: |
|
||||||
npm version patch --no-git-tag-version
|
npm version patch --no-git-tag-version
|
||||||
VERSION=$(node -p "require('./package.json').version")
|
VERSION=$(node -p "require('./package.json').version")
|
||||||
echo "NEW_VERSION=$VERSION" >> $GITHUB_ENV
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
cd ..
|
cd ..
|
||||||
git add web/package.json
|
git add web/package.json
|
||||||
git commit -m "chore(web): bump version to $VERSION [skip ci]" || echo "No version change"
|
git commit -m "chore(web): bump version to $VERSION [skip ci]" || echo "No version change"
|
||||||
git push || echo "Nothing to push"
|
git push || echo "Nothing to push"
|
||||||
|
|
||||||
- name: Download build artifact
|
# ========================================
|
||||||
uses: actions/download-artifact@v4
|
# DEPLOY TO app.pezkuwichain.io (DEV VPS)
|
||||||
with:
|
# Pulls SHA-tagged image from GHCR, extracts /dist, scp to VPS.
|
||||||
name: web-dist
|
# Health check + auto-rollback to .deploy-tag-prev on failure.
|
||||||
path: dist/
|
# ========================================
|
||||||
|
deploy-app:
|
||||||
|
name: Deploy app.pezkuwichain.io
|
||||||
|
runs-on: pwap-runner
|
||||||
|
needs: [telegram-gate, bump-version, build-image]
|
||||||
|
if: |
|
||||||
|
always() &&
|
||||||
|
needs.telegram-gate.result == 'success' &&
|
||||||
|
((github.event_name == 'push' && needs.build-image.result == 'success' && needs.bump-version.result == 'success') ||
|
||||||
|
(github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_to != ''))
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: read
|
||||||
|
env:
|
||||||
|
DOMAIN: app.pezkuwichain.io
|
||||||
|
TARGET_PATH: /var/www/subdomains/app
|
||||||
|
|
||||||
- name: Deploy to VPS
|
steps:
|
||||||
|
- name: Determine image SHA
|
||||||
|
id: sha
|
||||||
|
run: |
|
||||||
|
if [ -n "${{ github.event.inputs.rollback_to }}" ]; then
|
||||||
|
echo "sha=${{ github.event.inputs.rollback_to }}" >> $GITHUB_OUTPUT
|
||||||
|
echo "Rolling back to: ${{ github.event.inputs.rollback_to }}"
|
||||||
|
else
|
||||||
|
echo "sha=${{ needs.build-image.outputs.image_sha }}" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Capture currently-live SHA (for auto-rollback)
|
||||||
|
id: prev
|
||||||
|
run: |
|
||||||
|
# /.deploy-sha is written into every deploy; read what's live now
|
||||||
|
PREV=$(curl -sf --max-time 5 "https://${{ env.DOMAIN }}/.deploy-sha" | head -c 40 | tr -dc 'a-f0-9' || echo "")
|
||||||
|
echo "Previous live SHA: ${PREV:-unknown}"
|
||||||
|
echo "prev=$PREV" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Log in to GHCR
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Install cosign (for verify)
|
||||||
|
uses: sigstore/cosign-installer@v3
|
||||||
|
with:
|
||||||
|
cosign-release: 'v2.4.1'
|
||||||
|
|
||||||
|
- name: Verify image signature (cosign keyless)
|
||||||
|
env:
|
||||||
|
COSIGN_EXPERIMENTAL: '1'
|
||||||
|
run: |
|
||||||
|
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.sha }}"
|
||||||
|
echo "${{ secrets.GITHUB_TOKEN }}" | cosign login ghcr.io -u "${{ github.actor }}" --password-stdin
|
||||||
|
echo "Verifying signature for $IMAGE"
|
||||||
|
cosign verify "$IMAGE" \
|
||||||
|
--certificate-identity-regexp "^https://github.com/pezkuwichain/pwap/.github/workflows/quality-gate.yml@" \
|
||||||
|
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
|
||||||
|
> /dev/null
|
||||||
|
echo "✅ Signature valid — image was built by trusted CI"
|
||||||
|
|
||||||
|
- name: Extract /dist from image
|
||||||
|
run: |
|
||||||
|
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.sha }}"
|
||||||
|
docker pull "$IMAGE"
|
||||||
|
CID=$(docker create "$IMAGE")
|
||||||
|
mkdir -p dist
|
||||||
|
docker cp "$CID:/dist/." dist/
|
||||||
|
docker rm "$CID" >/dev/null
|
||||||
|
# Stamp this build's SHA into dist so future deploys can read PREV
|
||||||
|
echo "${{ steps.sha.outputs.sha }}" > dist/.deploy-sha
|
||||||
|
ls -la dist/ | head -10
|
||||||
|
|
||||||
|
- name: Deploy to DEV VPS
|
||||||
uses: appleboy/scp-action@v1.0.0
|
uses: appleboy/scp-action@v1.0.0
|
||||||
with:
|
with:
|
||||||
host: ${{ secrets.VPS_HOST }}
|
host: ${{ secrets.VPS_HOST }}
|
||||||
username: ${{ secrets.VPS_USER }}
|
username: ${{ secrets.VPS_USER }}
|
||||||
key: ${{ secrets.VPS_SSH_KEY }}
|
key: ${{ secrets.VPS_SSH_KEY }}
|
||||||
|
port: ${{ secrets.VPS_SSH_PORT || 2222 }}
|
||||||
source: 'dist/*'
|
source: 'dist/*'
|
||||||
target: '/var/www/subdomains/app'
|
target: '/var/www/subdomains/app'
|
||||||
strip_components: 1
|
strip_components: 1
|
||||||
|
|
||||||
- name: Post-deploy notification
|
- name: Health check (60s window)
|
||||||
|
id: healthcheck
|
||||||
run: |
|
run: |
|
||||||
echo "✅ Deployed web app v${{ env.NEW_VERSION }} to app.pezkuwichain.io"
|
for i in 1 2 3 4 5 6; do
|
||||||
|
if curl -sf --max-time 10 "https://${{ env.DOMAIN }}/" >/dev/null; then
|
||||||
|
echo "✅ ${{ env.DOMAIN }} healthy"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Attempt $i/6 failed, retrying in 10s..."
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
echo "❌ Health check failed for ${{ env.DOMAIN }}"
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
# ── Automatic rollback: pull PREV SHA image, redeploy, recheck ──
|
||||||
|
- name: Auto-rollback to previous SHA
|
||||||
|
id: rollback
|
||||||
|
if: failure() && steps.healthcheck.conclusion == 'failure' && steps.prev.outputs.prev != ''
|
||||||
|
run: |
|
||||||
|
PREV="${{ steps.prev.outputs.prev }}"
|
||||||
|
echo "🔄 Rolling back ${{ env.DOMAIN }} to $PREV"
|
||||||
|
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$PREV"
|
||||||
|
docker pull "$IMAGE"
|
||||||
|
CID=$(docker create "$IMAGE")
|
||||||
|
rm -rf dist && mkdir dist
|
||||||
|
docker cp "$CID:/dist/." dist/
|
||||||
|
docker rm "$CID" >/dev/null
|
||||||
|
echo "$PREV" > dist/.deploy-sha
|
||||||
|
echo "rollback_sha=$PREV" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: SCP rollback artifact
|
||||||
|
if: steps.rollback.outcome == 'success'
|
||||||
|
uses: appleboy/scp-action@v1.0.0
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.VPS_HOST }}
|
||||||
|
username: ${{ secrets.VPS_USER }}
|
||||||
|
key: ${{ secrets.VPS_SSH_KEY }}
|
||||||
|
port: ${{ secrets.VPS_SSH_PORT || 2222 }}
|
||||||
|
source: 'dist/*'
|
||||||
|
target: '/var/www/subdomains/app'
|
||||||
|
strip_components: 1
|
||||||
|
|
||||||
|
- name: Re-health-check after rollback
|
||||||
|
if: steps.rollback.outcome == 'success'
|
||||||
|
id: healthcheck_rb
|
||||||
|
run: |
|
||||||
|
for i in 1 2 3 4 5 6; do
|
||||||
|
if curl -sf --max-time 10 "https://${{ env.DOMAIN }}/" >/dev/null; then
|
||||||
|
echo "✅ Rolled back successfully — ${{ env.DOMAIN }} healthy on ${{ steps.rollback.outputs.rollback_sha }}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
echo "❌ Rollback also failed!"
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Post-deploy notification
|
||||||
|
if: success()
|
||||||
|
run: |
|
||||||
|
echo "✅ Deployed image ${{ steps.sha.outputs.sha }} to ${{ env.DOMAIN }}"
|
||||||
|
|
||||||
|
- name: Notify failure (Telegram)
|
||||||
|
if: failure()
|
||||||
|
env:
|
||||||
|
BOT_TOKEN: ${{ secrets.PEXSEC_BOT_TOKEN }}
|
||||||
|
CEO_CHAT_ID: ${{ secrets.TELEGRAM_CEO_CHAT_ID }}
|
||||||
|
NEW_SHA: ${{ steps.sha.outputs.sha }}
|
||||||
|
PREV_SHA: ${{ steps.prev.outputs.prev }}
|
||||||
|
ROLLBACK_OUTCOME: ${{ steps.rollback.outcome }}
|
||||||
|
RECHECK_OUTCOME: ${{ steps.healthcheck_rb.outcome }}
|
||||||
|
run: |
|
||||||
|
if [ "$RECHECK_OUTCOME" = "success" ]; then
|
||||||
|
MSG="⚠️ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed health check, AUTO-ROLLED-BACK to $PREV_SHA. Site healthy."
|
||||||
|
elif [ "$ROLLBACK_OUTCOME" = "success" ]; then
|
||||||
|
MSG="🚨 pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed AND rollback to $PREV_SHA also failed. Manual intervention needed."
|
||||||
|
elif [ -z "$PREV_SHA" ]; then
|
||||||
|
MSG="❌ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed. No previous SHA available (first deploy?). Manual rollback: gh workflow run quality-gate.yml -f rollback_to=<sha>"
|
||||||
|
else
|
||||||
|
MSG="❌ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed. Auto-rollback was not attempted. Manual: gh workflow run quality-gate.yml -f rollback_to=$PREV_SHA"
|
||||||
|
fi
|
||||||
|
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
|
||||||
|
-d "chat_id=${CEO_CHAT_ID}" --data-urlencode "text=$MSG"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# DEPLOY TO pex.mom (VPS3 — geo-redundant mirror)
|
||||||
|
# ========================================
|
||||||
|
deploy-pex:
|
||||||
|
name: Deploy pex.mom
|
||||||
|
runs-on: pwap-runner
|
||||||
|
needs: [telegram-gate, bump-version, build-image]
|
||||||
|
if: |
|
||||||
|
always() &&
|
||||||
|
needs.telegram-gate.result == 'success' &&
|
||||||
|
((github.event_name == 'push' && needs.build-image.result == 'success' && needs.bump-version.result == 'success') ||
|
||||||
|
(github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_to != ''))
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: read
|
||||||
|
env:
|
||||||
|
DOMAIN: pex.mom
|
||||||
|
TARGET_PATH: /var/www/pex.mom
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Determine image SHA
|
||||||
|
id: sha
|
||||||
|
run: |
|
||||||
|
if [ -n "${{ github.event.inputs.rollback_to }}" ]; then
|
||||||
|
echo "sha=${{ github.event.inputs.rollback_to }}" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "sha=${{ needs.build-image.outputs.image_sha }}" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Capture currently-live SHA (for auto-rollback)
|
||||||
|
id: prev
|
||||||
|
run: |
|
||||||
|
PREV=$(curl -sf --max-time 5 "https://${{ env.DOMAIN }}/.deploy-sha" | head -c 40 | tr -dc 'a-f0-9' || echo "")
|
||||||
|
echo "Previous live SHA: ${PREV:-unknown}"
|
||||||
|
echo "prev=$PREV" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Log in to GHCR
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Install cosign (for verify)
|
||||||
|
uses: sigstore/cosign-installer@v3
|
||||||
|
with:
|
||||||
|
cosign-release: 'v2.4.1'
|
||||||
|
|
||||||
|
- name: Verify image signature (cosign keyless)
|
||||||
|
env:
|
||||||
|
COSIGN_EXPERIMENTAL: '1'
|
||||||
|
run: |
|
||||||
|
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.sha }}"
|
||||||
|
echo "Verifying signature for $IMAGE"
|
||||||
|
cosign verify "$IMAGE" \
|
||||||
|
--certificate-identity-regexp "^https://github.com/pezkuwichain/pwap/.github/workflows/quality-gate.yml@" \
|
||||||
|
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
|
||||||
|
> /dev/null
|
||||||
|
echo "✅ Signature valid — image was built by trusted CI"
|
||||||
|
|
||||||
|
- name: Extract /dist from image
|
||||||
|
run: |
|
||||||
|
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.sha }}"
|
||||||
|
docker pull "$IMAGE"
|
||||||
|
CID=$(docker create "$IMAGE")
|
||||||
|
mkdir -p dist
|
||||||
|
docker cp "$CID:/dist/." dist/
|
||||||
|
docker rm "$CID" >/dev/null
|
||||||
|
echo "${{ steps.sha.outputs.sha }}" > dist/.deploy-sha
|
||||||
|
|
||||||
|
- name: Deploy to VPS3
|
||||||
|
uses: appleboy/scp-action@v1.0.0
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.VPS_PEX_HOST }}
|
||||||
|
username: ${{ secrets.VPS_PEX_USER }}
|
||||||
|
key: ${{ secrets.VPS_PEX_SSH_KEY }}
|
||||||
|
port: ${{ secrets.VPS_PEX_SSH_PORT || 22 }}
|
||||||
|
source: 'dist/*'
|
||||||
|
target: '/var/www/pex.mom'
|
||||||
|
strip_components: 1
|
||||||
|
|
||||||
|
- name: Health check (60s window)
|
||||||
|
id: healthcheck
|
||||||
|
run: |
|
||||||
|
for i in 1 2 3 4 5 6; do
|
||||||
|
if curl -sf --max-time 10 "https://${{ env.DOMAIN }}/" >/dev/null; then
|
||||||
|
echo "✅ ${{ env.DOMAIN }} healthy"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Attempt $i/6 failed, retrying in 10s..."
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
echo "❌ Health check failed for ${{ env.DOMAIN }}"
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Auto-rollback to previous SHA
|
||||||
|
id: rollback
|
||||||
|
if: failure() && steps.healthcheck.conclusion == 'failure' && steps.prev.outputs.prev != ''
|
||||||
|
run: |
|
||||||
|
PREV="${{ steps.prev.outputs.prev }}"
|
||||||
|
echo "🔄 Rolling back ${{ env.DOMAIN }} to $PREV"
|
||||||
|
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$PREV"
|
||||||
|
docker pull "$IMAGE"
|
||||||
|
CID=$(docker create "$IMAGE")
|
||||||
|
rm -rf dist && mkdir dist
|
||||||
|
docker cp "$CID:/dist/." dist/
|
||||||
|
docker rm "$CID" >/dev/null
|
||||||
|
echo "$PREV" > dist/.deploy-sha
|
||||||
|
echo "rollback_sha=$PREV" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: SCP rollback artifact
|
||||||
|
if: steps.rollback.outcome == 'success'
|
||||||
|
uses: appleboy/scp-action@v1.0.0
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.VPS_PEX_HOST }}
|
||||||
|
username: ${{ secrets.VPS_PEX_USER }}
|
||||||
|
key: ${{ secrets.VPS_PEX_SSH_KEY }}
|
||||||
|
port: ${{ secrets.VPS_PEX_SSH_PORT || 22 }}
|
||||||
|
source: 'dist/*'
|
||||||
|
target: '/var/www/pex.mom'
|
||||||
|
strip_components: 1
|
||||||
|
|
||||||
|
- name: Re-health-check after rollback
|
||||||
|
if: steps.rollback.outcome == 'success'
|
||||||
|
id: healthcheck_rb
|
||||||
|
run: |
|
||||||
|
for i in 1 2 3 4 5 6; do
|
||||||
|
if curl -sf --max-time 10 "https://${{ env.DOMAIN }}/" >/dev/null; then
|
||||||
|
echo "✅ Rolled back successfully — ${{ env.DOMAIN }} healthy on ${{ steps.rollback.outputs.rollback_sha }}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
echo "❌ Rollback also failed!"
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Post-deploy notification
|
||||||
|
if: success()
|
||||||
|
run: |
|
||||||
|
echo "✅ Deployed image ${{ steps.sha.outputs.sha }} to ${{ env.DOMAIN }}"
|
||||||
|
|
||||||
|
- name: Notify failure (Telegram)
|
||||||
|
if: failure()
|
||||||
|
env:
|
||||||
|
BOT_TOKEN: ${{ secrets.PEXSEC_BOT_TOKEN }}
|
||||||
|
CEO_CHAT_ID: ${{ secrets.TELEGRAM_CEO_CHAT_ID }}
|
||||||
|
NEW_SHA: ${{ steps.sha.outputs.sha }}
|
||||||
|
PREV_SHA: ${{ steps.prev.outputs.prev }}
|
||||||
|
ROLLBACK_OUTCOME: ${{ steps.rollback.outcome }}
|
||||||
|
RECHECK_OUTCOME: ${{ steps.healthcheck_rb.outcome }}
|
||||||
|
run: |
|
||||||
|
if [ "$RECHECK_OUTCOME" = "success" ]; then
|
||||||
|
MSG="⚠️ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed health check, AUTO-ROLLED-BACK to $PREV_SHA. Site healthy."
|
||||||
|
elif [ "$ROLLBACK_OUTCOME" = "success" ]; then
|
||||||
|
MSG="🚨 pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed AND rollback to $PREV_SHA also failed. Manual intervention needed."
|
||||||
|
elif [ -z "$PREV_SHA" ]; then
|
||||||
|
MSG="❌ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed. No previous SHA available (first deploy?). Manual rollback: gh workflow run quality-gate.yml -f rollback_to=<sha>"
|
||||||
|
else
|
||||||
|
MSG="❌ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed. Auto-rollback was not attempted. Manual: gh workflow run quality-gate.yml -f rollback_to=$PREV_SHA"
|
||||||
|
fi
|
||||||
|
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
|
||||||
|
-d "chat_id=${CEO_CHAT_ID}" --data-urlencode "text=$MSG"
|
||||||
|
|
||||||
# ========================================
|
# ========================================
|
||||||
# SECURITY CHECKS (BLOCKING)
|
# SECURITY CHECKS (BLOCKING)
|
||||||
|
# npm audit (high + critical) + TruffleHog secret scan
|
||||||
# ========================================
|
# ========================================
|
||||||
security-audit:
|
security-audit:
|
||||||
name: Security Audit
|
name: Security Audit
|
||||||
runs-on: ubuntu-latest
|
runs-on: pwap-runner
|
||||||
needs: [web]
|
needs: [web]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: ${{ github.event_name == 'pull_request' && 0 || 1 }}
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: '20'
|
||||||
|
|
||||||
- name: Web - npm audit (critical only)
|
- name: Web — npm audit (high + critical, production deps only)
|
||||||
working-directory: ./web
|
working-directory: ./web
|
||||||
run: |
|
run: |
|
||||||
npm install
|
npm install
|
||||||
npm audit --audit-level=critical
|
# Audit only production dependencies. Build tooling (vite, esbuild,
|
||||||
|
# vite-plugin-node-polyfills → elliptic, etc.) ships to no user, and
|
||||||
|
# advisories on those dev deps kept blocking production deploys.
|
||||||
|
npm audit --audit-level=high --omit=dev
|
||||||
|
|
||||||
- name: TruffleHog Secret Scan
|
- name: TruffleHog — PR diff (verified secrets only)
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
uses: trufflesecurity/trufflehog@main
|
||||||
|
with:
|
||||||
|
base: ${{ github.event.pull_request.base.sha }}
|
||||||
|
head: ${{ github.event.pull_request.head.sha }}
|
||||||
|
extra_args: --only-verified
|
||||||
|
|
||||||
|
- name: TruffleHog — full repo scan (verified secrets only)
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
uses: trufflesecurity/trufflehog@main
|
uses: trufflesecurity/trufflehog@main
|
||||||
with:
|
with:
|
||||||
path: ./
|
path: ./
|
||||||
extra_args: --only-verified
|
extra_args: --only-verified
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# CI GATE — explicit merge-block
|
||||||
|
# All required checks must succeed (or be skipped, e.g. for rollback path).
|
||||||
|
# Branch protection on main should require this job's success.
|
||||||
|
# ========================================
|
||||||
|
ci-gate:
|
||||||
|
name: CI Gate ✅
|
||||||
|
runs-on: pwap-runner
|
||||||
|
needs: [web, security-audit]
|
||||||
|
if: always()
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Verify all required jobs succeeded or were intentionally skipped
|
||||||
|
run: |
|
||||||
|
results='${{ toJSON(needs) }}'
|
||||||
|
echo "$results" | python3 -c "
|
||||||
|
import json, sys
|
||||||
|
needs = json.load(sys.stdin)
|
||||||
|
failed = [name for name, job in needs.items() if job['result'] not in ('success', 'skipped')]
|
||||||
|
if failed:
|
||||||
|
print('❌ Required jobs failed: ' + ', '.join(failed))
|
||||||
|
sys.exit(1)
|
||||||
|
print('✅ All required CI jobs passed or skipped')
|
||||||
|
"
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
# Internal resources (never commit)
|
||||||
|
res/
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
@@ -159,3 +162,11 @@ mobile/credentials.json
|
|||||||
|
|
||||||
# Backup files
|
# Backup files
|
||||||
*.bak
|
*.bak
|
||||||
|
|
||||||
|
# Generated docs artifacts — the in-app /docs route now redirects to the
|
||||||
|
# external docs.pezkuwichain.io (see web/src/pages/Docs.tsx, since c56e021a).
|
||||||
|
# These are (re)built from Pezkuwi-SDK by web/generate-docs-structure.cjs on
|
||||||
|
# pre(dev|build); they are NOT served at runtime, so we don't track them.
|
||||||
|
/web/public/docs/
|
||||||
|
/web/public/sdk_docs/
|
||||||
|
/web/public/docs-structure.json
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ pwap/
|
|||||||
|
|
||||||
**Status:** ✅ Production Ready
|
**Status:** ✅ Production Ready
|
||||||
|
|
||||||
The primary web interface for Pezkuwi blockchain at [pezkuwichain.app](https://pezkuwichain.app)
|
The primary web interface for Pezkuwi blockchain at [app.pezkuwichain.io](https://app.pezkuwichain.io)
|
||||||
|
|
||||||
**Tech Stack:**
|
**Tech Stack:**
|
||||||
- React 18 + TypeScript
|
- React 18 + TypeScript
|
||||||
@@ -166,9 +166,10 @@ RTL support for CKB, AR, FA.
|
|||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
- **Website:** https://pezkuwichain.app
|
- **Website:** https://app.pezkuwichain.io
|
||||||
- **SDK UI:** https://pezkuwichain.app/sdk
|
- **Website (alt):** https://pex.mom
|
||||||
- **Documentation:** https://docs.pezkuwichain.app
|
- **Exchange:** https://pex.network
|
||||||
|
- **Documentation:** https://docs.pezkuwichain.io
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
|
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 { ApiPromise } from '@pezkuwi/api';
|
||||||
import type { SubmittableExtrinsic } from '@pezkuwi/api/types';
|
import type { SubmittableExtrinsic } from '@pezkuwi/api/types';
|
||||||
import { Tiki } from './tiki';
|
import { Tiki } from './tiki';
|
||||||
import { encodeAddress, sortAddresses } from '@pezkuwi/util-crypto';
|
import { encodeMultiAddress, sortAddresses } from '@pezkuwi/util-crypto';
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// MULTISIG CONFIGURATION
|
// MULTISIG CONFIGURATION
|
||||||
@@ -30,6 +30,21 @@ export const USDT_MULTISIG_CONFIG = {
|
|||||||
] as MultisigMember[],
|
] as MultisigMember[],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Addresses for the two non-unique Tiki roles (Noter, Berdevk are held by all 21 validators,
|
||||||
|
* so which specific validator sits on THIS multisig has to be pinned manually - it can't be
|
||||||
|
* derived from the Tiki on-chain lookup alone). This is the single source of truth: it MUST
|
||||||
|
* match the real 5 signatories the on-chain bridge multisig was actually derived from (see
|
||||||
|
* res/validators-tiki.md "USDT Bridge Custody Multisig" and the usdt-bridge-multisig-migration
|
||||||
|
* memory). Centralized here instead of copy-pasted per-page - a stale copy of these two
|
||||||
|
* addresses previously sat in ReservesDashboardPage.tsx and didn't match any real signer,
|
||||||
|
* which silently made every derived multisig address wrong.
|
||||||
|
*/
|
||||||
|
export const BRIDGE_MULTISIG_SPECIFIC_ADDRESSES: Record<string, string> = {
|
||||||
|
Noter: '5ELgySrX5ZyK7EWXjj6bAedyTCcTNWDANbiiipsT5gnpoCEp', // Validator_04
|
||||||
|
Berdevk: '5HWFZbhkZuTUySXu6ZXYKrTHBnWXHvWRKLozE22zhnwXGGxk', // Validator_02
|
||||||
|
};
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// MULTISIG MEMBER QUERIES
|
// MULTISIG MEMBER QUERIES
|
||||||
// ========================================
|
// ========================================
|
||||||
@@ -87,21 +102,15 @@ export function calculateMultisigAddress(
|
|||||||
threshold: number = USDT_MULTISIG_CONFIG.threshold,
|
threshold: number = USDT_MULTISIG_CONFIG.threshold,
|
||||||
ss58Format: number = 42
|
ss58Format: number = 42
|
||||||
): string {
|
): string {
|
||||||
// Sort members (multisig requires sorted order)
|
// Delegates to @pezkuwi/util-crypto's own createKeyMulti/encodeMultiAddress, which correctly
|
||||||
const sortedMembers = sortAddresses(members);
|
// implements pallet_multisig's derivation (blake2_256 of
|
||||||
|
// SCALE-encode(b"modlpy/utilisuba" ++ compact-len ++ sorted pubkeys ++ u16 threshold)). A
|
||||||
// Create multisig address
|
// previous hand-rolled version here decoded each address with `Buffer.from(addr, 'hex')` -
|
||||||
// Formula: blake2(b"modlpy/utilisuba" + concat(sorted_members) + threshold)
|
// treating an SS58 string as hex, which is simply wrong - and never hashed the preimage at
|
||||||
const multisigId = encodeAddress(
|
// all, so it silently produced an address with no relationship to the real multisig account.
|
||||||
new Uint8Array([
|
// Caught by directly testing this function against the known real 5 bridge signatories,
|
||||||
...Buffer.from('modlpy/utilisuba'),
|
// which threw immediately instead of quietly returning a bogus address.
|
||||||
...sortedMembers.flatMap((addr) => Array.from(Buffer.from(addr, 'hex'))),
|
return encodeMultiAddress(members, threshold, ss58Format);
|
||||||
threshold,
|
|
||||||
]),
|
|
||||||
ss58Format
|
|
||||||
);
|
|
||||||
|
|
||||||
return multisigId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
// Handles wUSDT minting, burning, and reserve management
|
// Handles wUSDT minting, burning, and reserve management
|
||||||
|
|
||||||
import type { ApiPromise } from '@pezkuwi/api';
|
import type { ApiPromise } from '@pezkuwi/api';
|
||||||
|
import { encodeAddress } from '@pezkuwi/util-crypto';
|
||||||
import { ASSET_IDS, ASSET_CONFIGS } from './wallet';
|
import { ASSET_IDS, ASSET_CONFIGS } from './wallet';
|
||||||
import { getMultisigMembers, createMultisigTx } from './multisig';
|
import { getMultisigMembers, createMultisigTx } from './multisig';
|
||||||
|
|
||||||
@@ -14,6 +15,32 @@ import { getMultisigMembers, createMultisigTx } from './multisig';
|
|||||||
export const WUSDT_ASSET_ID = ASSET_CONFIGS.WUSDT.id;
|
export const WUSDT_ASSET_ID = ASSET_CONFIGS.WUSDT.id;
|
||||||
export const WUSDT_DECIMALS = ASSET_CONFIGS.WUSDT.decimals;
|
export const WUSDT_DECIMALS = ASSET_CONFIGS.WUSDT.decimals;
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// BRIDGE CUSTODY (3-of-5 multisig)
|
||||||
|
// ========================================
|
||||||
|
// See res/validators-tiki.md "USDT Bridge Custody Multisig" and the
|
||||||
|
// usdt-bridge-multisig-migration memory for how these were derived/verified on-chain.
|
||||||
|
// Deliberately NOT env-overridable like the constants in wallet.ts - these are fixed,
|
||||||
|
// security-critical addresses, not per-deployment config; an env misconfiguration must
|
||||||
|
// never be able to silently point this UI at the wrong custody account.
|
||||||
|
|
||||||
|
/** The 3-of-5 multisig's own account on Pezkuwi Asset Hub - owns/issues wUSDT (asset 1000). */
|
||||||
|
export const WUSDT_BRIDGE_CUSTODY_PEZKUWI = '5GvwxmCDp3PC33KHoeWSgj3S7ocE7nzk1jiCCZMPSDBFeNcj';
|
||||||
|
|
||||||
|
/** Delegate key the multisig grants a bounded, auto-renewing Assets.approve_transfer allowance
|
||||||
|
* to (see pezkuwi-DKS/tools/usdt-bridge/src/bin/pezbridge_bot.rs) so small/routine deposits
|
||||||
|
* don't need a live 3-of-5 signature every time. */
|
||||||
|
export const WUSDT_AUTOMATION_KEY_ADDRESS = '5GQu4PFUb1f3MTJ7i7c1CtLgDk3TVvpSW1VbQCRmfkMoC8cM';
|
||||||
|
|
||||||
|
/** The standard top-up amount the multisig renews the automation key's allowance to (6
|
||||||
|
* decimals) - used to reconstruct and auto-describe that one deterministic pending call.
|
||||||
|
* MUST match pezbridge_bot_config.json's topup_amount AND the Android wallet's
|
||||||
|
* BridgeMultisigConstants.TOPUP_AMOUNT: all three signing channels build the SAME on-chain
|
||||||
|
* call, so a different amount here computes a different call hash - real renewals would
|
||||||
|
* show as "Unknown call" instead of auto-matching. 10,000 (the bot's old built-in default)
|
||||||
|
* was stale; the live config renews to 200,000 (renewal_threshold 40,000). */
|
||||||
|
export const STANDARD_RENEWAL_TOPUP = 200_000_000_000n; // 200,000 wUSDT
|
||||||
|
|
||||||
// Withdrawal limits and timeouts
|
// Withdrawal limits and timeouts
|
||||||
export const WITHDRAWAL_LIMITS = {
|
export const WITHDRAWAL_LIMITS = {
|
||||||
instant: {
|
instant: {
|
||||||
@@ -162,6 +189,36 @@ export async function createBurnWUSDTTx(
|
|||||||
// WITHDRAWAL HELPERS
|
// WITHDRAWAL HELPERS
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the plain, single-signer transfer a user submits THEMSELVES to initiate a withdrawal:
|
||||||
|
* sending their own wUSDT to the multisig custody account. This is what usdt-bridge's
|
||||||
|
* listen_withdrawals() actually watches for (an Assets.Transferred event targeting the custody
|
||||||
|
* account) - the user never calls Assets.burn directly. Burning is the multisig's job, later,
|
||||||
|
* once real USDT has actually been released on the Polkadot side; burn requires Admin origin
|
||||||
|
* (the multisig), so a user-submitted burn call has always failed on-chain with NoPermission -
|
||||||
|
* see the fixed bug in USDTBridge.tsx's handleWithdrawal, which used to call burn directly and
|
||||||
|
* report success purely from `status.isFinalized` without checking whether the extrinsic
|
||||||
|
* actually succeeded (a failed dispatch is still included/finalized, so it always "succeeded").
|
||||||
|
* @param api - Polkadot API instance
|
||||||
|
* @param amount - Amount in human-readable format (e.g., 100.50 USDT)
|
||||||
|
* @returns Plain (non-multisig) transfer extrinsic the user signs with their own key
|
||||||
|
*/
|
||||||
|
export function createWithdrawalInitiationTx(api: ApiPromise, amount: number) {
|
||||||
|
const amountBN = parseWUSDT(amount);
|
||||||
|
return api.tx.assets.transfer(WUSDT_ASSET_ID, WUSDT_BRIDGE_CUSTODY_PEZKUWI, amountBN.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where real USDT actually gets released to on Polkadot Asset Hub: usdt-bridge's relayer
|
||||||
|
* (listen_withdrawals in main.rs) derives it by re-encoding the SENDER's own address under
|
||||||
|
* Polkadot's SS58 prefix (0) - the same underlying sr25519/ed25519 key, just a different
|
||||||
|
* network's address format. It does NOT read a separately user-specified destination, so the
|
||||||
|
* UI must show this derived address rather than pretend an arbitrary one can be entered.
|
||||||
|
*/
|
||||||
|
export function deriveWithdrawalDestination(pezkuwiAddress: string): string {
|
||||||
|
return encodeAddress(pezkuwiAddress, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate withdrawal delay based on amount
|
* Calculate withdrawal delay based on amount
|
||||||
* @param amount - Withdrawal amount in USDT
|
* @param amount - Withdrawal amount in USDT
|
||||||
|
|||||||
@@ -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
|
# 1. Project URL: Copy from "Project URL" section
|
||||||
# 2. Anon key: Copy from "Project API keys" → "anon" → "public"
|
# 2. Anon key: Copy from "Project API keys" → "anon" → "public"
|
||||||
|
|
||||||
VITE_SUPABASE_URL=https://vbhftvdayqfmcgmzdxfv.supabase.co
|
VITE_SUPABASE_URL=https://supabase.pezkuwichain.io
|
||||||
VITE_SUPABASE_ANON_KEY=your_supabase_anon_key_here
|
VITE_SUPABASE_ANON_KEY=your_supabase_anon_key_here
|
||||||
|
|
||||||
# ========================================
|
# ========================================
|
||||||
|
|||||||
@@ -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-textdecoder": "^14.0.25",
|
||||||
"@pezkuwi/x-textencoder": "^14.0.25",
|
"@pezkuwi/x-textencoder": "^14.0.25",
|
||||||
"@pezkuwi/x-ws": "^14.0.25",
|
"@pezkuwi/x-ws": "^14.0.25",
|
||||||
"@pezkuwi/networks": "^14.0.25"
|
"@pezkuwi/networks": "^14.0.25",
|
||||||
|
"elliptic": "^6.6.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.9.0",
|
"@eslint/js": "^9.9.0",
|
||||||
@@ -147,6 +148,7 @@
|
|||||||
"typescript-eslint": "^8.0.1",
|
"typescript-eslint": "^8.0.1",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.1",
|
||||||
"vite-plugin-node-polyfills": "^0.25.0",
|
"vite-plugin-node-polyfills": "^0.25.0",
|
||||||
|
"vite-plugin-subresource-integrity": "^0.0.12",
|
||||||
"vitest": "^4.0.10"
|
"vitest": "^4.0.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
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
|
|
||||||