Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Reseller Bootstrap: Zero-Float Mesh Formation (Implementation Plan)

Open
#239 3 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
25/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Quiet
Tech stack
go, playwright, react

Research direction

This is a multi-phase plan; start with Phase 1 in config_manager_config.go, config_schema.go, upstream_manager.go, vendor_element_manager.go, main.go, and upstream_session_manager/types.go. Read PR #235 first, since the vendor IE work depends on its TLV format, then add unit coverage for hop-count derivation and check the balance JSON and vendor IE tests. Done means hop_count is propagated as specified; the later phases require separate backend and captive-portal work.

Written by the indexing model from the issue text.

Description

enhancement

Reseller Bootstrap: Zero-Float Mesh Formation

Problem

A freshly configured TollGate in reseller mode has no internet, no upstream
session, and no ecash balance. When a customer pays, the reseller cannot
grant internet access because it has none to grant. The network cannot
bootstrap without pre-funding or trust.

Solution

The first customer's first proof is forwarded in full to the upstream
TollGate. No split, no swap, no margin on the first transaction. The
upstream authorizes the reseller's STA MAC, granting a data session. The
reseller now has internet. Every subsequent payment earns margin normally.

This works recursively — a reseller-of-a-reseller bootstraps the same way.
Each hop adds latency only on the first transaction (5-15s for payment
forwarding). After that, the reseller can reach the mint directly for
proof swaps.

No walled gardens. No whitelists. No pre-funding. No trust. The first
proof is the spark that lights the network.

Bootstrap Flow

First customer transaction (cold start)
  1. Customer connects to reseller WiFi, sees captive portal, pays 2 sats
  2. Reseller detects: no active upstream session → bootstrap mode
  3. Reseller forwards the ENTIRE proof to upstream via Nostr payment
  4. Upstream receives proof, validates at mint, authorizes reseller STA MAC
  5. Reseller's upstream session begins (2 MB at 1 sat/MB upstream rate)
  6. Reseller opens valve for customer: 1 MB (what 2 sats buys at reseller rate)
  7. Reseller retains 1 MB of prepaid upstream bandwidth as float

Customer UX during cold start (5-15s):

  • "Payment received"
  • "Establishing internet link..."
  • "Link established — N hops to gateway"
  • "Access granted!"
Second transaction (warm path)
  1. Customer pays 2 sats (renewal or new customer)
  2. Reseller HAS internet — reaches mint directly
  3. Reseller swaps proof at mint, splits: 1 sat upstream + 1 sat profit
  4. Sends 1 sat to upstream for 1 MB more
  5. Customer gets 1 MB

Every subsequent transaction: normal margin. The "cost" of cold start
was zero — the reseller didn't lose money, it just didn't earn margin
on transaction #1, and gained 1 MB of prepaid float.

Recursive multi-hop

A chain of TollGates A → B → C:

  1. Customer pays C (hop 2 reseller)
  2. C has no internet → forwards proof to B
  3. B has no internet → forwards proof to A
  4. A is the gateway → has internet, validates proof, authorizes B's MAC
  5. B now has internet → authorizes C's MAC
  6. C now has internet → opens valve for customer

Each hop adds one payment round-trip (~5-8s). A 3-hop chain cold-starts
in ~20s on the first transaction only.

Hop Count Propagation

Each TollGate advertises its hop distance from the internet gateway:

  • Direct gateway (has its own internet): hop_count = 0
  • Reseller of a gateway: hop_count = upstream.hop_count + 1

Propagated via:

  1. Vendor IE (802.11, for router-to-router discovery)
  2. NostR details event (kind 21023, for client-facing portal)
  3. Balance JSON (/balance endpoint, for status display)

Customer sees on balance page:

Connection active
2 hops to internet
0.8 MB remaining

Implementation Phases

Phase 1: Hop Count Propagation (backend)

Scope: Add hop_count field end-to-end through the backend.

  • Add hop_count int field to UpstreamManagerConfig and config schema
  • Add hop_count byte to vendor IE encoding (#235's TLV format, new type 0x03)
  • Add hop_count tag to NostR details event (kind 21023)
  • Add hop_count to balanceResponse JSON struct in main.go
  • UpstreamManager: when connecting to upstream, read its hop_count from
    vendor IE or probe response, set local = upstream + 1
  • Direct gateway: hop_count = 0 when reseller_mode = false
  • Config default: hop_count = 0 (updated at runtime by UpstreamManager)

Files touched: config_manager_config.go, config_schema.go,
upstream_manager.go, vendor_element_manager.go, main.go,
upstream_session_manager/types.go

Dependencies: PR #235 (vendor IE) must merge first (for the TLV encoding)

Testing: Unit test hop_count derivation logic. Router test: verify
hop_count appears in balance JSON and vendor IE on real hardware.

Estimate: 2-3 tasks

Phase 2: Bootstrap Passthrough Logic (backend)

Scope: First-proof-forward bootstrap in the merchant payment flow.

  • Add bootstrap_mode state to merchant/upstream session manager
  • Detect cold start: no active upstream session + reseller_mode = true
  • In bootstrap mode: forward entire received proof to upstream via Nostr
  • After upstream session established: set bootstrap_mode = false
  • Subsequent payments: normal proof swap + split flow
  • Add /bootstrap-status endpoint (or extend /balance):
    • bootstrap_phase: idle | forwarding | establishing | complete | error
    • hop_count: derived from upstream

Files touched: merchant/merchant.go, upstream_session_manager/session.go,
main.go (new endpoint or extended balance response)

Dependencies: Phase 1 (hop_count field exists in response structs)

Testing: Unit test bootstrap state machine. Integration test: mock
upstream, verify first proof forwarded whole, second proof split.

Estimate: 3-4 tasks

Phase 3: Bootstrap Status UI (frontend)

Scope: Customer-facing UI for bootstrap progress + hop count display.

  • New processing states in captive portal during bootstrap:
    • "Payment received"
    • "Establishing internet link..." (with hop count if >1)
    • "Link established — N hops to gateway"
    • "Access granted!"
  • Poll /balance (or /bootstrap-status) during bootstrap for phase updates
  • Balance page: add hop count display alongside remaining/usage
  • i18n strings for all new status messages

Files touched: captive portal React app (separate repo:
OpenTollGate/tollgate-captive-portal-site), locale files

Dependencies: Phase 2 (backend serves bootstrap phase data)

Testing: Playwright/visual test of bootstrap UI states

Estimate: 2 tasks

Phase 4: Pre-connect, Post-pay (optimization)

Scope: Eliminate cold-start latency for subsequent network formations.

  • Upstream TollGate: add reseller_grace_seconds config (default: 0 = off)
  • When grace > 0: upstream allows identified reseller STAs to connect and
    get a local IP without payment, for N seconds
  • Reseller: pre-connects to upstream WiFi immediately on boot
  • When first customer pays: upstream session already established,
    payment flows instantly
  • Grace window expires → upstream disconnects unpaid STA
  • Reseller can reconnect (grace restarts) but must pay to stay

Files touched: upstream_session_manager/, config_manager/,
nodogsplash configuration in packaging/

Dependencies: Phases 1-3 (bootstrap flow works end-to-end first)

Testing: Two-router test: cold start with grace enabled, verify
first customer experiences <2s latency instead of 5-15s

Estimate: 3-4 tasks

Key Design Decisions

  1. No walled gardens. The upstream does not whitelist mint URLs.
    The first proof goes directly to the upstream TollGate via Nostr,
    which has internet and can validate it.

  2. No proof splitting on cold start. The entire first proof is
    forwarded. Splitting requires a mint swap, which requires internet,
    which requires paying the upstream — circular dependency. Forwarding
    the whole proof breaks the cycle.

  3. Hop count is informational, not control. Display only. No
    automatic chain-depth limiting in v1. Operators can configure
    max_reseller_hops later if abuse becomes a concern.

  4. Vendor IE carries hop count. This lets scanning resellers see
    the hop depth before connecting, enabling price calculation
    (markup compounds per hop).

  5. Phase 4 is the goal, Phases 1-3 are stepping stones. The
    pre-connect grace window is the optimal UX, but the passthrough
    bootstrap must work first as the fallback when grace is not
    available or expires.

Open Questions

  • Should the reseller calculate its price dynamically from upstream
    price + configured markup, or keep static pricing? (Dynamic is
    correct but adds complexity.)
  • Should hop count be capped? (e.g., refuse to resell beyond hop 5)
  • What happens if the upstream rejects the forwarded proof (wrong mint)?
    Error message to customer: "This mint is not accepted by the upstream
    gateway. Try a different token."

Related PRs

  • #235 (vendor IE) — provides the TLV encoding framework
  • #232 / #231 (identity) — reseller identity is derived from merchant key
Dominant language
Go
Stars
12
Forks
14
Avg merge
1d 5h
Merged PRs (30d)
220

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from OpenTollGate/tollgate-module-basic-go

All issues in OpenTollGate/tollgate-module-basic-go

Similar issues

More Go issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.