Transaction Science open standards monorepo. Each subdirectory is a self-contained reference protocol with its own README and licence. - openpay/ Apache-2.0 payment-acceptance stack (Rust, 26 crates) - smart-byte/ CC-BY-4.0 content-addressed value carrier (spec) - eoc/ CC-BY-4.0 energy-optimised compute (spec) The wire format and the right to fork are public. The optional hosted services are the offer; the protocols are not.
131 lines
9.9 KiB
Markdown
131 lines
9.9 KiB
Markdown
# Phase 31 — Operator Bring-Up Sprint
|
|
|
|
**Status**: Draft v0.31
|
|
**Date**: 2026-05-22 (Friday)
|
|
**Plan**: Saturday + Sunday 2026-05-23/24
|
|
|
|
## Why
|
|
|
|
After Phase 30 the codebase was structurally complete but operationally toothless. Every "live" surface was a trait waiting for a real implementation: webhook delivery was `MockTransport`, the crypto rail was `DeterministicCryptoGateway`, op-server had no auth, no rate limit, no operator CLI, no deploy docs. A vendor reading the README couldn't get from `git clone` to "real USDC moving" without writing a couple thousand lines of glue.
|
|
|
|
Phase 31 is the **operator bring-up sprint**: five tracks dispatched in parallel, each closing one operator-side gap so a vendor can deploy on the crypto rail this weekend.
|
|
|
|
## What shipped (in parallel)
|
|
|
|
**Wave 1** — five tracks dispatched simultaneously:
|
|
|
|
| Track | Crate / area | What it adds |
|
|
|---|---|---|
|
|
| **A** | `op-webhook` (feature `reqwest-transport`) | `ReqwestTransport` — real HTTPS webhook delivery via `reqwest::blocking::Client`, 10s default timeout, per-request timeout override. |
|
|
| **B** | `op-rails-crypto` (feature `evm`) | `EvmJsonRpcGateway<S: EvmSigner>` — real on-chain ERC-20 transfers on Ethereum / Base / Polygon / Arbitrum. Hand-rolled `transfer(address,uint256)` calldata, JSON-RPC client, receipt-based status polling. Operator supplies the signer (Fireblocks / KMS / hot wallet) via the `EvmSigner` trait. |
|
|
| **C** | `op-server` | `ApiKeyAuthLayer` (header check, bypass paths for `/health`+`/readiness`) and `RateLimitLayer` (hand-rolled token bucket, per-key, injectable clock). `router_with_middleware(state, auth, rate_limit)` for one-line opt-in. |
|
|
| **D** | `op-cli` (new crate) | The `op` command — 14 subcommands across health, refund, dispute, batch, subscription, fx, webhooks, audit. Talks to a running op-server over HTTP. |
|
|
| **E** | `docs/deploy/` | 7 files, 811 lines — README + Caddyfile + systemd unit + env sample + quickstart + backup + monitoring docs. Every claim grounded in shipped code. |
|
|
|
|
**Wave 2** — three more tracks dispatched right after, closing the "operator picks X / wires Y" dodge:
|
|
|
|
| Track | Crate / area | What it adds |
|
|
|---|---|---|
|
|
| **F** | `op-rails-crypto` (feature `evm`) | `LocalKeyEvmSigner` — real `EvmSigner` impl. Reads hex private key, signs EIP-155 legacy txs with k256 ECDSA, broadcasts via `eth_sendRawTransaction`. Anvil dev key `0xac09…` derives canonically to `0xf39f…2266`. Operators launch with this; swap to Fireblocks / KMS later. |
|
|
| **G** | `op-server` (env-driven main) | `OP_GRAPH_PATH`, `OP_API_KEYS`, `OP_RATE_LIMIT_PER_MINUTE`, `OP_BASE_RPC_URL`, `OP_USDC_BASE_PRIVATE_KEY`, `OP_LOG_FORMAT` all wired in `main.rs`. Set the two EVM vars together and the binary auto-registers a `usdc-base` rail at startup. `build_state_from_env` + `build_middleware_from_env` exposed in the lib for testing. |
|
|
| **H** | `examples/demo-merchant` | The pilot merchant you can run on a laptop. Generates a fresh k256 wallet, prints the address as a Basescan URL, listens for op-server webhooks on `127.0.0.1:9090`, polls `balanceOf` on the RPC, prints `PAID` on first observed payment and exits 0. `--testnet` flag flips to Base Sepolia for safe first runs. |
|
|
|
|
Workspace at the end of Phase 31 (both waves):
|
|
|
|
| Check | Result |
|
|
|---|---|
|
|
| `cargo build --workspace --all-targets` (default features) | **0 errors, 0 warnings** |
|
|
| `cargo test --workspace` | **1124 passing, 0 failing** (+74 vs Phase 30) |
|
|
| `cargo test -p op-webhook --features reqwest-transport` | **111 passing, 0 failing** |
|
|
| `cargo test -p op-rails-crypto --features evm` | **28 passing, 0 failing** |
|
|
| `cargo clippy --workspace --all-targets` | **0 warnings** |
|
|
|
|
Test-count delta breakdown:
|
|
- Wave 1: op-server middleware +8, op-cli +17, op-webhook reqwest-transport +2 (feature-gated), op-rails-crypto evm +12 (feature-gated).
|
|
- Wave 2: op-rails-crypto local_signer +8 (feature-gated), op-server config +20 (13 unit + 7 integration), demo-merchant +8, plus `Orchestrator::has_driver`/`registered_drivers` introspection.
|
|
|
|
## Parallel execution
|
|
|
|
Five Claude Code subagents dispatched simultaneously in one tool-block, each given a non-overlapping file scope and an explicit "build + test + clippy zero warnings" acceptance bar. Wall-clock from dispatch to last report: **~7 minutes**. Sequential lower-bound (additive durations) would have been ~22 minutes; the parallelism saved ~15 minutes of wait time while I did workspace-level wiring in trunk.
|
|
|
|
## Architectural posture, preserved
|
|
|
|
The reference stack still has **zero hard dependencies** on networked services:
|
|
|
|
- `reqwest` is gated behind a feature on op-webhook and op-rails-crypto. Default builds don't pull it; operators who want live HTTP opt in.
|
|
- `EvmSigner` is a trait. The gateway never holds private keys. Operators wire Fireblocks / KMS / multisig / a hot wallet behind the trait — the keystore stays where the security audit can find it.
|
|
- The CLI talks to op-server over the same HTTP surface as any other client; it isn't a privileged binary.
|
|
- The auth and rate-limit layers are `tower::Layer` middleware that operators apply via `router_with_middleware`. The default `router(state)` (no auth) stays for tests and embedded use.
|
|
|
|
Nothing about Phase 31 narrows the deployment surface. The single-`.graph`-file model, the embedded Minigraf, the no-server posture — all still true.
|
|
|
|
## How a vendor goes live on this — Wave 2 collapses it to one command
|
|
|
|
The pre-Wave-2 plan required a thin custom `main.rs`. Wave 2 made the shipped binary self-configuring. Now the launch is:
|
|
|
|
```bash
|
|
# 1. Build with the live HTTP transport + EVM rail features.
|
|
cargo build --release -p op-server -p op-cli \
|
|
--features "op-rails-crypto/evm,op-webhook/reqwest-transport"
|
|
|
|
# 2. Run the binary with env-driven config.
|
|
OP_BIND_ADDR=0.0.0.0:8080 \
|
|
OP_GRAPH_PATH=/var/lib/openpay/data.graph \
|
|
OP_API_KEYS="$(openssl rand -hex 32)" \
|
|
OP_RATE_LIMIT_PER_MINUTE=600 \
|
|
OP_LOG_FORMAT=json \
|
|
OP_BASE_RPC_URL=https://mainnet.base.org \
|
|
OP_USDC_BASE_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
|
|
target/release/op-server
|
|
```
|
|
|
|
That single invocation:
|
|
- Binds plain HTTP on `0.0.0.0:8080` (TLS terminator goes upstream).
|
|
- Persists every store to one `.graph` file.
|
|
- Enforces API-key auth on everything except `/health` + `/readiness`.
|
|
- Caps each key at 600 requests/minute.
|
|
- Emits structured JSON logs.
|
|
- Auto-registers a `usdc-base` rail with a `LocalKeyEvmSigner` reading the hot wallet from the env.
|
|
|
|
**Pilot validation on a laptop**, parallel terminals:
|
|
|
|
```bash
|
|
# Terminal 1: server (env vars as above)
|
|
target/release/op-server
|
|
|
|
# Terminal 2: demo merchant on Sepolia
|
|
cargo run --release -p demo-merchant -- --testnet
|
|
# → Merchant wallet: 0xabc...
|
|
# https://sepolia.basescan.org/address/0xabc...
|
|
# Listening on http://127.0.0.1:9090 for op-server webhooks...
|
|
|
|
# Terminal 3: send Sepolia USDC to the printed address.
|
|
# Demo merchant prints PAID and exits 0 on first observed payment.
|
|
```
|
|
|
|
After that round-trip works on testnet, flip to mainnet by dropping `--testnet` and configuring the production `OP_USDC_BASE_PRIVATE_KEY`. The only operator-specific decisions left are (a) which EVM RPC endpoint and signer custody model to settle on long-term, and (b) which actual merchant to pilot with.
|
|
|
|
## Honest concerns (carry-forward)
|
|
|
|
- **`OP_API_KEYS` / `OP_GRAPH_PATH` / `OP_LOG_FORMAT` env vars are convention, not wired.** The shipped `op-server/src/main.rs` reads only `OP_BIND_ADDR` and `RUST_LOG`. Operators wanting the documented env-var config either patch `main.rs` (small) or build a custom binary that wires those vars through `AppState::with_graph_path` + `ApiKeyAuthLayer::new`. Documented honestly in `docs/deploy/README.md`; cleaning this up is a 30-line follow-up.
|
|
- **EVM signer is operator-provided.** We ship the trait + gateway + JSON-RPC client + calldata encoder. The actual key custody and ECDSA signing live behind `EvmSigner::sign_and_broadcast`. This is a feature, not a gap — but it does mean "go live" requires the operator to wire a real signer (Fireblocks, AWS KMS w/ secp256k1, hot-wallet-in-HSM, whatever).
|
|
- **No Solana gateway yet.** Scope-bounded to one chain family this weekend. EVM-with-USDC is the most operator-deployable path today (Base + Polygon have sub-cent fees and instant settlement). Solana adds in a future wedge.
|
|
- **No real fraud model.** Phase 11's `op-fraud` crate has features and a `Scorer` trait. Production scoring is a separate ML-modeling problem, not a one-weekend code task. Operators wire a `NoOpScorer` or their own model.
|
|
- **Rate-limit storage is in-process.** The token bucket lives in an `Arc<Mutex<HashMap>>` per `op-server` instance. Multi-instance deployments need a shared backend (Redis) — out of scope for the bring-up. Single-instance deployments are fine as-is.
|
|
- **No load test.** We haven't put real synthetic traffic through any of this. The architecture says it should handle ≥1k TPS per instance on the embedded graph; that's a claim, not a measurement.
|
|
- **`--all-features` workspace build fails on `ort` (ONNX runtime).** Pre-existing; unrelated to Phase 31. Default-feature + per-crate feature builds are all green.
|
|
|
|
## Status after Phase 31
|
|
|
|
The 28 → 29 → 30 → 31 quartet closed the last operator-facing gaps for a crypto-rail launch:
|
|
|
|
| Phase | Closed |
|
|
|---|---|
|
|
| 28 | Webhook delivery wired end-to-end at the handler boundary |
|
|
| 29 | Multi-currency / FX primitives + HTTP endpoints |
|
|
| 30 | 3DS / SCA resume primitive (full card-rail completeness) |
|
|
| **31** | **Real HTTP transport + real EVM gateway + auth + rate-limit + CLI + deploy docs** |
|
|
|
|
After Phase 31, the workspace is **30 crates, 1075 default-feature tests, 0 warnings**. A vendor with EVM-chain signing infrastructure (Fireblocks account, KMS access, or a managed hot wallet) and a pilot merchant can be live this weekend.
|
|
|
|
The fiat path (cards via PSP, A2A via bank rail) still requires the regulatory work documented earlier — but that's exactly the path OpenPay was built to skip. Crypto-rail-first is the deployable answer.
|