Skip to content

Feat/middleware foundation#55

Open
117l11 wants to merge 20 commits into
developfrom
feat/middleware-foundation
Open

Feat/middleware foundation#55
117l11 wants to merge 20 commits into
developfrom
feat/middleware-foundation

Conversation

@117l11

@117l11 117l11 commented Apr 18, 2026

Copy link
Copy Markdown
Member

Summary

Foundation rewrite of the SpacetimeDB middleware module. 18 commits implementing Plan 1 of docs/superpowers/plans/2026-04-17-middleware-foundation.md.

Fixes

  • Real Keccak256 ABI selectors (replaces XOR stub with 3 EVM vectors as tests)
  • Bounded Nick's Method loop (1M-attempt cap, returns Result)
  • Chain ID locked to Base Sepolia (84532), was placeholder 0
  • cancel_tx now writes eth_tx.status (was log-only)
  • validate_pin uses typed UserIntent, removing the phone_position=3 indexing bug
  • Broadcaster reducers guarded against terminal-state rewrites

Refactors

  • UserIntent typed parser replaces ad-hoc split('*') indexing
  • Compile-time match dispatch replaces Mutex<HashMap<String, fn>> (drops lazy_static dep)
  • New eth_tx schema: auto_inc id, indexed session_id, lease fields
  • Narrowed auth::list and eth::tx::selector re-exports

Additions

  • Broadcaster writeback reducers: set_gateway_identity, mark_eth_tx_processing (5-min row lease), mark_eth_tx_broadcast, confirm_eth_tx, fail_eth_tx, mark_auth7702_active
  • Tables: withdrawal_request, sms_notification, balance_query, user_preferences
  • tests/smoke/test_register_pin_flow.sh against a live SpacetimeDB
  • tests/e2e/test_ussdclient_flows.py — 8 pytest cases driving the live HTTP bridge

Cleanup

  • Deleted unused PhoneWallet table
  • Stripped commented-out Swap code, unused SwapTable/USSDSessionTable aliases
  • Inlined _check_profile_exists / _check_session_exists

Test plan

  • cargo build --release clean
  • cargo test --lib → 36 passed
  • spacetime publish --project-path middleware gateway2 succeeds
  • ./tests/smoke/test_register_pin_flow.sh passes end-to-end
  • pytest tests/e2e/test_ussdclient_flows.py → 8/8 passed against live ussdclient_v2.py + SpacetimeDB

Deferred to Plan 2 (tracked in memory)

  • set_gateway_identity's owner check (ctx.sender != ctx.identity()) is unreachable for CLI callers — broadcaster reducers still deny-by-default, so no security regression, but the reducer is currently uncallable. Replace with one-time admin-claim or compile-time
    trusted identity.
  • PIN salt is Timestamp::to_string() — replace with rand::Rng (helper already exists in functions/pin.rs).
  • PIN brute-force lockout fields exist but validate_pin doesn't increment/check them. Wire up.
  • TxParams::encode() / TxType::signature() are empty stubs — real ABI encoding happens when the broadcaster lands.

🤖 Generated with Claude Code

117l11 and others added 20 commits April 17, 2026 22:47
Captures approved design for splitting the stack into four components
(ussdclient, middleware, broadcaster, smsclient) with Base Sepolia as
the demo chain, Pretium for fiat off-ramp, and Termii for SMS.

Covers middleware cleanup scope (real Keccak, FUNCTION_MAP removal,
typed UserIntent), new tables (withdrawal_request, sms_notification,
balance_query, user_preferences), eth_tx schema migration, broadcaster
worker patterns (lazy 7702 auth, row-lease double-submission guard),
and phased ordering through to architecture refinement.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
13 tasks covering: real Keccak selectors, bounded nick_auth_7702,
PhoneWallet deletion, eth_tx schema migration, new tables
(withdrawal_request, sms_notification, balance_query, user_preferences),
UserIntent typed parser, FUNCTION_MAP removal, handler refactors
(register, validate, cancel), broadcaster writeback reducers,
commented-code cleanup, and a register-flow smoke test.

69 TDD steps. Produces compiling middleware with new schema ready for
Plan 2 (broadcaster + E2E send ETH).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces keccak256_simple (a byte-XOR accumulator, not a hash) with
sha3::Keccak256::digest()[..4] in a new pure selector module. Verified
against known vectors for transfer, balanceOf, and swapExactTokensForTokens.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Tighten `pub use selector::*` to `pub use selector::keccak_selector`
so future additions to selector.rs don't silently escape to the
crate's public surface.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Caps Nick's Method derivation at 1M attempts and returns Result<_, AuthGenError>
so callers handle failure explicitly instead of risking an infinite loop.

Also fixes the placeholder chain_id = 0 in register/validate to 84532 (Base Sepolia).

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

Drops the `pub use hashing::*` wildcard in favor of explicit symbol
re-exports so future additions to hashing.rs do not silently leak to
the crate's public surface.

Replaces the raw `AuthGenError` Display text that was bubbling up to
the USSD screen with a user-appropriate "Service temporarily
unavailable" message, and logs the internal error detail via
log::error! for operators.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Superseded by EsimProfile. No callers used phone_wallet.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
eth_tx now uses auto-inc id as PK (was session_id), supporting multiple
txs per session (Withdraw needs escrow + refund tied to one session).

Adds: tx_type, block_number, gas_used, error_reason, processing_by,
processing_since lease fields used by the broadcaster (Plan 2).

TxType reduced to SendEth | WithdrawEscrow | WithdrawRefund. All three
move native ETH via Burner7702.execute(to, value, 0x) — no ABI
selector needed until Swap ships.

Deletes obsolete tests referencing removed Swap* types.

BREAKING: requires spacetime delete + republish.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…uery, user_preferences tables

These are consumed by Plan 2 (broadcaster) and later plans (SMS service,
balance worker, Pretium worker). Tables land first with no callers so
migrations can settle before we wire flows.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces scattered split('*') indexing in function handlers with a
single parse_intent(screen, data) returning a typed enum. Covered
by 8 unit tests (happy path + arity + unknown screen + invalid values).

Handlers migrate to consume UserIntent in Task 8 onward.

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

The lazy_static Mutex<HashMap<String, fn>> was cleared and rebuilt on
every USSD step — wasteful and race-prone. Replaced with a single
match statement in functions::dispatch::dispatch(fn_name, ctx, session).

USSDService::execute_fn now calls dispatch directly; load_function and
register_functions are removed. Drops the lazy_static crate dependency.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
register_pin and confirm_register_pin now call parse_intent and pattern-
match on the typed variant instead of indexing session.data.split('*').

Uses BASE_SEPOLIA_CHAIN_ID = 84532 constant for 7702 derivation.

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

validate_pin/phone/amount/token all consume typed UserIntent variants
from parse_intent instead of indexing parts[N]. This removes the
phone_position=3 branch that addressed the wrong index when the
cumulative input had only 2 segments.

validate_token becomes a no-op until Swap is in scope.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ConfirmDecision::Confirm → TxStatus::Submitted (broadcaster picks up)
ConfirmDecision::Cancel  → TxStatus::Cancelled

Previously only logged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- set_gateway_identity (module-owner only) registers the broadcaster's
  SpacetimeDB identity in app_config
- mark_eth_tx_processing (with 5-min row lease), mark_eth_tx_broadcast,
  confirm_eth_tx, fail_eth_tx handle the eth_tx state machine
- mark_auth7702_active flips auth_7702.status once the SetCode tx
  confirms on-chain

All reducers require ctx.sender == gateway_identity from app_config.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Deletes commented-out Swap struct, SwapStatus/SwapType enums, claim_swap
reducer stub, and unused SwapTable/USSDSessionTable aliases.

Inlines _check_profile_exists and _check_session_exists (one call each).

Git history preserves the deleted code for reference.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Exercises process_ussd_step through the full registration path (initial
dial → pick Register → enter PIN → confirm PIN) and asserts that
esim_profile, user_pin, and auth_7702 rows are created.

Runs against a live SpacetimeDB instance — not part of cargo test.
Prereq: middleware published via 'spacetime publish'.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- esim_profile/user_pin store phone without the leading '+'
  (normalize_phone_number strips it before insert)
- SpacetimeDB SQL rejects 'WHERE 1=1' and 'ORDER BY … LIMIT 1';
  use bare DELETE and drop the LIMIT clause
- row assertions now count PHONE_NORM hits across multi-line output
  instead of relying on tail -1

Verified: register_pin + confirm_register_pin produces one
esim_profile, user_pin, and auth_7702 row each.

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

Each of mark_eth_tx_processing, mark_eth_tx_broadcast, confirm_eth_tx,
and fail_eth_tx now bail out if the row's status is already Confirmed,
Failed, or Cancelled. Previously a stale lease after the 5-min window
could have let a late worker rewrite a terminal row.

Also tightens the smoke test:
- DELETE FROM auth_7702 is now scoped to the test phone's derived
  wallet (was unconditional)
- row-count greps anchor on quoted values so header rows cannot
  satisfy the assertion

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eight pytest cases that POST to ussdclient's /ussdeth endpoint and verify
both the USSD response text and the resulting SpacetimeDB rows. Each test
provisions a fresh phone via UUID so cases can run in any order.

Covered flows:
- initial dial (new user sees RegisterScreen)
- register -> confirm_register creates esim_profile + user_pin
- registered user sees MainScreen on next dial
- send-eth menu chain creates eth_tx(Pending)
- confirm flips status to Submitted; cancel flips to Cancelled
- bad PIN returns 'Invalid PIN'
- malformed phone returns 'Invalid phone number format'

Prereqs documented in module docstring; tests call `spacetime sql` for
assertions and cleanup, so the CLI must be authed against the local DB.

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

vercel Bot commented Apr 18, 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 18, 2026 10:21am
stk2eth-ussdgeth Error Error Apr 18, 2026 10:21am

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