From 727df8275d7d4bfc60c6d16b5df57b3eeb30033d Mon Sep 17 00:00:00 2001 From: aladi11isah Date: Thu, 24 Sep 2026 16:25:14 +0000 Subject: [PATCH 1/4] Implement Issue #302: Design community treasury-spending governance process - Create docs/treasury-spending-governance.md defining treasury spending governance - Specify valid proposal categories (audits, ecosystem dev, research, infrastructure, grants) - Document proposal process: 14-day RFC discussion, multisig approval, 14-day timelock - Include time-sensitive expedited path for emergencies (3-day timelock, 0k cap) - Define multisig approval thresholds and signer requirements - Establish quarterly transparency reports and post-execution accountability - Document treasury contract primitives needed from issue #37 - Coordinate with issue #112 (RFC process) and issue #114 (multisig design) --- docs/treasury-spending-governance.md | 213 +++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 docs/treasury-spending-governance.md diff --git a/docs/treasury-spending-governance.md b/docs/treasury-spending-governance.md new file mode 100644 index 0000000..577fbb6 --- /dev/null +++ b/docs/treasury-spending-governance.md @@ -0,0 +1,213 @@ +# Treasury Spending Governance Process + +**Issue:** [#302](https://github.com/stellar-vortex-protocol/vortex-contracts/issues/302) +**Status:** Proposed +**Last updated:** 2026-09-24 + +--- + +## 1. Overview + +This document defines the community process by which accumulated protocol-fee revenue in the treasury is governed and allocated. It is **not** a technical specification of the treasury contract itself (see issue #37 for that), but rather the governance layer—the human process and decision criteria—that the technical contract must enable. + +The treasury accumulates protocol fees (`0.05%` of filled intent volume) and slash proceeds (10% of slashed solver bonds). Both streams represent value extracted from the protocol ecosystem. How that value gets deployed back into the ecosystem—funding security audits, ecosystem development, incentive programs, etc.—is the decision space this process governs. + +--- + +## 2. Core Principles + +1. **Legitimacy through process, not mechanism alone:** The technical security of the treasury (a timelock, a multisig) is necessary but insufficient. A perfectly secure contract with no community input is technically safer but not more trustworthy. + +2. **Conservative default:** Until this process is tested and proven, funds accumulate in the treasury with **no spending authorized**. This is the safe initial stance and an explicit statement that process maturity, not just fund size, gates spending. + +3. **Transparency and accountability:** Every proposal, approval, and execution is documented publicly. The community can audit every decision and trace how fees flowed back into the protocol. + +4. **Community input before admin action:** Proposals follow an RFC-style discussion period (see section §4 and issue #112) before an admin even initiates a `propose_spending` transaction. This is a cultural commitment, not a technical gate—the contract can move faster than this process dictates, but the community process is the expected and endorsed path. + +--- + +## 3. Valid Spending Proposal Types + +A spending proposal is any use of treasury funds. Valid categories include: + +| Category | Examples | Constraints | +|----------|----------|-------------| +| **Security & Audits** | Smart contract audits, bug bounties, incident response | Must name the target (e.g. "Halborn audit of intent_settlement v2.0.1") and expected cost | +| **Ecosystem Development** | Solver integration incentives, user onboarding grants, integration-partner subsidies | Must define success metrics and duration (e.g., "6-month solver-adoption rebate program: $X per fill, up to $Y total") | +| **Research & Protocol Innovation** | Cross-chain proof optimization, new fee mechanisms, DAO governance tooling | Must cite a specific design doc or research goal, and a delivery target (e.g., "issue #190: Wormhole proof registry design implementation by Q1 2027") | +| **Community Infrastructure** | Monitoring dashboards, public RPC nodes, indexer deployment | Must specify maintenance owner and SLA (e.g., "public metrics dashboard maintained by Vortex Labs, 99% uptime SLA") | +| **Grants & Bounties** | Retroactive funding for past work, hackathon prizes, ongoing contributor stipends | Must include selection criteria and, for ongoing work, a review cadence (e.g., "quarterly contributor stipend review") | +| **Operations & Admin** | Legal entity fees, compliance costs, insurance | Must be itemized and limited to actual costs, not profit margins | + +**Out of scope:** Treasury funds **cannot** be: +- Diverted to private parties with no public accountability. +- Used for short-term price support or market manipulation. +- Allocated to issues not grounded in the Vortex Protocol's technical roadmap or community governance process. + +--- + +## 4. Proposal Process + +### 4.1 Discussion Phase (RFC-style, min. 14 days) + +Before any `propose_spending` transaction is initiated on-chain, the proposal must follow the RFC process defined in issue #112 (`docs/GOVERNANCE.md`): + +1. **Proponent opens a GitHub discussion** (or equivalent, per issue #112) with: + - Title: `[Treasury Proposal] {proposal name}` + - Category from §3 (or explain why a novel category applies) + - Requested amount (in USD or the native treasury token) + - Itemized justification with link(s) to supporting design docs or research + - Timeline: when the work starts, expected completion, and reporting intervals + - Success criteria: how the community will measure whether funds were well-spent + +2. **Community discussion window:** 14 days minimum (calendar days, not business days). Solvers, users, and other stakeholders can comment; the proponent refines the proposal in response. + +3. **Signal-check (optional, recommended):** Once discussion has stabilized, the proponent may request a reputation-weighted signal via the tool defined in issue #305 (off-chain signaling mechanism). This is **not a gate**—discussion happens first, signaling is a refinement—but it surfaces community preference before the admin commits on-chain. + +4. **Approval by multisig signers:** The admin (a Stellar multisig account per issue #114 / `docs/114-multisig-admin-design.md`) reviews the proposal once the discussion period ends and community sentiment is known. The admin makes a **final approval/rejection decision**, with reasoning (e.g., in a GitHub comment tying the approval to the discussion). + +### 4.2 On-Chain Timelock Phase (14 days) + +Once the admin approves off-chain, they call `propose_spending()` on the treasury contract with: +- Recipient address (the account receiving funds) +- Amount +- A URI pointing to the approved proposal (e.g., the GitHub discussion URL) + +The contract enforces a **14-day timelock delay** before the funds can be moved (matching the discussion period, signaling process maturity rather than urgency). + +During this window: +- The community has a final opportunity to review the on-chain proposal. +- Any errors or mismatches between the approved proposal and the on-chain execution are public and can be contested (via off-chain discussion; the admin can cancel if convinced a mistake was made). + +### 4.3 Execution Phase + +After the 14-day timelock expires, the multisig signers call `execute_spending()`, which transfers the approved amount to the recipient address on-chain. + +### 4.4 Time-Sensitive Exception (Expedited Path) + +Some spending needs are genuinely time-sensitive (e.g., funding an emergency security audit discovered during an incident). For these cases: + +1. **Expedited discussion:** A proponent can request an expedited RFC discussion (issue #112 defines approval criteria for expedited status—typically "credible external threat"). + +2. **Expedited multisig approval:** The admin reviews and approves on an accelerated timeline (no fixed minimum, but logged with reasoning). + +3. **Shortened timelock:** The contract supports a `propose_spending_expedited()` variant with a **3-day timelock** (vs. the standard 14 days) for emergency spending up to a **maximum of $50,000 USD equivalent per proposal**. This limit can be raised by governance (it's a parameter in issue #37's contract). + +4. **Post-execution report:** The admin **must** publish a post-execution report within 7 days explaining the emergency and outcome. + +**Rationale:** True emergencies (e.g., a discovered exploit) need faster response than a 14-day discussion period allows. The 3-day shortened timelock with a cap preserves the multisig-review gate while reducing delay. The post-execution accountability is mandatory to prevent this path from becoming routine. + +--- + +## 5. Approval Thresholds + +The treasury multisig account (issue #114 / `docs/114-multisig-admin-design.md`) is the sole authority for initiating spending. The signer threshold is determined by the multisig configuration at deployment time. + +**Recommended configuration for mainnet:** +- **Signers:** 5 representatives (team, auditors, community delegates, etc., per governance process in issue #112) +- **Threshold:** 3-of-5 (a supermajority, preventing any single signer from acting alone; not so high that a lost key blocks all spending) + +Each signer is expected to: +1. Review the proposal for alignment with the treasury-spending categories (§3). +2. Validate that the community discussion period was observed. +3. Confirm that the proponent's justification is grounded and realistic. +4. Sign the `propose_spending` transaction only if convinced the spend is legitimate. + +The multisig account itself is Stellar-native (ed25519 signers), so each signer uses standard Stellar tooling (a keypair or hardware wallet). No custom on-chain voting logic is required. + +--- + +## 6. Reporting and Transparency + +### 6.1 Treasury Balance & Inflow Report (Quarterly, Public) + +Every quarter (January, April, July, October), a treasury report is published that includes: + +- **Opening balance** (in USD equivalent, using end-of-quarter spot rates) +- **Inflows** (protocol fees, slash proceeds, any other revenue) +- **Outflows** (each approved spending, with link to the approved proposal) +- **Closing balance** +- **Agenda for next quarter** (any proposals in discussion, expected timeline) + +This report is pinned in the GitHub `docs/` folder (e.g., `docs/treasury-reports/Q4-2026.md`) and cross-referenced from the main `README.md`. + +### 6.2 Proposal Tracking + +A public checklist (e.g., `docs/treasury-proposals.md` or a pinned GitHub discussion) lists: +- All active and past proposals (title, amount, status: approved/rejected/executed) +- Links to the discussion and on-chain proposal URI +- Current date and expected execution date (if approved but not yet executed) + +### 6.3 Post-Execution Report (Per Proposal) + +After funds are disbursed, the recipient **must** publish: +- A brief report (within 30 days of execution) confirming receipt of funds +- Progress toward the stated success criteria at 30, 60, and 90 days post-execution +- A final report at project completion + +For ongoing work (e.g., a quarterly contributor stipend), reports happen on the cadence defined in the proposal (typically quarterly). + +--- + +## 7. Treasury Technical Primitives (Issue #37 Dependency) + +For this governance process to be enforceable on-chain, issue #37's treasury contract must expose: + +| Primitive | Used for | Details | +|-----------|----------|---------| +| `propose_spending(recipient, amount, proposal_uri)` | Initiate a spending proposal | Requires admin multisig authorization; stores proposal and timelock deadline | +| `propose_spending_expedited(recipient, amount, proposal_uri, emergency_justification)` | Emergency spending | Requires admin multisig authorization; uses 3-day timelock instead of 14 days; capped at $50k per proposal | +| `execute_spending(proposal_id)` | Finalize a proposal after timelock expires | Transfers funds to recipient; succeeds only if timelock deadline has passed and recipient address is valid | +| `cancel_spending(proposal_id)` | Abort a proposed spending before execution | Requires admin multisig authorization; called if a mistake is discovered during the timelock window | +| `get_spending_proposals(start, limit)` | Enumerate all proposals (active and historical) | Paginated read; allows external indexing and reporting | +| `get_spending_proposal_details(proposal_id)` | Read a specific proposal's state and timeline | Returns recipient, amount, proposal_uri, timelock_deadline, status (proposed/executed/cancelled) | +| `get_treasury_balance()` | Read total treasury balance | Sum of all fees and slashes less all executed spending; used in quarterly reports | + +The contract **does not** enforce this governance process (discussion periods, approval thresholds, vetting criteria). It only provides the timelock and multi-sig gating primitives. The governance process lives off-chain in community practice and documented norms. + +--- + +## 8. Governance Evolution & Amendments + +This process can be amended by the community via the RFC process in issue #112. A proposal to change the treasury spending rules follows the same discussion and approval process as any other proposal, but: + +1. The change is documented in a new version of this document (with a date stamp). +2. The change applies to future proposals, not retroactively to approved or executing proposals. +3. A summary of the change is added to `CHANGELOG.md`. + +--- + +## 9. Transition & Initial State + +**Until this process is adopted and tested:** + +- No spending is authorized. +- Protocol fees and slash proceeds accumulate in the treasury contract. +- The admin (multisig) can receive fee_recipient updates (issue #115) to route new fees to a designated address, but **no draws from accumulated funds** occur. + +**Once this process is ratified (via a governance proposal under issue #112):** + +1. The treasury contract (issue #37) is deployed with the first 6 months designated as a **pilot phase**. +2. Proposals in the pilot phase are expected to be small, conservative, and well-researched (e.g., funding a specific, well-scoped audit). +3. After 6 months, the community reviews the pilot: Did the process work? Are amendments needed? Based on feedback, the process either stabilizes or is refined. + +This conservative ramp-up ensures the community has time to build confidence in both the process and the technical contract before large sums are deployed. + +--- + +## 10. Related Issues & Coordination + +- **Issue #37 (Treasury contract):** This governance process depends on issue #37's timelock and multisig primitives. Coordinate to ensure the contract design supports the human process defined here. +- **Issue #112 (RFC governance process):** All treasury spending proposals follow issue #112's discussion-phase norms. +- **Issue #114 (Multisig admin design):** The treasury is governed by a Stellar multisig account per this design. +- **Issue #305 (Off-chain reputation-weighted signaling):** Provides an optional signal mechanism to measure community preference before multisig approval. + +--- + +## 11. References + +- `SECURITY.md` — "Protocol fees" as an Asset at Risk (this process is the answer to that trust problem) +- `docs/114-multisig-admin-design.md` — Multisig admin key architecture +- `GOVERNANCE.md` (issue #112) — Community RFC process for all proposals +- Issue #37 — Treasury contract design & implementation +- Issue #305 — Off-chain reputation-weighted signaling tool From 964f2b329818c018141c2065329076fdd380dc10 Mon Sep 17 00:00:00 2001 From: aladi11isah Date: Thu, 24 Sep 2026 16:26:36 +0000 Subject: [PATCH 2/4] Implement Issue #303: Build off-chain Verified Solver vetting and endorsement program - Create docs/verified-solver-program.md defining solver vetting criteria and process - Vetting is informational only; does not gate on-chain participation - Application requirements: identity disclosure, track record, operational-security attestation, references - Vetting review by 2+ independent reviewers (target 10 business days) - Revocation process for slash events, misrepresentation, or changed practices - Create docs/SOLVERS.md as public tracker of verified solvers - Distinguish community-vetted from on-chain eligible solvers - Integration points: issue #13 (solver registry), issue #41 (leaderboard), issue #57 (tier badge) --- docs/SOLVERS.md | 205 ++++++++++++++++++++ docs/verified-solver-program.md | 329 ++++++++++++++++++++++++++++++++ 2 files changed, 534 insertions(+) create mode 100644 docs/SOLVERS.md create mode 100644 docs/verified-solver-program.md diff --git a/docs/SOLVERS.md b/docs/SOLVERS.md new file mode 100644 index 0000000..84dc0f6 --- /dev/null +++ b/docs/SOLVERS.md @@ -0,0 +1,205 @@ +# Verified Solvers + +Last updated: 2026-09-24 + +--- + +## Overview + +This page distinguishes **on-chain eligible** solvers from **community-vetted** solvers in the Vortex Protocol. + +- **On-chain eligible:** Any solver that passes `is_solver_eligible()` in the `intent_settlement` contract (meets minimum bond, passes reputation checks). These solvers can accept intents and participate fully, regardless of vetting status. +- **Community-vetted:** A subset of on-chain eligible solvers who have completed the Verified Solver Program (see `docs/verified-solver-program.md`). Vetting is an optional, informational trust signal, not a permission gate. + +See [`docs/verified-solver-program.md`](./verified-solver-program.md) for the full vetting criteria and process. + +--- + +## On-Chain Eligible (Not Community-Vetted) + +All registered solvers with `is_solver_eligible() == true` that are **not** listed in the Community-Vetted section below can accept intents and participate fully in the protocol. + +**Data source:** Issue #13 enumerable solver registry. The authoritative, real-time list of eligible solvers is available on-chain via `list_solvers(start, limit)`. + +To check if a specific solver address is eligible: +```bash +# Example: Query a deployed contract for solver eligibility +stellar contract invoke --id --source -- \ + is_solver_eligible --solver +``` + +--- + +## Community-Vetted Solvers + +| Solver Address | Operator Name | Verified On | Status | Notes | +|---|---|---|---|---| +| *(none yet)* | *(accepting applications)* | — | — | First vetting cohort to be added as applications are approved. | + +--- + +## Recently Revoked + +| Solver Address | Operator Name | Revoked On | Reason | +|---|---|---|---| +| *(none yet)* | — | — | — | + +--- + +## Vetting Committee & Reviewers + +**Current vetting committee:** +- *(To be appointed during governance phase; see issue #112)* + +This committee oversees new applications, revocations, and process improvements. Committee membership is public and updated here when changes occur. + +--- + +## How to Apply for Vetting + +### Step 1: Prepare Your Application + +Gather the following information (see [`docs/verified-solver-program.md`](./verified-solver-program.md#9-application-template) for detailed requirements): + +1. **Identity & Organization:** + - Legal name or organization name + - Jurisdiction (if registered) + - Point of contact (email/Discord) + +2. **Track Record:** + - Off-Vortex history: other protocols/platforms where you've operated + - On-Vortex history: your solver address, registration date, approximate volume, any slash events + +3. **Operational-Security Attestation:** + - A written statement (500–1500 words) on key management, incident response, fund custody, and monitoring + +4. **References:** + - Two or more references from past engagements (with contact info) + +### Step 2: Submit via GitHub + +1. Go to [GitHub Issues](https://github.com/stellar-vortex-protocol/vortex-contracts/issues). +2. Click **New Issue**. +3. Use this template: + +```markdown +# [Verified Solver Application] Your Operator Name + +## Identity & Organization + +- **Operator Name:** +- **Jurisdiction:** +- **Point of Contact:** +- **Website/Social:** + +## Track Record + +### Off-Vortex History +- Protocol 1: [Dates, activities, references] +- Protocol 2: [Dates, activities, references] + +### On-Vortex History +- Solver address: `G...` +- Registration date: YYYY-MM-DD +- Approximate volume: +- Slash events: + +## Operational-Security Attestation + +[Your 500–1500 word statement on key management, incident response, fund custody, monitoring, and commitment to the vetting program] + +## References + +1. **Name, Role, Contact** + - Description of engagement + +2. **Name, Role, Contact** + - Description of engagement + +--- + +**Applicant confirmation:** I affirm that the above is accurate and agree to the terms in `docs/verified-solver-program.md`. +``` + +### Step 3: Vetting Review + +1. A **vetting coordinator** will acknowledge your application within 3 business days. +2. Two independent **reviewers** will assess your application (target: 10 business days). +3. You'll receive written feedback and a decision (approved, rejected, or pending further review). + +For details on the review criteria and appeals process, see `docs/verified-solver-program.md` §4. + +--- + +## Vetting Criteria + +At a glance: + +- **Identity:** Must be verifiable (individual with known identity or legal entity). +- **Track record:** Evidence of responsible operation on other platforms (solvers with history are lower risk; new operators are accepted if other factors are strong). +- **Operational security:** A credible commitment to key management, incident response, and fund custody (transparency is valued over perfection). +- **References:** Peer endorsement from past engagements. + +Vetting is **not**: +- A technical security audit. +- A performance guarantee. +- A permission for on-chain participation (that's determined by `is_solver_eligible()` alone). + +--- + +## Revocation & Appeals + +A solver's vetting status can be revoked if: + +- **Slash events:** Multiple slashes (3+ in 30 days) may trigger revocation after review. +- **Misrepresentation:** False identity, history, or operational practices discovered after vetting. +- **Changed practices:** Material reduction in operational security without updating the attestation. + +**Revocation process:** +1. A reviewer flags the solver and gives them 7 days to respond. +2. If no satisfactory response, the solver is removed and notified. +3. Reapplication is possible after 6 months or after circumstances change. + +**Appeals:** A rejected or revoked solver can appeal once after 6 months. + +For full details, see `docs/verified-solver-program.md` §5. + +--- + +## Vetting Status vs. On-Chain Eligibility + +| | On-Chain Eligible | Community-Vetted | +|---|---|---| +| **Can accept intents?** | ✅ Yes (via `accept_intent`) | ✅ Yes (via `accept_intent`) | +| **Can fill intents?** | ✅ Yes (via `fill_intent`) | ✅ Yes (via `fill_intent`) | +| **Can earn reputation?** | ✅ Yes (via volume/slashes) | ✅ Yes (via volume/slashes) | +| **Who determines eligibility?** | On-chain contract logic (`is_solver_eligible`) | Community reviewers (off-chain) | +| **Impacts on-chain behavior?** | ❌ No (vetting is informational) | ❌ No (vetting is informational) | + +**Key point:** Vetting does not gate on-chain participation. It is a trust signal for users and integrators, not a permission mechanism. + +--- + +## Integration & Reference + +- **In `docs/solver-integration-guide.md`:** Recommend that new solvers consider the vetting program as a way to build trust with users. +- **In `docs/solver-registry-design.md`:** Note that vetting is a complementary trust signal to on-chain reputation scores. +- **In leaderboard tools (issue #41):** Optionally display a "verified" badge or filter option. + +--- + +## History & Process Updates + +- **2026-09-24:** Vetting program announced and accepting applications. +- *(Future entries will document changes, revocations, and process refinements.)* + +For governance of the vetting program itself, see issue #112 (RFC governance process). + +--- + +## Questions? + +For questions about: +- **Vetting criteria or process:** See `docs/verified-solver-program.md`. +- **On-chain solver eligibility:** See `intent_settlement/src/lib.rs` (`is_solver_eligible`). +- **Governance and appeals:** See issue #112. diff --git a/docs/verified-solver-program.md b/docs/verified-solver-program.md new file mode 100644 index 0000000..7a2c44e --- /dev/null +++ b/docs/verified-solver-program.md @@ -0,0 +1,329 @@ +# Verified Solver Program + +**Issue:** [#303](https://github.com/stellar-vortex-protocol/vortex-contracts/issues/303) +**Status:** Proposed +**Last updated:** 2026-09-24 + +--- + +## 1. Overview + +The Verified Solver Program is an **off-chain, human-vetted endorsement** of solver operators. It is **explicitly not** a permission or ranking mechanism, and it imposes no on-chain restrictions; instead, it provides users, integrators, and other solvers with an additional trust signal beyond the mechanical, on-chain reputation scores defined in `compute_reputation_score()` and `is_solver_eligible()` (`intent_settlement/src/lib.rs`). + +**What this is not:** +- **Not a filter on `accept_intent()`:** Any solver that passes `is_solver_eligible()` can accept intents; vetting does not gate on-chain participation. +- **Not a reputation tier badge (issue #57):** That issue defines on-chain, score-derived tier indicators; this is a separate, off-chain human-judgment layer. +- **Not a ranking or leaderboard (issue #41):** The leaderboard is a measurement tool; this program is a vetting/endorsement tool. + +**What this is:** +- An attestation that a solver operator has disclosed their identity/organization, provided references, and committed to operational-security practices. +- A public list distinguishing "on-chain eligible" solvers from "community-vetted" solvers. +- A process for revoking vetting status if a solver's conduct changes (e.g., after a slash event or discovered misrepresentation). + +--- + +## 2. Core Principles + +1. **Falsifiable vetting criteria:** Approval is based on concrete, verifiable claims (e.g., "operates under a named legal entity," "has run a solver on protocol X for N months"), not subjective trust. + +2. **Clarity of boundaries:** The public list explicitly separates "on-chain eligible" from "community-vetted," so users never confuse these signals or mistake vetting for permission. + +3. **Revocation is explicit:** If a solver's status should be withdrawn (due to a slash, misrepresentation, or changed operational practices), there is a documented process and public explanation. A program without revocation loses credibility. + +4. **Independence:** Vetting review is conducted by one or more reviewers (not the Vortex Labs core team alone) to avoid the appearance of gatekeeping. Reviewers are named and their role is public. + +5. **Transparency:** All applications (approved, rejected, and pending) are tracked publicly so the vetting process is auditable. + +--- + +## 3. Application Requirements + +A solver operator applying for community vetting must submit (via a GitHub issue or other public venue designated by issue #112) the following: + +### 3.1 Identity & Organizational Information + +- **Full name or organization name** under which the solver operates. +- **Registered legal entity** (if applicable; e.g., LLC, GmbH, etc.): + - Country/jurisdiction. + - Registration number or public URL (e.g., corporate registry link). + - Beneficial owner(s) (if applicable). +- **Point of contact:** Email and/or Discord handle for communication. +- **Public website or social media** (if any) where solvers can learn more about the operator. + +**Why:** Establishes that the operator is identifiable and accountable, not anonymous. Does not require DAO or enterprise structure—a solo operator with a verifiable identity is acceptable. + +### 3.2 Operational History + +- **Track record predating Vortex Protocol:** + - Other protocols/platforms where the operator has run bots, solvers, or market-making services (e.g., "ran a solver on protocol X from Month/Year to Month/Year"). + - Links to public evidence (e.g., a GitHub account, published research, community references). + - **Rationale:** Solvers with a history of responsible operation elsewhere are lower-risk than brand-new operators, even if their Vortex-specific reputation is still building. + +- **Activity on Vortex Protocol:** + - How long the solver's address has been registered on-chain. + - Approximate volume filled to date (or range, if exact data is private). + - Any slash events or on-chain disputes (disclosed transparently). + +### 3.3 Operational-Security Self-Attestation + +A written statement (500–1500 words) addressing: + +- **Key management:** How the solver stores and rotates signing keys. (E.g., "hardware wallet for production keys," "multi-sig approval for fund transfers," "key rotation quarterly.") No need to disclose the actual key structure, but enough detail that a reviewer can assess whether the operator takes this seriously. + +- **Incident response:** If a key were compromised, what is the procedure? (E.g., "pause all activity, coordinate with Vortex admins via designated emergency contact, rotate keys.") + +- **Fund custody:** Where are solver-controlled funds held? (E.g., "in a multi-sig account," "in a custodian like Fireblocks," "in a cold storage account accessed only for withdrawals.") Reassurance, not perfection—even a single signer account is acceptable if the operator is transparent about it. + +- **Monitoring and alerting:** How does the solver detect a failed fill or other anomaly? (E.g., "automated monitoring of fill window expiry, Slack alerts on slash event.") + +- **Public commitment:** A statement affirming that the operator agrees to the values listed in §2 (falsifiable criteria, transparency, revocation clause). + +**Why:** This is not a security audit. It is a signal that the operator has thought about these risks and is willing to be held accountable for their practices. A one-paragraph answer is acceptable; opacity is the red flag, not perfection. + +### 3.4 References + +- **Two or more references** from the operator's past work: + - A protocol lead, auditor, or other solver from a previous engagement. + - A public link or contact information for the reference. + - Short description of what the reference can attest to (e.g., "worked with operator on protocol X; can confirm consistent, responsible operation from 2023–2025"). + +**Why:** Peer references are lightweight and add credibility without requiring formal credentials. + +--- + +## 4. Vetting Review Process + +### 4.1 Application Intake & Triage + +1. Applicant submits via a designated GitHub issue template (see §9 for template). +2. A **vetting coordinator** (a community member or Vortex Labs designate) acknowledges receipt within 3 business days and confirms the application is complete. +3. If incomplete, the coordinator requests missing information. If complete, the application enters review. + +### 4.2 Review & Decision (Target: 10 business days) + +1. **Two independent reviewers** (not the same person) are assigned. Reviewers should be: + - Vortex Protocol contributors (core team, auditors, integrators) or trusted community members. + - Named publicly so conflicts of interest can be disclosed. + - Not the solver's employer (to avoid appearance of favoritism). + +2. Each reviewer assesses: + - **Identity verification:** Is the legal entity or individual real and traceable? + - **References:** Did the references respond? Do they endorse the applicant? + - **Operational-security statement:** Is it credible? Does it suggest genuine care? + - **On-chain history:** If the solver has been active on Vortex, is the activity normal and consistent with their stated practices? + - **Red flags:** Any slash events, governance proposals to revoke vetting, or public disputes? + +3. **Approval decision:** + - If both reviewers approve → solver is vetted. + - If one approves, one rejects → escalation to a third reviewer or vetting committee (see §4.3). + - If both reject → application is denied; applicant may reapply in 6 months or after addressing specific feedback. + +4. **Feedback:** Accepted or rejected, the applicant receives written feedback (2–3 sentences) explaining the decision. + +### 4.3 Escalation & Appeals + +If a decision is split or contentious: + +1. A **vetting committee** (3–5 trusted reviewers named publicly) makes a final decision. +2. The committee's reasoning is published, allowing the community to audit the decision. +3. A denied applicant can appeal once after 6 months if they believe material new information changes the decision. + +--- + +## 5. Vetting Revocation + +A vetted solver's status can be revoked if: + +1. **Slash event:** A solver incurs a slash (10% bond penalty for missing a fill window). The on-chain event is public; the vetting coordinator flags it and a reviewer decides whether revocation is warranted. + - **Threshold:** A single slash does not trigger automatic revocation, but it triggers a review conversation. Multiple slashes (e.g., 3+ in 30 days) or a pattern of slashing may warrant revocation. + +2. **Misrepresentation:** If a solver's disclosed identity, history, or operational practices are found to be false or misleading after vetting, revocation is recommended. + +3. **Changed operational practices:** If a vetted solver materially changes their key management or fund custody (e.g., moves to a less secure approach) and refuses to update their attestation, revocation can be proposed. + +4. **Governance proposal:** Any community member can propose revoking a solver's vetting status (following the RFC process in issue #112, if that process applies to vetting changes). A proposal is treated like a vetting appeal: evidence is gathered, and a decision is made by a reviewer or committee. + +### 5.1 Revocation Process + +1. A reviewer or community member flags the solver for potential revocation. +2. The vetting coordinator notifies the solver and gives them 7 calendar days to respond. +3. If the solver provides a satisfactory explanation (e.g., "the slash was due to a network issue and I've updated my monitoring since"), the review is closed. +4. If no satisfactory response, a reviewer makes a revocation decision, with public reasoning. +5. The solver is removed from the Verified Solvers list and notified. They may reapply after 6 months if circumstances change. + +**Transparency:** Every revocation is logged (with a brief reason) in the public list, so the community understands that the vetting program has teeth and is not a rubber stamp. + +--- + +## 6. Public Verified Solvers List + +A public file (`docs/SOLVERS.md` or a page consuming issue #13's on-chain enumerable solver list) maintains: + +### 6.1 Format + +```markdown +# Verified Solvers + +Last updated: 2026-10-15 + +## On-Chain Eligible (Not Community-Vetted) + +All registered solvers (`is_solver_eligible() == true`) that are **not** listed below are on-chain eligible but not yet community-vetted. They can accept intents and participate fully in the protocol. + +**Data source:** issue #13 enumerable solver registry (updated from on-chain `list_solvers()` every 24 hours). + +--- + +## Community-Vetted Solvers + +| Solver Address | Operator Name | Verified On | Vetting Expires | Notes | +|---|---|---|---|---| +| `GXXXXXX...` | Example Solver LLC | 2026-09-15 | Annual review required | Operates across 3 protocols; multi-sig fund custody | +| `GYYYY...` | Solo Operator Alice | 2026-08-20 | Annual review required | Track record on protocol X; self-custodied; slash-free record | + +--- + +## Recently Revoked + +| Solver Address | Operator Name | Revoked On | Reason | +|---|---|---|---| +| `GZZZZ...` | Former Solver Inc | 2026-09-10 | Multiple slash events (3 in 7 days) and unresponsive to review | + +--- + +## Vetting Criteria + +See [`docs/verified-solver-program.md`](./verified-solver-program.md) for full details. + +## How to Apply + +1. Open a GitHub issue with title `[Verified Solver Application] Your Operator Name`. +2. Follow the template in [`docs/SOLVERS.md#Application-Template`](./SOLVERS.md#Application-Template). +3. A reviewer will contact you within 3 business days. + +--- + +## Reviewers & Governance + +**Current Vetting Committee:** +- Alice (Vortex Labs, lead coordinator) +- Bob (Independent auditor) +- Carol (Community contributor) + +This committee meets quarterly to review new applications, revocation proposals, and process improvements. + +**Process updates:** Changes to this vetting program follow issue #112 (RFC governance process). +``` + +### 6.2 Update Cadence + +- **New verifications:** Listed within 48 hours of approval. +- **Revocations:** Logged within 48 hours with reason. +- **Annual review:** Every 12 months, vetted solvers' vetting status is reviewed (no new application needed, but continued compliance with the attestation is expected). + +### 6.3 Distinction from On-Chain Eligibility + +The list **explicitly states:** +> "All registered solvers (`is_solver_eligible() == true`) that are not listed below are on-chain eligible but not yet community-vetted." + +This phrasing ensures users never mistake eligibility for vetting, and it celebrates the on-chain data as the source of truth for who can participate, while the vetting list is purely informational. + +--- + +## 7. Vetting Does Not Gate On-Chain Participation + +**Critical:** This program is informational and reputational only. It does **not** modify `accept_intent()` or any other on-chain logic. + +A solver who is **not** vetted: +- ✅ Can still call `accept_intent()` and fill intents. +- ✅ Has the same fill-window bonuses as a vetted solver (based on issue #57 tier, not vetting status). +- ✅ Can still earn reputation and accumulate volume. + +A solver who is **rejected** or **revoked:** +- ❌ Cannot be re-approved for 6 months (for rejected applicants) or after a review period (for revoked solvers). +- ✅ Can still call `accept_intent()` and fill intents on-chain (vetting revocation does not disable on-chain access). + +**Rationale:** The on-chain protocol is open and permissionless. Vetting is an optional trust signal, not a gate. + +--- + +## 8. Coordination with Other Issues + +- **Issue #13 (Enumerable solver registry):** When issue #13 ships, the Verified Solvers list will cross-reference on-chain addresses from issue #13's enumerable list for easy lookup. +- **Issue #41 (Leaderboard tool):** The leaderboard can display a "verified" badge or filter alongside reputation scores, giving users a multi-signal view. +- **Issue #57 (Reputation tier badge):** Tier badges are on-chain and score-derived; vetting is off-chain and human-judged. Both are presented to users, but they answer different questions. +- **Issue #112 (RFC governance process):** Applications and revocations follow issue #112's discussion and proposal norms if they become contentious. + +--- + +## 9. Application Template + +```markdown +# [Verified Solver Application] Your Operator Name + +## Identity & Organization + +- **Operator Name:** (individual or legal entity name) +- **Jurisdiction:** (country/registration info if applicable) +- **Point of Contact:** (email/Discord) +- **Website/Social:** (optional) + +## Track Record + +### Off-Vortex History +- Protocol/platform 1: Dates, activities, references +- Protocol/platform 2: Dates, activities, references + +### On-Vortex History +- Solver address: `GXXXXXX...` +- Registration date: YYYY-MM-DD +- Approximate volume: (range acceptable) +- Slash events: (none / describe any) + +## Operational-Security Attestation + +[500–1500 words addressing key management, incident response, fund custody, monitoring, and public commitment] + +## References + +1. **Name, Role, Contact Info** + - Description of past engagement + +2. **Name, Role, Contact Info** + - Description of past engagement + +--- + +**Applicant signature/confirmation:** I affirm that the above information is accurate and I agree to the terms in `docs/verified-solver-program.md`, including revocation for misrepresentation. +``` + +--- + +## 10. Initial State & Pilot + +**Phase 1 (Months 1–3 after adoption):** +- The vetting program is announced and open to applications. +- Reviewers are named and trained on the criteria. +- The first 3–5 applications are expected (conservative, early adoption). +- The program operates as described in §1–§9. + +**Phase 2 (Months 4–12):** +- Vetting is routine; the list grows to 10–20 verified solvers. +- Revocation triggers are tested (e.g., first revocation due to slash event). +- Community feedback is gathered on the criteria and process. + +**Annual review (Year 2):** +- Lessons learned from Phase 1–2 are documented. +- The program is refined (if needed) via issue #112's RFC process. +- Vetting expiration is introduced for long-standing verified solvers (e.g., annual recertification). + +--- + +## 11. References + +- `docs/solver-integration-guide.md` — Integration guide (cross-reference vetting in the "finding a solver" section). +- `docs/solver-registry-design.md` — Solver registry design (cross-reference vetting as a complementary trust signal). +- Issue #13 — Enumerable solver registry (on-chain source of truth for solver addresses). +- Issue #41 — Leaderboard tool (can display vetting status alongside reputation scores). +- Issue #57 — Reputation tier badge (on-chain tier, distinct from off-chain vetting). +- Issue #112 — RFC governance process (appeals and revocation proposals follow this process). From 4c6a9809d078a7c080f5f03a85d2560c6dce481a Mon Sep 17 00:00:00 2001 From: aladi11isah Date: Thu, 24 Sep 2026 16:27:51 +0000 Subject: [PATCH 3/4] Implement Issue #304: Consolidate scattered roadmap notes into public ROADMAP.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create comprehensive ROADMAP.md organizing all forward-looking work by theme - Themes: cross-chain proof, solver reputation, governance/treasury, monitoring, contract maintenance, protocol parameters, security, integration, research - Map scattered items from SECURITY.md, design docs, and README into status tracking - Use clear status indicators: 🔄 In Progress, 🎯 Not Started, 🔍 Under Research, ✅ Shipped, 🚫 Out of Scope (v1) - Document known limitations and mitigation timelines - Cross-reference each item to related issues and design docs - Update README.md to link to ROADMAP.md as primary reference --- README.md | 8 +- ROADMAP.md | 214 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+), 5 deletions(-) create mode 100644 ROADMAP.md diff --git a/README.md b/README.md index 9ed5afa..f269f4e 100644 --- a/README.md +++ b/README.md @@ -548,11 +548,9 @@ def compute_intent_id(user_address: str, src_chain: str, src_amount: int, timest ## Roadmap -- [x] **Contract test suite** — `soroban_sdk` testutils coverage for the full intent - lifecycle, solver bonding/slashing, admin controls, pause, and storage TTL - management -- [~] **Solver registry contract** — tier lookup + perk schedule shipped and wired into `accept_intent` / `slash_solver` (#197); score-gated promotion, staking, reputation NFT, dispute resolution still to do (#186) -- [ ] **Cross-chain proof verification** — verify source-chain tx on-chain via Stellar oracle / messaging infra +For a comprehensive, thematic view of the protocol's forward-looking work—cross-chain proof verification, solver reputation infrastructure, community governance, observability, and more—see the public [**ROADMAP.md**](./ROADMAP.md). + +That document consolidates scattered follow-up notes and roadmap items from design docs and `SECURITY.md` into a single, community-visible view organized by theme and status (in progress, not started, under research, shipped, out of scope for v1). --- diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..f3aa371 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,214 @@ +# Vortex Protocol Public Roadmap + +**Last updated:** 2026-09-24 + +This document consolidates forward-looking work, deferred features, and known limitations scattered across `SECURITY.md`, design docs, and the README into a single, community-visible roadmap. It is a **thematic, high-level view** of what the team considers important future work, organized by category and current status. + +**Status legend:** +- 🔄 **In Progress:** Active development or design underway. +- 🎯 **Not Started:** Scoped and ready to implement; awaiting resources or dependencies. +- 🔍 **Under Research:** Still in design/spike phase; requirements not yet final. +- ✅ **Shipped:** Implemented and live on mainnet (or testnet). +- 🚫 **Out of Scope (v1):** Deferred to a future release; design exists but not a priority for v1. + +--- + +## 1. Cross-Chain Proof & Verification + +**Goal:** Cryptographic verification of source-chain deposits using Wormhole or similar, to replace the current economic-only trust model. + +| Item | Status | Details | Issue(s) | +|------|--------|---------|----------| +| Proof Registry contract & interface | 🎯 Not Started | Contract to store and serve verified Wormhole VAAs; on-chain reference for `fill_intent(require_proof=true)` | #190, #124 | +| Proof verification integration with `fill_intent` | 🔄 In Progress | Optional on-chain gate: `fill_intent(..., require_proof=true)` checks the `ProofRegistry` before allowing a fill | #190 | +| Proof mismatch fallback & recovery | 🔄 In Progress | Handling for edge cases: proof arrives late, is invalid, or conflicts with a fill | #129 | +| Cross-chain oracle pricing (cross-token value comparison) | 🔍 Under Research | Currently slash amounts assume same-token or admin-set minimums; a price oracle would enable true cross-token comparisons | SECURITY.md, #193 | +| Multi-chain proof aggregation research | 🚫 Out of Scope (v1) | Extending beyond Wormhole to other proof sources; deferred until v1 stability is proven | | + +**Coordination:** Issue #124 is the main implementation tracker. See also `docs/124-proof-verification-interface.md` for design. + +--- + +## 2. Solver Reputation & Governance Infrastructure + +**Goal:** Establish on-chain solver reputation tracking and off-chain governance signals to enable community-driven solver oversight. + +| Item | Status | Details | Issue(s) | +|------|--------|---------|----------| +| Solver Registry contract & enumerable list | ✅ Shipped | On-chain registry with paginated `list_solvers(start, limit)` for real-time solver discovery | #13, #197 | +| Solver reputation score computation | ✅ Shipped | `compute_reputation_score` based on fills completed, fills failed, and total volume | #41, #197 | +| Reputation-tier badge (on-chain, score-derived) | 🎯 Not Started | NFT-style on-chain tier display (Unranked / Bronze / Silver / Gold / Platinum) based on reputation score | #57 | +| Leaderboard & ranking tool (off-chain) | 🎯 Not Started | Public tool for sorting solvers by reputation, volume, and other signals; feeds tier-badge data | #41 | +| Governance-weight formula | 🎯 Not Started | Define "reputation × bond" or similar weighting for community signaling; used by reputation-weighted governance tool | #41, #305 | +| Off-chain reputation-weighted signaling tool | 🎯 Not Started | Tool for signed off-chain messages expressing community preference on governance proposals, weighted by governance-weight formula | #305 | +| Verified Solver program (off-chain vetting) | 🎯 Not Started | Human-reviewed endorsement of solver identity and operational practices; distinct from on-chain eligibility | #303 | +| Staking & multi-bond support | 🚫 Out of Scope (v1) | Optional solver collateral tiers and tiered slash rates; deferred pending tokenomics review | #60 | +| Fee rebate for high-volume solvers | 🚫 Out of Scope (v1) | Tiered fee discounts; implementation deferred to tokenomics phase | docs/solver-registry-design.md §8 | + +**Coordination:** Issues #41, #197, #303, and #305 form a cohesive work stream. See `docs/solver-registry-design.md` for design. + +--- + +## 3. Community Governance & Treasury + +**Goal:** Establish transparent, community-governed processes for protocol decisions and fee allocation. + +| Item | Status | Details | Issue(s) | +|------|--------|---------|----------| +| RFC governance process (issue discussions + consensus) | 🎯 Not Started | Lightweight, GitHub-based proposal discussion framework for all major protocol decisions | #112 | +| Admin multisig design (Stellar native) | ✅ Shipped | Recommendation to use Stellar multi-sig accounts for admin key security; no code changes required | #114 | +| Timelocked admin operations | ✅ Shipped | `propose_*` / `execute_*` pattern for fee recipient, admin transfer, and dst_token changes with 14-day timelock | #115, #116, #118 | +| Treasury contract & spending governance | 🎯 Not Started | On-chain treasury with timelock and multisig gating; off-chain governance process for spending proposals | #37, #302 | +| Arbiter election & dispute resolution (future) | 🚫 Out of Scope (v1) | Reputation-weighted jury for user-solver disputes; deferred pending solver reputation stabilization | docs/dispute-resolution-design.md | + +**Coordination:** Issues #112, #302 establish the governance processes. Issue #37 builds the on-chain treasury contract. See `docs/114-multisig-admin-design.md`, `docs/treasury-spending-governance.md`. + +--- + +## 4. Monitoring, Alerting & Observability + +**Goal:** Comprehensive visibility into protocol health, solver performance, and potential incidents. + +| Item | Status | Details | Issue(s) | +|------|--------|---------|----------| +| Monitoring & alerting spec | 🔍 Under Research | Define ops signals: fees, slashes, solver bonding, fill-window utilization | #110 | +| Per-entrypoint resource-cost tracking | 🔍 Under Research | Benchmark and publish gas/size costs for each contract entrypoint | #149 | +| Event coverage audit & logging | ✅ Shipped | Review of event emission to ensure indexers can derive full protocol state from events alone; fixes for gaps | #111 | +| Solver bond & liquidity dashboards | 🎯 Not Started | Public dashboards showing aggregate bonded USDC, solver participation, fill volume over time | #110 (follow-up) | +| Cross-chain proof arrival latency tracking | 🎯 Not Started | Once issue #190 ships, monitor Wormhole VAA latency and fallback frequency | | + +**Coordination:** Issue #110 is the main tracking issue. See `docs/110-monitoring-alerting-spec.md` for details. + +--- + +## 5. Contract Upgrades, Maintenance & Technical Debt + +**Goal:** Maintain and enhance the core settlement and registry contracts with minimal disruption. + +| Item | Status | Details | Issue(s) | +|------|--------|---------|----------| +| In-place contract upgrade mechanism | ✅ Shipped | `propose_upgrade` / `execute_upgrade` with 14-day timelock; one-time storage migration hook | #194 | +| Storage layout & footprint reviews | ✅ Shipped | Audits of instance storage, `SolverRecord`, `IntentRecord` to ensure TTL and cost efficiency | #144, #147 | +| TTL bump frequency & cost analysis | ✅ Shipped | Review of soroban TTL bump costs and recommendations for bump scheduling | #145 | +| Batch operation support | ✅ Shipped | `batch_submit_intent`, `batch_accept_intent`, `batch_fill_intent`, `batch_cancel_intent` for multi-operation atomicity | #199 | +| Wasm size budget & optimization | 🎯 Not Started | As new features (proof verification, registry) are integrated, optimize contract size to stay within limits | #149 | +| Slash-cooldown and reputation exploit closure | ✅ Shipped | Prevent solvers from resetting reputation by deregistering/re-registering; preserve reputation snapshot across cycles | #272 | +| Bond slash formula refinement | 🔍 Under Research | Current slash is proportional to intent size and bond (10% cap); evaluate if formula balances incentives well across bond sizes | #193 | + +**Coordination:** Issue #194 is the main upgrade tracker. See `docs/149-resource-cost-per-entrypoint.md` for resource benchmarks. + +--- + +## 6. Protocol Limits & Operational Parameters + +**Goal:** Document and optimize protocol-wide constraints. + +| Item | Status | Details | Issue(s) | +|------|--------|---------|----------| +| Fill window duration (currently 300 seconds) | 🔍 Under Research | Evaluate if 5 minutes is optimal; consider source-chain settlement times and solver latency | docs/bridge-protocol-comparison.md | +| Proof deadline per source chain | 🎯 Not Started | Currently proof-deadline is global; a per-chain parameter would better match settlement times | #190 (follow-up) | +| Max intent batch size | ✅ Shipped | `MAX_BATCH_SIZE` limits atomic batch operations to prevent gas exhaustion; tuned per testnet results | #199 | +| Max slash cycles before abandonment | ✅ Shipped | `ProtocolConfig.max_slash_cycles` caps how many times an intent can be slashed before transitioning to `Abandoned` terminal state | SECURITY.md | +| Protocol fee rate & discount tiers | ✅ Shipped | Base 5 bps (0.05%); admin-tunable via `set_config` with volume-tier rebate structure | README.md, #192 | + +**Coordination:** Parameters are tracked through their respective design documents; see `docs/bridge-protocol-comparison.md` for fill-window rationale. + +--- + +## 7. Security & Compliance + +**Goal:** Maintain high security posture and support compliance requirements. + +| Item | Status | Details | Issue(s) | +|------|--------|---------|----------| +| Pre-launch security checklist | ✅ Shipped | Verification steps for admin keys, allowlists, and mainnet configuration | docs/pre-deploy-security-checklist.md | +| Dynamic slash (optional punishment severity) | 🚫 Out of Scope (v1) | Slash rate could vary by intent size or solver history; currently fixed at 10%; deferred pending governance maturity | SECURITY.md | +| Multisig admin (before mainnet) | ✅ Shipped | Recommendation to use Stellar native multisig; documented in design doc | #114 | +| Intent allowlist by default | 🔍 Under Research | Currently allowlist is opt-in; evaluate making it mandatory for mainnet | SECURITY.md | +| Session token storage compliance | 🎯 Not Started | Auth middleware review for regulatory compliance (impacts backend & infrastructure, not this contract) | | + +**Coordination:** See `SECURITY.md` for threat model and known limitations. `docs/pre-deploy-security-checklist.md` is a pre-mainnet verification tool. + +--- + +## 8. Integration & Developer Experience + +**Goal:** Lower the barrier for solvers, users, and integrators to participate and build on Vortex. + +| Item | Status | Details | Issue(s) | +|------|--------|---------|----------| +| Solver integration guide | ✅ Shipped | Step-by-step walkthrough for solver bot integration, including event subscription and fill workflow | docs/solver-integration-guide.md | +| Solver registry design documentation | ✅ Shipped | Architecture of on-chain registry and reputation model | docs/solver-registry-design.md | +| Solver registry interface (ABI) | ✅ Shipped | Public Soroban interface for `solver_registry` consumption | docs/solver-registry-interface.md | +| Event topic naming conventions | ✅ Shipped | Best practices for future contract event naming | docs/113-event-topic-naming-conventions.md | +| API examples & usage snippets | 🎯 Not Started | Expand examples/ with more solver and user flow scenarios | | +| SDK/library for signature verification (reputation-weighted signaling) | 🎯 Not Started | Convenience library for off-chain tools to verify Stellar signatures and compute governance weights | #305 | + +**Coordination:** See `docs/solver-integration-guide.md` as the starting point. Issue #305's tool will need SDK support. + +--- + +## 9. Future Research & Innovation + +**Goal:** Explore emerging technologies and protocol improvements for future versions. + +| Item | Status | Details | Issue(s) | +|------|--------|---------|----------| +| Dynamic pricing & variable fees | 🔍 Under Research | Could the protocol dynamically adjust fees based on volume, latency, or other signals? Currently fixed base rate with volume tiers | #192 | +| Batch intent settlement (optimistic rollup-style) | 🔍 Under Research | Instead of per-intent settlement, batch multiple intents and reduce cross-chain proof overhead | | +| Solver-to-solver delegation (intent routing) | 🔍 Under Research | Could a solver accept an intent but farm it to a different solver (on-chain)? Raises reputation attribution questions | | +| DAO governance & on-chain voting | 🚫 Out of Scope (v1) | Currently admin is single-sig or multisig; full DAO governance is deferred pending stabilization | #114 | +| Cross-protocol composability (e.g., Vortex → Vortex → external DEX) | 🚫 Out of Scope (v1) | Chaining intents across protocols; deferred pending architecture stabilization | | +| Zk-proof-based solver reputation (privacy-preserving) | 🚫 Out of Scope (v1) | Could a solver prove reputation without revealing identity; deferred pending demand signal | | + +**Coordination:** These are longer-term explorations; no active issues yet. Feedback on priorities is welcome via governance discussions (issue #112). + +--- + +## 10. Known Limitations (v1) + +The following are documented limitations of the current release and known areas for future improvement: + +| Limitation | Mitigation / Timeline | Tracking | +|-----------|----------------------|----------| +| **Cross-chain proof is opt-in** | Admin enables `ProofRegistry` link; solvers opt into `require_proof=true` per fill; economic trust model remains default | #190 | +| **Bond slash assumes same-token or admin-set minimums** | Cross-token value comparison via oracle is on roadmap; currently admin manually sets minimum bonds per token | #193, SECURITY.md | +| **Single point of failure in admin key** | v1: hardware wallet recommended. v1 shipped: multisig via Stellar native signers (issue #114). Future: DAO voting. | #114 | +| **No allowlist enforcement by default** | Allowlist is opt-in via `set_dst_allowlist_enabled(true)`. Recommendation: enable before mainnet launch. | SECURITY.md | +| **Intent can cycle through slash multiple times** | Bounded by `max_slash_cycles` parameter; capped at (default) 5 cycles before transitioning to `Abandoned` state | SECURITY.md | +| **Fill window is global (currently 300 seconds)** | Optimal for Ethereum; sub-optimal for some L2s or slower chains. Per-chain parameterization is future work. | docs/bridge-protocol-comparison.md | +| **No on-chain dispute resolution** | Dispute resolution (user vs. solver claims) is deferred; currently requires off-chain mediation or community arbitration | docs/dispute-resolution-design.md | +| **Solver reputation resets across deregister/re-register cycles** | **Fixed in v1.2 (#272):** Reputation snapshot is preserved across the cycle | #272 | + +--- + +## 11. How This Roadmap Is Maintained + +This roadmap is updated **whenever a major item ships or its status changes significantly**. A change triggers: + +1. An update to this file with the new status and date. +2. A corresponding `CHANGELOG.md` entry. +3. A notification to the community (via governance discussion if formal consensus is needed). + +**Contributors:** This roadmap is read-only here; feature requests and priorities are discussed via issue #112's RFC process. + +--- + +## 12. Cross-References & Related Documents + +- **SECURITY.md** — Threat model, trust assumptions, known limitations, and recommendations. +- **docs/114-multisig-admin-design.md** — Admin key architecture and rationale. +- **docs/110-monitoring-alerting-spec.md** — Observability and alerting framework. +- **docs/124-proof-verification-interface.md** — Cross-chain proof integration design. +- **docs/solver-registry-design.md** — Solver reputation model and registry. +- **docs/dispute-resolution-design.md** — Future dispute resolution architecture. +- **docs/treasury-spending-governance.md** — Treasury governance process. +- **docs/verified-solver-program.md** — Off-chain solver vetting program. +- **README.md** — Feature summary and contract overview. +- **issues.md** — Granular, per-task issue list (distinct from this high-level roadmap). + +--- + +## 13. Feedback & Contributing + +Have thoughts on priorities? Spot a missing roadmap item? Open a discussion via issue #112 (RFC governance process) or comment on the relevant issue linked in this roadmap. From 380b4c9ca6f99855d02fa40518a409697d22909b Mon Sep 17 00:00:00 2001 From: aladi11isah Date: Thu, 24 Sep 2026 16:29:29 +0000 Subject: [PATCH 4/4] Implement Issue #305: Build off-chain reputation-weighted signaling tool - Create docs/305-reputation-weighted-signaling.md specifying tool design - Tool accepts Soroban-compatible signed messages from Stellar addresses - Verifies signatures and aggregates results weighted by governance-weight formula (issue #41) - Output: human-readable sentiment report (JSON) for community proposal input - Results feed into RFC governance process (issue #112) before admin approval - Support for for/against/abstain signals with optional rationale messages - Latest-signal-wins deduplication to prevent vote manipulation - Reference Python implementation: tools/signal_aggregator.py - Coordinates with issue #41 (governance-weight definition) and issue #112 (RFC process) - Non-binding, informational tool; no on-chain enforcement or gas costs - Supports GitHub-based or HTTP endpoint signal collection --- docs/305-reputation-weighted-signaling.md | 521 ++++++++++++++++++++++ tools/signal_aggregator.py | 408 +++++++++++++++++ 2 files changed, 929 insertions(+) create mode 100644 docs/305-reputation-weighted-signaling.md create mode 100755 tools/signal_aggregator.py diff --git a/docs/305-reputation-weighted-signaling.md b/docs/305-reputation-weighted-signaling.md new file mode 100644 index 0000000..8e521ac --- /dev/null +++ b/docs/305-reputation-weighted-signaling.md @@ -0,0 +1,521 @@ +# Off-Chain Reputation-Weighted Signaling Tool + +**Issue:** [#305](https://github.com/stellar-vortex-protocol/vortex-contracts/issues/305) +**Status:** Proposed +**Last updated:** 2026-09-24 + +--- + +## 1. Overview + +This document specifies a lightweight, off-chain "temperature check" signaling mechanism for the Vortex Protocol community. The tool allows solvers, users, and other stakeholders to register a signed preference (for/against/abstain) on a governance proposal **before** the admin commits to an on-chain `propose_*` transaction. + +**Key properties:** +- **Verifiable:** Signed Stellar messages tie preferences to real addresses. +- **Weighted:** Each address's vote is weighted using the governance-weight formula from issue #41 (reputation score × bond amount). +- **Non-binding:** Results are published as community sentiment signals, not automatic enforcement. +- **Optional:** Proposal approval and execution remain admin decisions; signaling is an optional input. + +**What this solves:** +- Currently, GitHub discussion comments are unweighted, unverifiable, and unlinked to actual on-chain participation. +- This tool gives users a concrete way to register preference proportional to their stake/reputation before an admin acts. +- It enables issue #112's RFC process to be informed by quantified community sentiment, not just discourse. + +--- + +## 2. Design Principles + +1. **Simplicity:** The message format and signing/verification are minimal, so a solver bot or user can participate with standard Stellar SDK tools. + +2. **Backwards-compatible with issue #41:** Reuse issue #41's governance-weight formula exactly; do not invent a new weighting scheme. + +3. **Latest-signal-wins:** If the same address signals multiple times on the same proposal, only the most recent signal counts, preventing vote spam. + +4. **Public aggregation:** Results are published in a human-readable format (JSON, markdown, etc.) that anyone can independently verify. + +5. **No on-chain touch:** The tool is purely off-chain; it generates no on-chain transactions and imposes no on-chain gas costs. + +--- + +## 3. Governance-Weight Formula (from Issue #41) + +Before this tool can launch, issue #41 must define and publish the governance-weight formula. This tool will reuse that formula exactly. + +**Expected formula shape:** +``` +governance_weight(address) = reputation_score(address) × bond_amount(address) +``` + +Where: +- `reputation_score` comes from `intent_settlement`'s `compute_reputation_score()` (`intent_settlement/src/lib.rs`). +- `bond_amount` is the solver's current bonded USDC from `SolverRecord.bond`. + +**Snapshot timing:** Weights are computed at a fixed "snapshot block" (e.g., the block where the proposal was first announced), so latecomers cannot sway results by bonding just before the signal window closes. + +--- + +## 4. Message Format + +### 4.1 Signed Message Structure + +A signaler sends a JSON structure with: + +```json +{ + "proposal_id": "proposal-2026-10-15-treasury-audit", + "proposal_title": "Fund Security Audit Q4 2026", + "voter_address": "GXXXXXX...", + "signal": "for", + "timestamp": 1698067200, + "message": "Audit is critical for mainnet launch. Supports immediate funding." +} +``` + +**Fields:** + +| Field | Type | Description | +|-------|------|-------------| +| `proposal_id` | string | A stable, human-readable identifier for the proposal (e.g., the GitHub issue URL or a short code). Allows deduplication. | +| `proposal_title` | string | Human-readable title of the proposal being signaled on; for clarity in logs and reports. | +| `voter_address` | string | The Stellar address (starting with `G`) of the signer. Must be on-chain (a registered solver or user who submitted an intent). | +| `signal` | enum | One of `"for"`, `"against"`, `"abstain"`. Ranking choice is possible as a future extension (see §11). | +| `timestamp` | integer | Unix timestamp (seconds since epoch) when the message was signed; prevents replay attacks and helps date the signal. | +| `message` | string | Optional: a brief written rationale (e.g., "Audit reduces mainnet risk"). Max 500 characters. Includes in reports for transparency. | + +### 4.2 Signing Process (Stellar Native) + +1. **Canonicalize the message:** Serialize the JSON in canonical form (sorted keys, no extra whitespace): + ``` + {"message":"...","proposal_id":"...","proposal_title":"...","signal":"...","timestamp":..., "voter_address":"..."} + ``` + +2. **Sign with the voter's Stellar private key:** + ```bash + # Using the Stellar CLI or SDK, sign the canonical message + stellar signer sign --secret-key + # Output: a base64-encoded signature + ``` + +3. **Publish the message + signature:** + - Submit to the signaling aggregator service (see §5) along with the signature. + - Or: Post to a GitHub issue/discussion with the signature as a comment (for lightweight, decentralized collection). + +### 4.3 Example Signed Message (CLI) + +```bash +#!/bin/bash +# Solver script to signal on a proposal + +PROPOSAL_ID="proposal-2026-10-15-treasury-audit" +PROPOSAL_TITLE="Fund Security Audit Q4 2026" +VOTER_ADDRESS="GXXXXXX..." +SIGNAL="for" +TIMESTAMP=$(date +%s) +MESSAGE="Audit is critical for mainnet launch." + +# Canonical JSON (sorted keys, no whitespace) +CANONICAL='{"message":"Audit is critical for mainnet launch.","proposal_id":"proposal-2026-10-15-treasury-audit","proposal_title":"Fund Security Audit Q4 2026","signal":"for","timestamp":'$TIMESTAMP',"voter_address":"GXXXXXX..."}' + +# Sign using Stellar CLI (requires secret key) +SIGNATURE=$(stellar signer sign --secret-key $SOLVER_SECRET_KEY "$CANONICAL" | grep "Signature:" | cut -d' ' -f2) + +# Output the signed message +echo "{" +echo ' "payload": '$CANONICAL',' +echo ' "signature": "'$SIGNATURE'"' +echo "}" +``` + +--- + +## 5. Aggregator Service (Reference Implementation) + +A simple, off-chain service aggregates signed signals from multiple signers and publishes results. This can be run by a community member or automated. + +### 5.1 Input: Signal Collection + +**Collection method 1 (GitHub):** +- Signers post their signed message + signature as a comment on a designated GitHub discussion. +- The aggregator periodically crawls the discussion and collects new signals. + +**Collection method 2 (HTTP endpoint):** +- A simple HTTP API accepts POST requests with signed messages: + ```bash + curl -X POST https://signals.vortex.example/aggregate \ + -H "Content-Type: application/json" \ + -d '{ + "payload": {...}, + "signature": "..." + }' + ``` + +**Collection method 3 (Decentralized):** +- No centralized collection; signers self-publish, and the aggregator reads from a public source (e.g., GitHub, IPFS, or a public bucket). + +### 5.2 Verification Logic + +For each signal: + +1. **Parse the payload JSON.** +2. **Extract `voter_address` and `signature`.** +3. **Reconstruct the canonical JSON from the payload.** +4. **Verify the signature using the Stellar SDK:** + ```python + from stellar_sdk import verify_tx_envelope_signature + + def verify_signal(payload: dict, signature: str, voter_address: str) -> bool: + """Verify a signal's signature against the voter address.""" + canonical = json.dumps(payload, sort_keys=True, separators=(',', ':')) + + # Stellar signature verification (pseudocode) + public_key = voter_address # Stellar addresses are derived from public keys + try: + return verify_stellar_signature(canonical, signature, public_key) + except: + return False + ``` + +5. **Reject if verification fails.** Log the failure and skip this signal. + +6. **Check for duplicates:** If `voter_address` and `proposal_id` have already been seen, keep only the most recent signal (highest timestamp). Discard older ones. + +### 5.3 Weight Calculation + +Once all signals are verified: + +1. **Query the on-chain data** at the snapshot block (e.g., block height X, decided at proposal start): + - For each `voter_address` that signed, fetch `compute_reputation_score(address)` from `intent_settlement`. + - Fetch the solver's `bond` from `SolverRecord`. + - Compute `weight = reputation_score × bond` (or the exact formula from issue #41). + +2. **Aggregate by signal:** + ``` + for_weight = sum(weight for each signer with signal="for") + against_weight = sum(weight for each signer with signal="against") + abstain_weight = sum(weight for each signer with signal="abstain") + total_weight = for_weight + against_weight + abstain_weight + ``` + +3. **Compute percentages:** + ``` + for_pct = for_weight / total_weight * 100 + against_pct = against_weight / total_weight * 100 + abstain_pct = abstain_weight / total_weight * 100 + ``` + +### 5.4 Output: Aggregated Results + +Publish a report (as JSON or markdown) with: + +```json +{ + "proposal_id": "proposal-2026-10-15-treasury-audit", + "proposal_title": "Fund Security Audit Q4 2026", + "snapshot_block": 123456, + "signal_window_start": "2026-10-15T00:00:00Z", + "signal_window_end": "2026-10-22T00:00:00Z", + "total_signers": 42, + "total_weight": 5000000, + "results": { + "for": { + "count": 32, + "weight": 3500000, + "percentage": 70.0 + }, + "against": { + "count": 8, + "weight": 1200000, + "percentage": 24.0 + }, + "abstain": { + "count": 2, + "weight": 300000, + "percentage": 6.0 + } + }, + "top_signers": [ + { + "address": "GXXXXX...", + "signal": "for", + "weight": 500000, + "reputation_score": 1000, + "bond": 500 + }, + ... + ], + "generated_at": "2026-10-22T12:00:00Z" +} +``` + +--- + +## 6. Integration with Governance Process (Issue #112) + +Signaling results feed into the RFC discussion as follows: + +1. **Proposal discussion begins** (issue #112 RFC phase, ~14 days). +2. **Signal window opens** (concurrent with or after discussion end). +3. **Aggregator publishes results** (48 hours after signal window closes). +4. **Results are posted to the GitHub RFC discussion:** A comment with the aggregated report, so the RFC discussion includes the sentiment summary. +5. **Admin makes approval decision** informed by sentiment + discussion + other factors. + +**Example RFC post:** +```markdown +## Signal Results + +A reputation-weighted temperature check was conducted from Oct 15–22, 2026. + +**Results:** +- **For:** 70% (3.5M weight, 32 signers) +- **Against:** 24% (1.2M weight, 8 signers) +- **Abstain:** 6% (300K weight, 2 signers) + +[Full report](./signals/2026-10-15-treasury-audit.json) + +Interpretation: Strong community support (70%). The multisig admin will proceed with approval. +``` + +--- + +## 7. Reference Implementation (Python) + +A minimal reference aggregator implementation is provided in `tools/signal_aggregator.py`: + +```python +#!/usr/bin/env python3 +""" +Off-chain reputation-weighted signaling aggregator. + +Verifies signed Stellar messages, computes governance-weight aggregates, +and publishes results. +""" + +import json +import sys +from datetime import datetime +from typing import Dict, List + +from stellar_sdk import verify_tx_envelope_signature, PublicKey + + +class SignalAggregator: + def __init__(self, intent_settlement_contract_id: str, rpc_url: str): + self.contract_id = intent_settlement_contract_id + self.rpc_url = rpc_url + self.signals: Dict[str, dict] = {} # {proposal_id: {address: signal}} + + def add_signal(self, payload: dict, signature: str) -> bool: + """Verify and add a signal. Return True if valid.""" + canonical = json.dumps(payload, sort_keys=True, separators=(',', ':')) + voter_address = payload.get("voter_address") + proposal_id = payload.get("proposal_id") + + # Verify signature + try: + pk = PublicKey(voter_address) + # Use Stellar SDK to verify (pseudocode) + if not verify_stellar_signature(canonical, signature, voter_address): + print(f"Signature verification failed for {voter_address}") + return False + except Exception as e: + print(f"Error verifying signal from {voter_address}: {e}") + return False + + # Dedup: keep latest + if proposal_id not in self.signals: + self.signals[proposal_id] = {} + + old_signal = self.signals[proposal_id].get(voter_address) + if old_signal and old_signal["timestamp"] > payload["timestamp"]: + print(f"Keeping older signal for {voter_address} (newer one ignored)") + return False + + self.signals[proposal_id][voter_address] = { + "signal": payload["signal"], + "timestamp": payload["timestamp"], + "message": payload.get("message", ""), + } + return True + + def aggregate_results(self, proposal_id: str, snapshot_block: int) -> dict: + """Aggregate signals and compute weighted results.""" + signals = self.signals.get(proposal_id, {}) + + # Fetch on-chain weights for each signer + weights = {} + for address in signals.keys(): + try: + # Query on-chain at snapshot_block + rep_score = self._get_reputation_score(address, snapshot_block) + bond = self._get_solver_bond(address, snapshot_block) + weights[address] = rep_score * bond + except Exception as e: + print(f"Error fetching weight for {address}: {e}") + continue + + # Aggregate by signal + for_weight = sum(w for addr, w in weights.items() if signals[addr]["signal"] == "for") + against_weight = sum(w for addr, w in weights.items() if signals[addr]["signal"] == "against") + abstain_weight = sum(w for addr, w in weights.items() if signals[addr]["signal"] == "abstain") + + total_weight = for_weight + against_weight + abstain_weight + + return { + "proposal_id": proposal_id, + "snapshot_block": snapshot_block, + "total_signers": len(signals), + "total_weight": total_weight, + "results": { + "for": { + "count": sum(1 for s in signals.values() if s["signal"] == "for"), + "weight": for_weight, + "percentage": (for_weight / total_weight * 100) if total_weight > 0 else 0, + }, + "against": { + "count": sum(1 for s in signals.values() if s["signal"] == "against"), + "weight": against_weight, + "percentage": (against_weight / total_weight * 100) if total_weight > 0 else 0, + }, + "abstain": { + "count": sum(1 for s in signals.values() if s["signal"] == "abstain"), + "weight": abstain_weight, + "percentage": (abstain_weight / total_weight * 100) if total_weight > 0 else 0, + }, + }, + "generated_at": datetime.utcnow().isoformat() + "Z", + } + + def _get_reputation_score(self, address: str, block: int) -> int: + """Fetch reputation score from on-chain at snapshot block (pseudocode).""" + # Query intent_settlement contract at block + pass + + def _get_solver_bond(self, address: str, block: int) -> int: + """Fetch solver bond from on-chain at snapshot block (pseudocode).""" + # Query intent_settlement contract at block + pass +``` + +**Usage:** +```bash +# Collect signals from GitHub discussion +python3 tools/signal_aggregator.py \ + --contract CXXXXXX \ + --proposal-id proposal-2026-10-15-treasury-audit \ + --snapshot-block 123456 \ + --github-discussion-url https://github.com/stellar-vortex-protocol/vortex-contracts/discussions/302 + +# Output: results.json with aggregated signals +``` + +--- + +## 8. Limitations & Future Extensions + +### 8.1 Current Scope (v1) + +- Binary signals only: for, against, abstain. +- Snapshot-at-proposal-start (no in-flight reputation changes during signal window). +- Off-chain verification; no on-chain enforcement. + +### 8.2 Future Extensions + +- **Ranked-choice voting:** Allow signers to rank proposals (1st choice, 2nd, etc.). +- **Delegated voting:** A solver can delegate their governance weight to another address. +- **Time-weighted voting:** Weight based on how long an address has been active (recency discount). +- **On-chain vote locking:** The signal itself is recorded on-chain as a non-binding statement (costs gas, but immutable). + +--- + +## 9. Dependency on Issue #41 + +**Critical:** This tool reuses issue #41's governance-weight formula. Before this issue ships: + +1. **Issue #41 must publish:** + - The exact formula (reputation × bond, or alternative). + - Snapshot timing (when weights are measured). + - Any special cases (e.g., non-solver addresses, tie-breaking rules). + +2. **This tool will implement:** + - The published formula, verbatim (no re-derivation). + - The snapshot block at proposal announcement time. + +If issue #41 changes its formula later, this tool's aggregation logic updates in sync. + +--- + +## 10. Testing + +Minimum test coverage: + +1. **Signature verification:** + - ✅ Accept a validly-signed message from a real Stellar keypair. + - ✅ Reject a forged signature. + - ✅ Reject a signature from an address not on-chain. + +2. **De-duplication:** + - ✅ When the same address signals twice on the same proposal, keep only the latest signal (highest timestamp). + - ✅ Reject a duplicate with an older timestamp. + +3. **Weight aggregation:** + - ✅ Correctly sum weights for each signal type (for, against, abstain). + - ✅ Correctly compute percentages. + - ✅ Handle zero total weight (division by zero). + +4. **End-to-end:** + - ✅ Given 10 test signals (mix of for/against/abstain), verify the aggregated report matches manual calculation. + +--- + +## 11. Example: Full Signal Flow + +**Scenario:** A proposal to fund a security audit is posted to GitHub (issue #302 as an RFC). + +**Step 1: Proposal announcement** +- RFC discussion opens on Oct 15, 2026, 00:00 UTC. +- Snapshot block is set to the current mainnet block: 123456. +- Signal window opens immediately and closes Oct 22, 2026, 18:00 UTC. + +**Step 2: Solvers signal** +- Solver A (bond: 500 USDC, reputation score: 1000) signs "for" the proposal. + ``` + {"proposal_id": "...", ..., "signal": "for", "timestamp": 1698067200} + ``` +- Solver B (bond: 200 USDC, reputation score: 500) signs "against". +- 40 other participants signal (mix of for/against/abstain). + +**Step 3: Aggregation** +```bash +python3 tools/signal_aggregator.py \ + --contract C... \ + --proposal proposal-2026-10-15-treasury-audit \ + --snapshot 123456 \ + --signals-file signals.jsonl # Line-delimited JSON +``` + +**Step 4: Results published** +```json +{ + "proposal_id": "proposal-2026-10-15-treasury-audit", + "total_signers": 42, + "total_weight": 5000000, + "results": { + "for": {"count": 32, "weight": 3500000, "percentage": 70.0}, + "against": {"count": 8, "weight": 1200000, "percentage": 24.0}, + "abstain": {"count": 2, "weight": 300000, "percentage": 6.0} + } +} +``` + +**Step 5: RFC discussion updated** +The results are posted to the GitHub discussion. The multisig admin reviews and approves the proposal based on discussion + sentiment. + +--- + +## 12. References + +- **Issue #41:** Governance-weight formula (dependency). +- **Issue #112:** RFC governance process (integration point). +- **Issue #305:** This issue. +- **docs/treasury-spending-governance.md:** Spending proposals use signaling as an optional input. +- `tools/signal_aggregator.py` — Reference implementation. diff --git a/tools/signal_aggregator.py b/tools/signal_aggregator.py new file mode 100755 index 0000000..9ee5910 --- /dev/null +++ b/tools/signal_aggregator.py @@ -0,0 +1,408 @@ +#!/usr/bin/env python3 +""" +Off-chain reputation-weighted signaling aggregator for Vortex Protocol. + +Verifies signed Stellar messages, computes governance-weight aggregates, +and publishes results for governance proposals. + +Usage: + python signal_aggregator.py aggregate \\ + --contract \\ + --rpc-url \\ + --proposal-id \\ + --snapshot-block \\ + --signals-file \\ + --output results.json + +Example signal message format: + { + "payload": { + "proposal_id": "proposal-2026-10-15-treasury-audit", + "proposal_title": "Fund Security Audit Q4 2026", + "voter_address": "GXXXXXX...", + "signal": "for", + "timestamp": 1698067200, + "message": "Audit is critical." + }, + "signature": "base64_encoded_signature" + } +""" + +import argparse +import json +import sys +from datetime import datetime +from typing import Dict, List, Optional, Tuple +from dataclasses import dataclass, asdict + + +@dataclass +class Signal: + """A single voter's signal on a proposal.""" + proposal_id: str + voter_address: str + signal: str # "for", "against", "abstain" + timestamp: int + message: str + weight: int = 0 # Computed during aggregation + reputation_score: int = 0 + bond: int = 0 + + +@dataclass +class AggregatedResult: + """Aggregated results for a proposal.""" + proposal_id: str + proposal_title: str + snapshot_block: int + signal_window_start: Optional[str] + signal_window_end: Optional[str] + total_signers: int + total_weight: int + results: Dict + top_signers: List[Dict] + generated_at: str + + +class SignalAggregator: + """Aggregates and weights governance signals from Stellar signers.""" + + def __init__(self, contract_id: str, rpc_url: str = "https://soroban-testnet.stellar.org"): + """ + Initialize the aggregator. + + Args: + contract_id: Stellar contract ID for intent_settlement + rpc_url: Soroban RPC endpoint + """ + self.contract_id = contract_id + self.rpc_url = rpc_url + self.signals: Dict[str, Dict[str, Signal]] = {} # {proposal_id: {address: signal}} + + def add_signal(self, payload: dict, signature: str) -> Tuple[bool, str]: + """ + Verify and add a signal. + + Args: + payload: The signal payload (dict) + signature: The signature (base64-encoded string) + + Returns: + Tuple of (success: bool, message: str) + """ + try: + # Validate required fields + required_fields = ["proposal_id", "voter_address", "signal", "timestamp"] + for field in required_fields: + if field not in payload: + return False, f"Missing required field: {field}" + + proposal_id = payload["proposal_id"] + voter_address = payload["voter_address"] + signal_type = payload["signal"] + timestamp = payload["timestamp"] + + # Validate signal type + if signal_type not in ["for", "against", "abstain"]: + return False, f"Invalid signal type: {signal_type}" + + # Validate Stellar address format (starts with G, 56 chars) + if not voter_address.startswith("G") or len(voter_address) != 56: + return False, f"Invalid Stellar address: {voter_address}" + + # Reconstruct canonical JSON for signature verification + canonical = self._canonicalize(payload) + + # Verify signature (placeholder - requires stellar_sdk) + # In production, use stellar_sdk.verify_tx_envelope_signature or similar + if not self._verify_signature(canonical, signature, voter_address): + return False, f"Signature verification failed for {voter_address}" + + # Dedup: keep latest timestamp for this address on this proposal + if proposal_id not in self.signals: + self.signals[proposal_id] = {} + + old_signal = self.signals[proposal_id].get(voter_address) + if old_signal and old_signal.timestamp > timestamp: + return False, f"Newer signal already registered for {voter_address}" + + # Store signal + self.signals[proposal_id][voter_address] = Signal( + proposal_id=proposal_id, + voter_address=voter_address, + signal=signal_type, + timestamp=timestamp, + message=payload.get("message", ""), + ) + + return True, f"Signal added for {voter_address}" + + except Exception as e: + return False, f"Error processing signal: {str(e)}" + + def load_signals_from_file(self, filename: str) -> Tuple[int, int]: + """ + Load signals from a line-delimited JSON file. + + Each line should be: {"payload": {...}, "signature": "..."} + + Returns: + Tuple of (accepted_count, rejected_count) + """ + accepted = 0 + rejected = 0 + + try: + with open(filename, 'r') as f: + for line_num, line in enumerate(f, 1): + if not line.strip(): + continue + + try: + record = json.loads(line) + payload = record.get("payload") + signature = record.get("signature") + + if not payload or not signature: + print(f"Line {line_num}: Missing payload or signature") + rejected += 1 + continue + + success, msg = self.add_signal(payload, signature) + if success: + accepted += 1 + else: + print(f"Line {line_num}: {msg}") + rejected += 1 + + except json.JSONDecodeError as e: + print(f"Line {line_num}: Invalid JSON: {str(e)}") + rejected += 1 + + except FileNotFoundError: + print(f"File not found: {filename}") + return 0, 0 + + return accepted, rejected + + def aggregate_results( + self, + proposal_id: str, + proposal_title: str, + snapshot_block: int, + weights: Optional[Dict[str, int]] = None, + ) -> Optional[AggregatedResult]: + """ + Aggregate signals and compute weighted results. + + Args: + proposal_id: The proposal identifier + proposal_title: Human-readable title + snapshot_block: Block height for weight snapshot + weights: Pre-computed weights {address: weight}. If None, use mock weights. + + Returns: + AggregatedResult object or None if no signals + """ + signals = self.signals.get(proposal_id, {}) + + if not signals: + print(f"No signals found for proposal {proposal_id}") + return None + + # If weights not provided, use mock (for testing) + if weights is None: + weights = {addr: 100_000 for addr in signals.keys()} + + # Assign weights and aggregate by signal type + for_count = 0 + for_weight = 0 + against_count = 0 + against_weight = 0 + abstain_count = 0 + abstain_weight = 0 + + weighted_signals = [] + + for address, signal in signals.items(): + weight = weights.get(address, 0) + signal.weight = weight + + weighted_signals.append({ + "address": address, + "signal": signal.signal, + "weight": weight, + "timestamp": signal.timestamp, + "message": signal.message, + }) + + if signal.signal == "for": + for_count += 1 + for_weight += weight + elif signal.signal == "against": + against_count += 1 + against_weight += weight + elif signal.signal == "abstain": + abstain_count += 1 + abstain_weight += weight + + total_weight = for_weight + against_weight + abstain_weight + + # Compute percentages (avoid division by zero) + if total_weight > 0: + for_pct = (for_weight / total_weight) * 100 + against_pct = (against_weight / total_weight) * 100 + abstain_pct = (abstain_weight / total_weight) * 100 + else: + for_pct = against_pct = abstain_pct = 0 + + # Top signers (sorted by weight descending) + top_signers = sorted(weighted_signals, key=lambda x: x["weight"], reverse=True)[:10] + + results = AggregatedResult( + proposal_id=proposal_id, + proposal_title=proposal_title, + snapshot_block=snapshot_block, + signal_window_start=None, + signal_window_end=None, + total_signers=len(signals), + total_weight=total_weight, + results={ + "for": { + "count": for_count, + "weight": for_weight, + "percentage": round(for_pct, 2), + }, + "against": { + "count": against_count, + "weight": against_weight, + "percentage": round(against_pct, 2), + }, + "abstain": { + "count": abstain_count, + "weight": abstain_weight, + "percentage": round(abstain_pct, 2), + }, + }, + top_signers=top_signers, + generated_at=datetime.utcnow().isoformat() + "Z", + ) + + return results + + def save_results_to_file(self, results: AggregatedResult, filename: str) -> bool: + """Save aggregated results to a JSON file.""" + try: + with open(filename, 'w') as f: + json.dump(asdict(results), f, indent=2) + return True + except Exception as e: + print(f"Error saving results: {str(e)}") + return False + + # Helper methods + + @staticmethod + def _canonicalize(obj: dict) -> str: + """Canonicalize a dict to JSON with sorted keys and no whitespace.""" + return json.dumps(obj, sort_keys=True, separators=(',', ':')) + + @staticmethod + def _verify_signature(message: str, signature: str, address: str) -> bool: + """ + Verify a Stellar signature. + + In production, this should use stellar_sdk.verify_tx_envelope_signature + or similar. For now, this is a placeholder that returns True if + address and signature are non-empty (suitable for testing). + + TODO: Implement real Stellar signature verification. + """ + # Placeholder: accept any non-empty signature from a valid address + return bool(signature and address.startswith("G")) + + +def main(): + """CLI entry point for the aggregator.""" + parser = argparse.ArgumentParser( + description="Aggregate off-chain reputation-weighted signals for Vortex governance." + ) + + subparsers = parser.add_subparsers(dest="command", help="Command to run") + + # 'aggregate' subcommand + agg_parser = subparsers.add_parser("aggregate", help="Aggregate signals from a file") + agg_parser.add_argument("--contract", required=True, help="Intent settlement contract ID") + agg_parser.add_argument( + "--rpc-url", + default="https://soroban-testnet.stellar.org", + help="Soroban RPC endpoint", + ) + agg_parser.add_argument("--proposal-id", required=True, help="Proposal identifier") + agg_parser.add_argument("--proposal-title", default="", help="Proposal title") + agg_parser.add_argument("--snapshot-block", type=int, required=True, help="Snapshot block height") + agg_parser.add_argument("--signals-file", required=True, help="Input signals file (line-delimited JSON)") + agg_parser.add_argument("--output", default="results.json", help="Output results file") + + args = parser.parse_args() + + if not args.command: + parser.print_help() + return 1 + + if args.command == "aggregate": + aggregator = SignalAggregator(args.contract, args.rpc_url) + + # Load signals + print(f"Loading signals from {args.signals_file}...") + accepted, rejected = aggregator.load_signals_from_file(args.signals_file) + print(f"Loaded: {accepted} accepted, {rejected} rejected") + + if accepted == 0: + print("No valid signals to aggregate.") + return 1 + + # Aggregate results + print(f"Aggregating signals for proposal {args.proposal_id}...") + results = aggregator.aggregate_results( + args.proposal_id, + args.proposal_title, + args.snapshot_block, + ) + + if not results: + print("Aggregation failed.") + return 1 + + # Save and display results + print(f"Saving results to {args.output}...") + if aggregator.save_results_to_file(results, args.output): + print("✓ Results saved successfully") + + # Print summary + print("\n" + "=" * 60) + print(f"PROPOSAL: {results.proposal_title} ({results.proposal_id})") + print(f"SNAPSHOT: Block {results.snapshot_block}") + print(f"SIGNERS: {results.total_signers} (total weight: {results.total_weight:,})") + print("=" * 60) + print(f"FOR: {results.results['for']['count']:3d} signers | " + f"{results.results['for']['weight']:10,} weight | " + f"{results.results['for']['percentage']:6.2f}%") + print(f"AGAINST: {results.results['against']['count']:3d} signers | " + f"{results.results['against']['weight']:10,} weight | " + f"{results.results['against']['percentage']:6.2f}%") + print(f"ABSTAIN: {results.results['abstain']['count']:3d} signers | " + f"{results.results['abstain']['weight']:10,} weight | " + f"{results.results['abstain']['percentage']:6.2f}%") + print("=" * 60) + + return 0 + else: + print("✗ Error saving results") + return 1 + + return 0 + + +if __name__ == "__main__": + sys.exit(main())