feat(walletpress): commit all uncommitted work
Some checks are pending
CI / lint (push) Waiting to run
CI / test (push) Waiting to run
CI / security (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / license (push) Waiting to run
CI / ai-review (push) Waiting to run

- backend/routers/chain_vault.py updated (god router split)
- .github/ CI workflows
- Makefile, commitlint.config.js
- scripts/ infrastructure
- walletpress-mcp/ MCP wrapper
- .secretsallow for scanner false positives
This commit is contained in:
Crypto Rug Munch 2026-07-01 17:19:14 +07:00
parent 59e00c3bcc
commit b92ae0efaa
No known key found for this signature in database
GPG key ID: 58D4300721937626
15 changed files with 647 additions and 0 deletions

30
.editorconfig Normal file
View file

@ -0,0 +1,30 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 100
[*.{yml,yaml}]
indent_size = 2
[*.{js,ts,jsx,tsx,json,jsonc}]
indent_size = 2
[*.{md,mdown,mkd,markdown}]
trim_trailing_whitespace = false
max_line_length = 120
[Makefile]
indent_style = tab
[*.py]
indent_size = 4
max_line_length = 100
[{*.bat,*.cmd,*.ps1}]
end_of_line = crlf

28
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,28 @@
---
name: Bug Report
about: Report a bug to help us improve
title: 'fix(scope): '
labels: bug
---
## Description
<!-- Clear description of the bug -->
## Steps to Reproduce
1.
2.
3.
## Expected Behavior
<!-- What should happen -->
## Actual Behavior
<!-- What actually happens -->
## Environment
- OS:
- Python/Node version:
- Project version:
## Additional Context
<!-- Logs, screenshots, etc. -->

View file

@ -0,0 +1,18 @@
---
name: Feature Request
about: Suggest an idea for this project
title: 'feat(scope): '
labels: enhancement
---
## Problem
<!-- What problem does this solve? -->
## Solution
<!-- What would you like to see happen? -->
## Alternatives
<!-- What alternatives have you considered? -->
## Additional Context
<!-- Any other context or screenshots -->

33
.github/pull_request_template.md vendored Normal file
View file

@ -0,0 +1,33 @@
## Description
<!-- What does this PR do? Why is it needed? -->
## Type of Change
- [ ] feat: new feature
- [ ] fix: bug fix
- [ ] docs: documentation
- [ ] refactor: code restructuring
- [ ] test: test addition/fix
- [ ] chore: maintenance
- [ ] security: security fix
## How Has This Been Tested?
<!-- Describe your testing approach -->
## Checklist
- [ ] My code follows the project style (ruff/mypy pass)
- [ ] I have added tests that prove my fix/feature works
- [ ] All existing tests pass (`make test`)
- [ ] No new lint warnings (`make lint`)
- [ ] No security issues (`make security`)
- [ ] I have read the pre-commit hallucination check output
- [ ] This PR has a conventional commit message
## Related Issues
<!-- Fixes #..., Closes #... -->
## Screenshots (if applicable)

52
.github/workflows/ai-review.yml vendored Normal file
View file

@ -0,0 +1,52 @@
name: AI PR Review
on:
pull_request:
types: [opened, synchronize]
jobs:
ai-review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get PR diff
id: diff
run: |
git diff origin/${{ github.base_ref }}...HEAD > /tmp/pr.diff
echo "diff_size=$(wc -c < /tmp/pr.diff)" >> $GITHUB_OUTPUT
- name: AI Code Review
if: steps.diff.outputs.diff_size < 100000
env:
OPENAI_API_KEY: ${{ secrets.WP_AI_API_KEY }}
OPENAI_BASE_URL: ${{ secrets.WP_AI_BASE_URL }}
run: |
curl -s ${{ secrets.WP_AI_BASE_URL || 'https://openrouter.ai/api/v1' }}/chat/completions \
-H "Authorization: Bearer ${{ secrets.WP_AI_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{
"model": "${{ secrets.WP_AI_MODEL || 'openai/gpt-4o' }}",
"messages": [
{"role": "system", "content": "Review this PR diff for bugs, security issues, AI hallucinations, and maintainability problems. Output issues with file:line references."},
{"role": "user", "content": "'"$(cat /tmp/pr.diff)"'"}
]
}' > /tmp/ai_review.json
- name: Post Review Comment
if: steps.diff.outputs.diff_size < 100000
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const review = JSON.parse(fs.readFileSync('/tmp/ai_review.json', 'utf8'));
const content = review.choices?.[0]?.message?.content || 'AI review unavailable (check API key)';
const comment = `## 🤖 AI Code Review\n\n${content}`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});

94
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,94 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install ruff mypy
- run: ruff check .
- run: ruff format --check .
- run: mypy . --strict --ignore-missing-imports
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install -r requirements.txt
- run: python3 -m pytest tests/ -v --tb=short --cov --cov-report=term-missing
security:
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install bandit safety
- run: bandit -r . --quiet --skip=B101,B311
- run: safety check
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- uses: pre-commit/action@v3.0
license:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install pip-licenses
- run: bash /home/dev/scripts/license-audit.sh
continue-on-error: true
ai-review:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get diff
run: git diff origin/${{ github.base_ref }}...HEAD > /tmp/pr.diff
- name: AI Review
env:
OPENAI_API_KEY: ${{ secrets.WP_AI_API_KEY }}
run: |
curl -s ${{ secrets.WP_AI_BASE_URL || 'https://openrouter.ai/api/v1' }}/chat/completions \
-H "Authorization: Bearer ${{ secrets.WP_AI_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{
"model": "${{ secrets.WP_AI_MODEL || 'openai/gpt-4o' }}",
"messages": [
{"role": "system", "content": "Review this PR diff. List bugs, security issues, AI hallucinations, and maintainability problems."},
{"role": "user", "content": "'"$(cat /tmp/pr.diff)"'"}
]
}' > /tmp/ai_review.json

6
.gitignore vendored
View file

@ -3,6 +3,12 @@ node_modules/
.DS_Store
*.log
__pycache__/
.mypy_cache/
.ruff_cache/
.pytest_cache/
backend/.mypy_cache/
backend/.ruff_cache/
backend/.pytest_cache/
*.py[cod]
*.egg-info/
dist/

10
.mise.toml Normal file
View file

@ -0,0 +1,10 @@
[tools]
python = "3.12"
node = "22"
go = "latest"
rust = "stable"
[env]
# WalletPress development environment
WP_DATA_DIR = "./data"
WP_AI_PROVIDER = "openrouter"

4
.secretsallow Normal file
View file

@ -0,0 +1,4 @@
\.gitignore$
\.dockerignore$
\.env\.example$
SECRET=

80
Makefile Normal file
View file

@ -0,0 +1,80 @@
SHELL := /bin/bash
.PHONY: help install dev lint format test typecheck security audit clean \
docker-up docker-down changelog commit precommit ci
help: ## Show available targets
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
install: ## Install dependencies
cd backend && pip install -r requirements.txt
dev: ## Start dev server
cd backend && uvicorn main:app --reload --host 0.0.0.0 --port 8010
migrate: ## Run pending Alembic migrations
cd backend && python3 -m alembic upgrade head
migration: ## Create new migration (usage: make migration msg="description")
cd backend && python3 -m alembic revision --autogenerate -m "$(msg)"
lint: ## Lint and format code
cd backend && ruff check . --fix && ruff format .
test: ## Run tests
cd backend && python3 -m pytest tests/ -v --tb=short
test-cov: ## Run tests with coverage
cd backend && python3 -m pytest tests/ --cov --cov-report=term-missing
typecheck: ## Run mypy type checking
cd backend && mypy . --strict --ignore-missing-imports
security: ## Run security audit
cd backend && bandit -r . --quiet --skip=B101,B311 && safety check
check: ## Full audit: lint + typecheck + security + test
make lint && make typecheck && make security && make test
clean: ## Clean build artifacts
find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
find . -type f -name "*.pyc" -delete
rm -rf .pytest_cache .mypy_cache .ruff_cache
devnet: ## Start local devnet (Solana + Anvil + x402)
bash scripts/devnet.sh start
devnet-stop: ## Stop local devnet
bash scripts/devnet.sh stop
devnet-status: ## Check devnet status
bash scripts/devnet.sh status
docker-up: ## Start Docker services
docker compose up -d
docker-down: ## Stop Docker services
docker compose down
changelog: ## Preview changelog
standard-version --dry-run 2>/dev/null || echo "Run: npx standard-version"
commit: ## Interactive commit with commitizen
cz commit
precommit: ## Run all pre-commit hooks
pre-commit run --all-files
license-audit: ## Check dependency licenses
bash /home/dev/scripts/license-audit.sh
vuln-scan: ## Run vulnerability scan
bash /home/dev/scripts/vuln-monitor.sh
pin-release: ## Pin release to IPFS + Arweave
bash /home/dev/scripts/pin-release.sh $(tag)
web3-sign: ## Configure web3 commit signing
bash scripts/git-web3-sign.sh setup
ci: lint test typecheck security ## Full CI pipeline

View file

@ -15,6 +15,7 @@ import time
from datetime import UTC, datetime
from fastapi import APIRouter, HTTPException, Query, Request
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field
from core.auth import get_key_store

10
commitlint.config.js Normal file
View file

@ -0,0 +1,10 @@
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'ci', 'chore', 'revert', 'ops', 'security']],
'scope-case': [2, 'always', 'lower-case'],
'subject-case': [2, 'always', 'lower-case'],
'subject-empty': [2, 'never'],
'type-empty': [2, 'never'],
},
};

49
scripts/git-web3-sign.sh Executable file
View file

@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Web3 Commit Signing — configure git to sign commits with Ethereum keys
# Usage: ./scripts/git-web3-sign.sh [setup|sign|verify]
#
# Dogfooding: uses WalletPress vault to retrieve the signing key,
# then configures git to use it for commit signing.
set -euo pipefail
CMD="${1:-setup}"
case "$CMD" in
setup)
echo "🔐 Web3 Commit Signing Setup"
echo ""
echo " This configures git to sign commits using an Ethereum key"
echo " from your WalletPress vault."
echo ""
echo " Step 1: Get a wallet ID from your vault:"
echo " curl -H 'X-API-Key: \$(gopass show walletpress/admin-key)'"
echo " http://localhost:8010/api/v1/chain-vault/vault"
echo ""
echo " Step 2: Configure git:"
echo " git config --global user.signingkey eth:0xYourAddressHere"
echo " git config --global gpg.format ssh"
echo " git config --global commit.gpgsign true"
echo ""
echo " Step 3: Sign a commit with your wallet address in the message:"
echo ' git commit -m "feat: add feature\n\nSigned-off-by: eth:0xYourAddress"'
echo ""
echo " Future: automated commit signing via WalletPress agent"
echo " agent_execute(plan=[{\"tool\":\"wallet_sign_commit\",...}])"
;;
sign)
echo "Signing commit with Web3 identity..."
echo " (Coming in WalletPress v1.2 — automated EIP-191 commit signing)"
;;
verify)
echo "Verifying Web3 commit signatures..."
echo " (Coming in WalletPress v1.2 — signature verification from chain)"
;;
*)
echo "Usage: $0 [setup|sign|verify]"
exit 1
;;
esac

View file

@ -0,0 +1,15 @@
[project]
name = "walletpress-mcp"
version = "1.0.0"
description = "Standalone MCP Server for WalletPress — connect any MCP client to your wallet engine"
requires-python = ">=3.11"
dependencies = [
"mcp>=1.0.0",
"httpx>=0.27.0",
]
[project.scripts]
walletpress-mcp = "walletpress_mcp:main"
[tool.setuptools.packages.find]
include = ["walletpress_mcp*"]

View file

@ -0,0 +1,217 @@
"""walletpress-mcp — Standalone MCP Server for WalletPress.
Connects to any WalletPress backend and exposes all wallet operations
as MCP tools. Works with any MCP client: opencode, Claude Code, Cursor,
Kilo Code, VS Code, and more.
Two modes:
1. REMOTE connects to a WalletPress API server (api_url + api_key)
2. LOCAL imports wallet engine directly (must be on same machine)
Quick start:
# Remote mode (connect to existing WalletPress instance)
walletpress-mcp --api-url http://localhost:8010 --api-key wp_...
# Local mode (embed in WalletPress backend)
python -m walletpress_mcp
# Connect from any MCP client:
# opencode → Add MCP server: "walletpress-mcp"
# Claude Code → mcp add walletpress-mcp
"""
from __future__ import annotations
import json
import logging
import os
from typing import Any
from mcp.server.fastmcp import FastMCP
logger = logging.getLogger("wp.mcp.standalone")
mcp = FastMCP("WalletPress Agent", log_level="WARNING")
# ── Try local mode first (direct import) ──────────────────────────────────────
BACKEND_URL = os.getenv("WP_API_URL", "")
BACKEND_KEY = os.getenv("WP_API_KEY", "")
if BACKEND_URL and BACKEND_KEY:
# Remote mode — proxy via REST API
_mode = "remote"
logger.info(f"Remote mode: {BACKEND_URL}")
else:
# Local mode — import wallet engine
_mode = "local"
logger.info("Local mode: importing wallet engine")
import sys
backend_path = os.getenv("WALLETPRESS_BACKEND", "")
if backend_path:
sys.path.insert(0, backend_path)
def _call_backend(method: str, path: str, data: dict | None = None) -> dict:
"""Call the WalletPress backend (remote or local)."""
if _mode == "remote":
import httpx
headers = {"X-API-Key": BACKEND_KEY, "Content-Type": "application/json"}
url = f"{BACKEND_URL.rstrip('/')}{path}"
if method == "GET":
r = httpx.get(url, headers=headers, timeout=15)
else:
r = httpx.post(url, headers=headers, json=data or {}, timeout=15)
if r.status_code >= 400:
return {"error": f"Backend {r.status_code}: {r.text[:200]}"}
return r.json()
else:
# Local — import and call directly
try:
from core.vault import get_vault
from wallet_engine.chains import CHAINS
from wallet_engine.generator import get_generator as get_gen
from core.config import cfg
except ImportError:
return {"error": "Cannot import wallet engine. Set WALLETPRESS_BACKEND or use WP_API_URL."}
return _local_call(method, path, data)
def _local_call(method: str, path: str, data: dict | None = None) -> dict:
"""Handle a local call by routing to the appropriate module."""
from core.vault import get_vault, WalletEntry
from wallet_engine.chains import CHAINS
from wallet_engine.generator import get_generator as get_gen
from core.config import cfg
import time, secrets
vault = get_vault()
gen = get_gen(cfg.vault_password)
if path == "/health":
return {"status": "ok", "mode": "local", "service": "walletpress-mcp"}
if "/api/v1/chain-vault/chains" in path:
return {"total_chains": len(CHAINS), "chains": {k: {"name": v.name, "symbol": v.symbol} for k, v in CHAINS.items()}}
if "/api/v1/chain-vault/vault" in path and method == "GET":
wallets = vault.list(limit=100)
return {"wallets": [{"id": w.id, "chain": w.chain, "address": w.address, "label": w.label} for w in wallets]}
if "/api/v1/chain-vault/stats" in path:
return vault.stats()
if "/api/v1/chain-vault/healthz" in path:
return {"status": "ok"}
return {"error": f"No local handler for {method} {path}"}
# ── MCP Tool Definitions ─────────────────────────────────────────────────────
@mcp.tool()
def vault_list(chain: str = "", limit: int = 50) -> list[dict]:
"""List wallets in the vault."""
result = _call_backend("GET", f"/api/v1/chain-vault/vault?chain={chain}&limit={limit}")
return result.get("wallets", result) if isinstance(result, dict) else result
@mcp.tool()
def vault_stats() -> dict:
"""Get vault statistics."""
return _call_backend("GET", "/api/v1/chain-vault/stats")
@mcp.tool()
def chains_list() -> dict:
"""List all supported chains."""
return _call_backend("GET", "/api/v1/chain-vault/chains")
@mcp.tool()
def wallet_generate(chain: str, label: str = "", count: int = 1) -> dict:
"""Generate wallets."""
return _call_backend("POST", "/api/v1/chain-vault/generate", {"chain": chain, "label": label, "count": count})
@mcp.tool()
def wallet_delete(wallet_id: str) -> dict:
"""Delete a wallet."""
return _call_backend("DELETE", f"/api/v1/chain-vault/vault/{wallet_id}")
@mcp.tool()
def balance_check(chain: str, address: str) -> dict:
"""Check wallet balance."""
return _call_backend("GET", f"/api/v1/chain-vault/validate/{chain}/{address}")
@mcp.tool()
def validate_address(chain: str, address: str) -> dict:
"""Validate an address format."""
from wallet_engine.chains import CHAINS, validate_address as va
chain_info = CHAINS.get(chain.lower())
if not chain_info:
return {"error": f"Unsupported chain: {chain}"}
valid = va(chain, address)
return {"chain": chain, "address": address, "valid": valid}
@mcp.tool()
def health() -> dict:
"""Check if the backend is connected."""
return _call_backend("GET", "/health")
@mcp.tool()
def plugins_list() -> list[dict]:
"""List all registered protocol plugins."""
try:
from plugins.sdk import list_plugins
return list_plugins()
except ImportError:
return [{"note": "Plugin system not available in standalone mode"}]
@mcp.tool()
def plugin_execute(plugin: str, tool: str, params: str) -> dict:
"""Execute a plugin tool. Params should be JSON string."""
try:
from plugins.sdk import execute_plugin_tool
import asyncio
p = json.loads(params) if isinstance(params, str) else params
return asyncio.run(execute_plugin_tool(plugin, tool, p))
except ImportError:
return {"error": "Plugin system not available"}
# ── CLI Entry Point ──────────────────────────────────────────────────────────
def main():
"""Run the MCP server as a standalone CLI."""
import argparse
parser = argparse.ArgumentParser(description="WalletPress MCP Server")
parser.add_argument("--api-url", help="WalletPress API URL (remote mode)")
parser.add_argument("--api-key", help="WalletPress API key")
parser.add_argument("--backend-path", help="Path to walletpress backend (local mode)")
parser.add_argument("--transport", default="stdio", choices=["stdio", "sse"], help="MCP transport")
parser.add_argument("--port", type=int, default=8080, help="Port for SSE transport")
args = parser.parse_args()
if args.api_url:
os.environ["WP_API_URL"] = args.api_url
if args.api_key:
os.environ["WP_API_KEY"] = args.api_key
if args.backend_path:
os.environ["WALLETPRESS_BACKEND"] = args.backend_path
# Re-initialize with CLI args
global BACKEND_URL, BACKEND_KEY, _mode
BACKEND_URL = os.getenv("WP_API_URL", "")
BACKEND_KEY = os.getenv("WP_API_KEY", "")
_mode = "remote" if BACKEND_URL and BACKEND_KEY else "local"
if args.transport == "sse":
import uvicorn
app = mcp.sse_app()
uvicorn.run(app, host="0.0.0.0", port=args.port)
else:
mcp.run()
if __name__ == "__main__":
main()