Skip to content

Plan 2a: Broadcaster + Ethclient (stacked on Plan 1)#56

Open
117l11 wants to merge 23 commits into
feat/middleware-foundationfrom
plan/2a-broadcaster
Open

Plan 2a: Broadcaster + Ethclient (stacked on Plan 1)#56
117l11 wants to merge 23 commits into
feat/middleware-foundationfrom
plan/2a-broadcaster

Conversation

@117l11

@117l11 117l11 commented Apr 19, 2026

Copy link
Copy Markdown
Member

Base: feat/middleware-foundation — stacked on Plan 1. Retarget to develop after Plan 1 merges.

Summary

  • Rust broadcaster crate that subscribes to eth_tx(Pending) in SpacetimeDB, builds + signs EIP-7702 type-4 txs, submits to Base Sepolia, watches receipts, and writes back via middleware reducers.
  • Middleware hardening: trust-on-first-use claim_gateway_identity, deleted stale set_gateway_identity / ABI stubs, Burner7702 default now points at the Base Sepolia deploy (0x1ff0…F013).
  • Startup reconcile recovers rows stuck in Broadcasting/Broadcast and rebootstraps the operator nonce from the RPC.
  • Integration harness (anvil fork + spacetime CLI wrappers) + live Base Sepolia smoke script.
  • .env.example template; .env updated with the new broadcaster env vars.

What's inside

Area Tasks Key files
Middleware 1–4 middleware/src/{claim_gateway_identity,lib}.rs, config default
Broadcaster scaffold 5–11 broadcaster/Cargo.toml, src/{stdb,error,config,signer,abi}.rs
Broadcaster runtime 12–16 src/{subscriber,submitter,watcher,reconcile,main}.rs
Tests 17–19 broadcaster/tests/{helpers,integration_anvil}.rs, tests/smoke/test_broadcaster_flow.sh
Env 20 .env, .env.example, .gitignore

Test plan

  • cargo build -p broadcaster
  • cargo test -p broadcaster — 13 unit tests pass
  • Missing-env path: binary exits 1 with Config("missing RPC_URL")
  • cargo test -p broadcaster --test integration_anvil -- --ignored --skip test_end_to_end_send_eth_flow — 2/2 scaffolds pass
  • cargo test -p broadcaster --test integration_anvil -- --ignored test_end_to_end_send_eth_flow (needs SpacetimeDB @ :3000 with gateway2_test)
  • ./tests/smoke/test_broadcaster_flow.sh against live Base Sepolia (needs funded burner + Alchemy RPC + running broadcaster binary)
  • Flesh out bodies of test_underfunded_burner_marks_row_failed_without_broadcasting and test_crash_mid_submit_reconciles_on_restart

Notes for reviewers

  • WALLET_PRIVATE_KEY / WALLET_PASSPHRASE are kept in .env as deprecated fallbacks; broadcaster reads only OPERATOR_KEYSTORE_PATH + OPERATOR_KEYSTORE_PASSPHRASE. Removed in Plan 2b.
  • Broadcaster verifies the on-chain chain_id matches env before doing anything, and waits up to 5s for app_config.gateway_identity to match its SpacetimeDB identity before starting the submit loop.
  • Reconcile is bounded to 60s + RECONCILE_SCAN_BLOCKS (default 20) recent blocks when walking for a lost tx hash.

117l11 and others added 21 commits April 18, 2026 14:26
Specs the Rust broadcaster binary, bundled-auth EIP-7702 tx shape,
claim_gateway_identity reducer, and middleware stub deletions.
Security items (PIN lockout, random salt) remain deferred to Plan 2b.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
21 tasks covering claim_gateway_identity reducer, middleware stub
deletion, Burner7702 address fix, new broadcaster crate (config,
signer, abi, error, subscriber, submitter, watcher, reconcile),
anvil integration tests, and live Base Sepolia smoke.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Removes the old set_gateway_identity reducer that allowed unauthorized
identity overwrites. Also deletes unused ABI encoding scaffolding
(signature, selector, encode stubs and helper functions) from encoding.rs —
real ABI encoding is deferred to broadcaster crate via alloy::sol!.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bindings generated via `spacetime generate --lang rust` against the
published middleware schema. Pinned spacetimedb-sdk to =1.11.1 to match
the CLI version that emits the code; SDK 1.12.0 adds a QueryBuilder
associated type the generated impls don't provide.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Hand-derived from ethclient/contracts/Burner7702.sol. Basescan V1 API
is deprecated, Etherscan V2 requires an API key, and this machine's
solc is older than the ^0.8.32 pragma — so the JSON was constructed
directly from the Solidity source. Contains constructor, receive,
OPERATOR getter, execute, and executeBatch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Tests use a module-local Mutex to serialize env-var mutation so they
can run under the default parallel cargo test harness.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…reconcile

Bootstraps the broadcaster binary: loads Config from env, decrypts operator
keystore, builds an alloy HTTP provider and verifies chain_id matches env,
connects the SpacetimeDB subscriber, subscribes to app_config/auth_7702/eth_tx,
waits for gateway_identity to match the broadcaster's STDB identity, rebootstraps
the operator nonce, runs reconcile once, spawns the Watcher on its own task,
and drives the submit loop until ctrl_c.

Also exposes Subscriber.stdb as Arc<DbConnection> so Submitter, Watcher, and
reconcile share a single connection.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sets up broadcaster/tests/helpers with an Anvil struct that spawns a
forked Base Sepolia node, and a scaffold integration_anvil test that
exercises the harness. Task 18 fleshes out the full end-to-end flow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Expands helpers with anvil cheat RPC wrappers (set_balance, set_code) and
spacetime CLI wrappers (reset, sql, call). Replaces the scaffold test with
an end-to-end flow that drives phone registration + send-eth USSD via the
middleware, then asserts a Pending eth_tx row exists. Adds two named follow-up
scaffolds (underfunded burner; crash mid-submit reconciliation) whose bodies
mirror the first test's pattern.

Full end-to-end assertion (Confirmed + recipient balance) requires a live
SpacetimeDB on 127.0.0.1:3000 with the middleware published as gateway2_test
and is gated behind #[ignore] + runtime stdb_available check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drives the full USSD register + send-eth flow against a running broadcaster
pointed at Base Sepolia, polls eth_tx until Confirmed (60s budget), and
prints the Basescan tx URL on success. Requires local SpacetimeDB, a running
broadcaster process, and a funded burner for +254712345678.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a safe-to-commit .env.example documenting the broadcaster's required
env vars (RPC_URL, RPC_WS_URL, CHAIN_ID, OPERATOR_KEYSTORE_*, SpacetimeDB
connection) plus optional tuning knobs. Adjusts .gitignore with a
!.env.example exception so the template is tracked while .env stays ignored.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@vercel

vercel Bot commented Apr 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stk2eth Ready Ready Preview, Comment Apr 19, 2026 6:58pm
stk2eth-ussdgeth Error Error Apr 19, 2026 6:58pm

117l11 and others added 2 commits April 20, 2026 19:21
Middleware stored the raw user-input decimal (e.g. "0.01") in
eth_tx.value, but the broadcaster parses that column as wei via
U256::from_str, which fails on the '.'. This left rows stuck in
Broadcasting with a parse error.

Adds eth_decimal_to_wei() that string-converts "0.01" -> "10000000000000000"
precisely (no f64 intermediate) and uses it both for validation in
validate_amount and for the eth_tx insert in validate_pin.

End-to-end verified on Base Sepolia: tx 0x799b908e... (type-4, auth to
0x1ff0a24d) moved 0.01 ETH burner->burner; recipient balance matches.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DbContext::identity() calls try_identity().unwrap() internally, which
panics if the SpacetimeDB WebSocket handshake hasn't completed yet.
On a cold start the subscriber's connection is established but the
identity token arrives a few hundred ms later, so the panic fires
before wait_for_gateway_identity ever runs.

Poll try_identity() up to 5s (50x100ms) before comparing against
app_config.gateway_identity.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@117l11

117l11 commented Apr 20, 2026

Copy link
Copy Markdown
Member Author

Live validation on Base Sepolia ✅

End-to-end USSD send-eth flow exercised against real infrastructure (local SpacetimeDB + Alchemy Base Sepolia):

  • Tx: 0x799b908ed8b2b59fe4d2ac8266b06335d00684255c8b082ce8373ff61092dfef
  • Type: 0x4 (EIP-7702)
  • authorizationList: 1 entry, delegate = 0x1ff0A24D1145d58Ea6F190569C10916E1a78F013 (Burner7702)
  • Operator: 0x60537D3Dc74E7Fc2d85b0AE8df341Ab2e2e703Bd
  • Sender burner → Recipient burner: 0x8220…a8840x2b72…649c, value 10000000000000000 wei (0.01 ETH)
  • On-chain recipient balance confirmed: 10000000000000000

Two fixes added during validation

  • 5d53186 middleware: send-eth amount is now converted from decimal ETH ("0.01") to wei ("10000000000000000") before eth_tx.insert. Prior behaviour left rows stuck in Broadcasting because the broadcaster parses value as wei via U256::from_str. Conversion is string-based (no f64 rounding) via new eth_decimal_to_wei helper.
  • 620b229 broadcaster: DbContext::identity() internally calls try_identity().unwrap(), which panics if the SpacetimeDB WS handshake hasn't completed. main.rs now polls try_identity() for up to 5s before comparing against app_config.gateway_identity.

Still open (not blocking)

  • Flesh out the two #[ignore] scaffold tests (test_underfunded_burner_marks_row_failed_without_broadcasting, test_crash_mid_submit_reconciles_on_restart).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant