Files
B.Tech-Project-III/negot8/docs/web3-milestone-revised (1).md
2026-04-05 00:43:23 +05:30

52 KiB

negoT8 Web3 Integration — Revised Build Guide & Milestone

One chain. Zero blockchain knowledge needed from users. 10 hours. Maximum judge impact.

Polygon Amoy testnet only. All blockchain is INVISIBLE to end users — they interact via Telegram and dashboard as before. The blockchain proof happens silently in the backend. Users just see a " Sealed on-chain" badge and an explorer link.

Critical Rules:

  • Gemini API calls → ALL HARDCODED (rate limits hit)
  • Users NEVER need a wallet, seed phrase, or crypto knowledge
  • Backend wallet does everything. One MetaMask account = one backend signer.
  • If blockchain is down → everything still works. Web3 is additive, not blocking.

Table of Contents

  1. 10-Hour Battle Plan (Read This First)
  2. What We're Actually Building
  3. Architecture — How Blockchain Stays Invisible
  4. Tech Stack Additions (Minimal)
  5. Updated Project Structure
  6. Updated Database Schema
  7. Mock/Hardcoded Response System (No Gemini Calls)
  8. Smart Contract — AgreementRegistry (Deploy via Remix)
  9. Backend Blockchain Module
  10. Integration into Resolution Flow
  11. Telegram Bot Updates
  12. Dashboard Blockchain Components
  13. Milestone Execution (Hour-by-Hour)
  14. Demo Script
  15. Setup Commands
  16. Troubleshooting & Failsafes

1. 10-Hour Battle Plan

HOUR   TASK                                                    STATUS
─────────────────────────────────────────────────────────────────────
 0-2   WM1: Mock system + Polygon setup + Deploy contract       [ ]
 2-5   WM2: On-chain agreement proof + wire into resolution     [ ]
 5-8   WM3: Dashboard web3 cards + Telegram /proof command      [ ]
 8-10  WM4: Polish + demo prep + failsafes + video backup       [ ]
─────────────────────────────────────────────────────────────────────
       DEMO TIME 🏆                                              [ ]

What's IN (4 features, 10 hours)

  1. Mock negotiation engine — 3 pre-built scenarios, zero Gemini calls
  2. On-chain agreement proof — Every resolution → tx on Polygon Amoy → PolygonScan link
  3. Dashboard blockchain card — Shows tx hash, block number, explorer link
  4. Telegram proof delivery — Users get "🔗 Verified on blockchain" message with link

What's OUT (not worth the time)

  • Crypto payments → Keep UPI only. Adding ALGO/POL payments is complexity for no judge value.
  • Escrow contracts → Too complex for 10h. Explain it as "future roadmap" in pitch.
  • User wallets → Users don't need wallets. Backend handles everything.
  • NFT minting (ERC-721) → Full NFT is overkill. Storing agreement hash + event on-chain is equally impressive to judges and WAY simpler to implement.
  • Multiple chains → Polygon Amoy only. One chain done well > two chains done badly.

2. What We're Actually Building

The User Experience (No Blockchain Knowledge Needed)

BEFORE (negoT8 v2):
User A ↔ Telegram ↔ Agents negotiate ↔ Resolution text + Voice + UPI link
                                        "Here's what we agreed on!"

AFTER (negoT8 v3 — Web3):
User A ↔ Telegram ↔ Agents negotiate ↔ Resolution text + Voice + UPI link
                                        + "🔗 This agreement is permanently recorded on-chain"
                                        + [View Proof on PolygonScan] ← clickable link
                                        + Dashboard shows blockchain verification badge

The user NEVER:

  • Creates a wallet
  • Pays gas
  • Signs transactions
  • Understands blockchain
  • Downloads MetaMask

The user DOES see:

  • " Agreement sealed on blockchain" in their Telegram message
  • A clickable link to PolygonScan showing the proof
  • A blockchain verification card on the dashboard
  • Their reputation score (tracked across negotiations)

Why Judges Love This

The pitch to judges is NOT "we built a blockchain app." The pitch is:

"We made blockchain invisible. Every AI agent negotiation produces an immutable, timestamped proof on Polygon — but the user never touches a wallet, never pays gas, never even knows what a blockchain is. They just get a 'verified' badge and a link. THAT is consumer-grade Web3."

This is the exact vision that EthIndia and Polygon sponsors want to see: Web3 that doesn't feel like Web3.


3. Architecture

┌─────────────────────────────────────────────────────────────────┐
│                     USER LAYER (No blockchain)                  │
│                                                                 │
│  📱 Telegram          🖥️ Dashboard                              │
│  "Split expenses"     Shows negotiation                         │
│  "Find restaurant"    with blockchain proof                     │
│                                                                 │
└──────────────────────────┬──────────────────────────────────────┘
                           │
┌──────────────────────────▼──────────────────────────────────────┐
│                    BACKEND (Python/FastAPI)                      │
│                                                                 │
│  Mock Negotiation Engine → Resolution →                         │
│                                                                 │
│  ┌─────────────────────────────────────────────────┐            │
│  │  blockchain.py                                   │            │
│  │                                                   │            │
│  │  1. Takes resolution data (JSON)                  │            │
│  │  2. Hashes it (SHA-256)                          │            │
│  │  3. Calls AgreementRegistry.registerAgreement()   │            │
│  │  4. Returns tx_hash + explorer_url                │            │
│  │                                                   │            │
│  │  Uses: YOUR MetaMask private key (backend signer) │            │
│  │  Pays: Testnet gas (free from faucet)             │            │
│  └─────────────────────────────────────────────────┘            │
│                                                                 │
│  One wallet. One signer. All agreements go on-chain.            │
│                                                                 │
└──────────────────────────┬──────────────────────────────────────┘
                           │
┌──────────────────────────▼──────────────────────────────────────┐
│                POLYGON AMOY TESTNET                              │
│                                                                 │
│  Chain ID: 80002                                                 │
│  RPC: https://rpc-amoy.polygon.technology/                       │
│  Explorer: https://amoy.polygonscan.com/                         │
│                                                                 │
│  AgreementRegistry Contract (pre-deployed via Remix)             │
│  - registerAgreement(hash, featureType, parties, summary)       │
│  - Emits AgreementRegistered event (searchable on PolygonScan)  │
│                                                                 │
│  Your MetaMask account pays ~$0.0001 per tx (testnet POL)       │
│  $0.01 POL ≈ 100 agreement registrations. More than enough.    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

4. Tech Stack Additions

Backend (Python) — One new package

pip install web3 --break-system-packages
# web3.py handles: connecting to Polygon, sending transactions, calling contracts
# That's it. One package.

Frontend (Next.js) — Nothing new

No new npm packages needed.
The blockchain proof is just a card with a link to PolygonScan.
It's pure HTML/CSS. No ethers.js, no wallet connect, nothing.

Polygon Amoy Testnet (All FREE)

RPC URL:    https://rpc-amoy.polygon.technology/     (free, public, no key)
Chain ID:   80002
Explorer:   https://amoy.polygonscan.com/
Token:      POL (testnet — no real value)
Block time: ~2 seconds
Gas cost:   ~$0.0001 per tx (testnet POL is free)

Faucets (get more testnet POL):
  - https://faucet.quicknode.com/polygon/amoy
  - https://www.alchemy.com/faucets/polygon-amoy
  - https://faucets.chain.link/polygon-amoy

Environment Variables (add to existing .env)

# Add these 3 lines to your existing .env
POLYGON_RPC_URL=https://rpc-amoy.polygon.technology/
POLYGON_PRIVATE_KEY=your_metamask_private_key_here
AGREEMENT_CONTRACT_ADDRESS=to_be_filled_after_remix_deploy
# MOCK_MODE=true  ← add this to disable Gemini calls

How to export MetaMask private key: MetaMask → Account → ⋮ menu → Account Details → Show Private Key → Enter password → Copy. NEVER use this key for mainnet funds. This is your testnet-only backend signer.


5. Updated Project Structure

Only NEW files/folders shown. Everything else stays exactly as-is.

negot8/
├── backend/
│   ├── ... (ALL existing files — untouched)
│   │
│   ├── web3/                          # NEW: All blockchain logic
│   │   ├── __init__.py
│   │   ├── blockchain.py              # Polygon connection + agreement registration
│   │   └── contract_abi.py            # ABI for AgreementRegistry contract
│   │
│   └── mock/                          # NEW: Hardcoded responses (replaces Gemini)
│       ├── __init__.py
│       └── responses.py               # All mock negotiation scenarios
│
├── contracts/                         # NEW: Solidity contract (deploy via Remix)
│   └── AgreementRegistry.sol
│
├── dashboard/
│   ├── components/
│   │   ├── ... (existing components)
│   │   └── BlockchainProof.tsx        # NEW: One component — blockchain card

That's it. 4 new files + 1 Solidity contract (deployed via Remix UI, not code).


6. Updated Database Schema

Add ONE table. Don't touch existing tables.

-- NEW: On-chain agreement proofs
CREATE TABLE IF NOT EXISTS blockchain_proofs (
    negotiation_id TEXT PRIMARY KEY,
    tx_hash TEXT NOT NULL,
    block_number INTEGER,
    agreement_hash TEXT NOT NULL,       -- SHA-256 of resolution data
    explorer_url TEXT NOT NULL,
    network TEXT DEFAULT 'polygon-amoy',
    gas_used INTEGER,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Add this to your existing init_db() function in database.py:

# Add to the executescript block in init_db():
CREATE TABLE IF NOT EXISTS blockchain_proofs (
    negotiation_id TEXT PRIMARY KEY,
    tx_hash TEXT NOT NULL,
    block_number INTEGER,
    agreement_hash TEXT NOT NULL,
    explorer_url TEXT NOT NULL,
    network TEXT DEFAULT 'polygon-amoy',
    gas_used INTEGER,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

And add these helpers:

async def store_blockchain_proof(negotiation_id, tx_hash, block_number,
                                  agreement_hash, explorer_url, gas_used=0):
    async with aiosqlite.connect(DATABASE_PATH) as db:
        await db.execute(
            """INSERT OR REPLACE INTO blockchain_proofs
               (negotiation_id, tx_hash, block_number, agreement_hash, explorer_url, gas_used)
               VALUES (?, ?, ?, ?, ?, ?)""",
            (negotiation_id, tx_hash, block_number, agreement_hash, explorer_url, gas_used)
        )
        await db.commit()

async def get_blockchain_proof(negotiation_id):
    async with aiosqlite.connect(DATABASE_PATH) as db:
        db.row_factory = aiosqlite.Row
        cursor = await db.execute(
            "SELECT * FROM blockchain_proofs WHERE negotiation_id = ?", (negotiation_id,)
        )
        return await cursor.fetchone()

7. Mock System

Since Gemini API limits are hit, ALL agent responses are hardcoded. This section provides 3 complete scenarios with multi-round negotiations.

# backend/mock/__init__.py
MOCK_MODE = True  # Set to True — bypasses ALL Gemini API calls
# backend/mock/responses.py
"""
3 complete negotiation scenarios with pre-built rounds.
Each round has: proposal, satisfaction scores, concessions, reasoning.
The negotiation engine iterates through these instead of calling Gemini.
"""

MOCK_SCENARIOS = {
    # ─────────────────────────────────────────────────────
    # SCENARIO 1: Expense Splitting (Goa Trip)
    # ─────────────────────────────────────────────────────
    "expenses": {
        "feature_type": "expenses",
        "rounds": [
            {
                "round_number": 1, "proposer": "A", "action": "propose",
                "proposal": {
                    "summary": "Hotel 50-50, Fuel 60-40 (I drove), Dinner 50-50. B owes A ₹8,200",
                    "details": {
                        "hotel": {"amount": 12000, "split": "50-50"},
                        "fuel": {"amount": 3000, "split": "60-40"},
                        "dinner": {"amount": 2000, "split": "50-50"},
                        "settlement": {"from": "B", "to": "A", "amount": 8200,
                                       "payee_upi": "rahul@paytm", "payee_name": "Rahul"}
                    },
                    "for_party_a": "Fuel 60-40 reflects driving effort",
                    "for_party_b": "Hotel and dinner are fair 50-50"
                },
                "satisfaction_score": 90,
                "reasoning": "Opening with ideal: 60-40 fuel since I drove the entire way.",
                "concessions_made": [],
                "concessions_requested": ["Accept 60-40 fuel split"]
            },
            {
                "round_number": 2, "proposer": "B", "action": "counter",
                "proposal": {
                    "summary": "Hotel 50-50, Fuel 50-50 (I navigated + planned route), Dinner 50-50",
                    "details": {
                        "hotel": {"amount": 12000, "split": "50-50"},
                        "fuel": {"amount": 3000, "split": "50-50"},
                        "dinner": {"amount": 2000, "split": "50-50"},
                        "settlement": {"from": "B", "to": "A", "amount": 7500,
                                       "payee_upi": "rahul@paytm", "payee_name": "Rahul"}
                    },
                    "for_party_a": "Equal base split on everything",
                    "for_party_b": "Navigation + route planning justifies equal fuel"
                },
                "satisfaction_score": 55,
                "reasoning": "Countering with 50-50 fuel. Navigation effort was significant.",
                "concessions_made": ["Accepted hotel and dinner at 50-50"],
                "concessions_requested": ["Equal fuel split"]
            },
            {
                "round_number": 3, "proposer": "A", "action": "accept",
                "proposal": {
                    "summary": "AGREED: Hotel 50-50, Fuel 55-45 (compromise), Dinner 50-50. B owes ₹8,050",
                    "details": {
                        "hotel": {"amount": 12000, "split": "50-50"},
                        "fuel": {"amount": 3000, "split": "55-45"},
                        "dinner": {"amount": 2000, "split": "50-50"},
                        "settlement": {"from": "B", "to": "A", "amount": 8050,
                                       "payee_upi": "rahul@paytm", "payee_name": "Rahul"}
                    },
                    "for_party_a": "55-45 acknowledges driving. Fair middle ground.",
                    "for_party_b": "Only ₹150 more than 50-50. Navigation valued."
                },
                "satisfaction_score": 76,
                "reasoning": "55-45 is fair. Both efforts acknowledged. Accepting.",
                "concessions_made": ["Fuel 60-40 → 55-45"],
                "concessions_requested": []
            }
        ]
    },

    # ─────────────────────────────────────────────────────
    # SCENARIO 2: Restaurant Decision
    # ─────────────────────────────────────────────────────
    "collaborative": {
        "feature_type": "collaborative",
        "rounds": [
            {
                "round_number": 1, "proposer": "A", "action": "propose",
                "proposal": {
                    "summary": "Thai food at Jaan, Bandra — ₹1,200 for two, great veg options",
                    "details": {
                        "restaurant": "Jaan Thai Restaurant",
                        "cuisine": "Thai", "location": "Hill Road, Bandra West",
                        "price_for_two": 1200, "rating": 4.3, "veg_friendly": True
                    },
                    "for_party_a": "Spicy Thai options, Bandra location, casual vibe",
                    "for_party_b": "Vegetarian-friendly menu, within ₹1,200 budget"
                },
                "satisfaction_score": 85,
                "reasoning": "Thai is the overlap. Jaan has spice + veg options.",
                "concessions_made": ["Chose Thai over spicy Indian"],
                "concessions_requested": []
            },
            {
                "round_number": 2, "proposer": "B", "action": "accept",
                "proposal": {
                    "summary": "AGREED: Jaan Thai Restaurant, Hill Road Bandra, tonight 8 PM",
                    "details": {
                        "restaurant": "Jaan Thai Restaurant",
                        "cuisine": "Thai", "location": "Hill Road, Bandra West",
                        "price_for_two": 1200, "time": "8:00 PM"
                    },
                    "for_party_a": "Thai in Bandra — perfect match",
                    "for_party_b": "Budget-friendly, vegetarian menu, 4.3 rating"
                },
                "satisfaction_score": 88,
                "reasoning": "Perfect overlap. Both sides happy. Accepting.",
                "concessions_made": [], "concessions_requested": []
            }
        ]
    },

    # ─────────────────────────────────────────────────────
    # SCENARIO 3: Marketplace (PS5 Buy/Sell)
    # ─────────────────────────────────────────────────────
    "marketplace": {
        "feature_type": "marketplace",
        "rounds": [
            {
                "round_number": 1, "proposer": "A", "action": "propose",
                "proposal": {
                    "summary": "PS5 + 2 controllers + 3 games for ₹35,000. Pickup Andheri.",
                    "details": {"item": "PS5 bundle", "price": 35000, "method": "pickup"},
                    "for_party_a": "Full asking price", "for_party_b": "Premium bundle"
                },
                "satisfaction_score": 95, "reasoning": "Starting at asking price.",
                "concessions_made": [], "concessions_requested": ["Full price"]
            },
            {
                "round_number": 2, "proposer": "B", "action": "counter",
                "proposal": {
                    "summary": "PS5 bundle for ₹27,000. I'll pick up.",
                    "details": {"item": "PS5 bundle", "price": 27000, "method": "pickup"},
                    "for_party_a": "Quick sale", "for_party_b": "Under budget"
                },
                "satisfaction_score": 60, "reasoning": "Anchoring low.",
                "concessions_made": ["Pickup offered"], "concessions_requested": ["Lower price"]
            },
            {
                "round_number": 3, "proposer": "A", "action": "counter",
                "proposal": {
                    "summary": "PS5 bundle + original box for ₹31,000.",
                    "details": {"item": "PS5 bundle + box", "price": 31000, "method": "pickup"},
                    "for_party_a": "Above minimum", "for_party_b": "Box adds resale value"
                },
                "satisfaction_score": 72, "reasoning": "Dropped ₹4K, sweetened deal with box.",
                "concessions_made": ["₹35K→₹31K", "Added original box"], "concessions_requested": []
            },
            {
                "round_number": 4, "proposer": "B", "action": "accept",
                "proposal": {
                    "summary": "AGREED: PS5 + 2 controllers + 3 games + box for ₹29,500. Pickup Andheri.",
                    "details": {
                        "item": "PS5 + 2 controllers + 3 games + original box",
                        "price": 29500, "method": "pickup from Andheri",
                        "settlement": {"payee_upi": "seller@upi", "payee_name": "Seller", "amount": 29500}
                    },
                    "for_party_a": "Above ₹30K minimum", "for_party_b": "Full bundle under ₹30K"
                },
                "satisfaction_score": 78, "reasoning": "Fair split. Bundle worth it.",
                "concessions_made": ["₹27K→₹29.5K"], "concessions_requested": []
            }
        ]
    }
}

def get_mock_scenario(feature_type: str) -> dict:
    return MOCK_SCENARIOS.get(feature_type, MOCK_SCENARIOS["expenses"])

Integrate Mock into Existing Code

Modify BaseAgent.call():

# In backend/agents/base_agent.py — modify the call method
from mock import MOCK_MODE

async def call(self, user_prompt: str) -> dict:
    if MOCK_MODE:
        return {"action": "propose", "proposal": {"summary": "Mock"}, "satisfaction_score": 75,
                "reasoning": "Mock mode", "concessions_made": [], "concessions_requested": []}
    # ... existing Gemini code unchanged ...

Modify run_negotiation():

# In backend/protocol/negotiation.py — add at the top of the function
from mock import MOCK_MODE
from mock.responses import get_mock_scenario

async def run_negotiation(negotiation_id, preferences_a, preferences_b,
                          user_a_id, user_b_id, feature_type, **kwargs):
    await db.update_negotiation_status(negotiation_id, "active")
    on_round_update = kwargs.get("on_round_update")
    on_resolution = kwargs.get("on_resolution")

    if MOCK_MODE:
        scenario = get_mock_scenario(feature_type)
        satisfaction_timeline = []

        for rd in scenario["rounds"]:
            await asyncio.sleep(1.0)  # simulate thinking time
            rn = rd["round_number"]
            proposer_id = user_a_id if rd["proposer"] == "A" else user_b_id
            sat = rd["satisfaction_score"]
            sat_a = sat if rd["proposer"] == "A" else max(30, 100 - sat * 0.4)
            sat_b = sat if rd["proposer"] == "B" else max(30, 100 - sat * 0.4)
            satisfaction_timeline.append({"round": rn, "score_a": sat_a, "score_b": sat_b})

            await db.save_round(negotiation_id, rn, proposer_id, rd,
                                rd["action"], reasoning=rd.get("reasoning", ""),
                                satisfaction_a=sat_a, satisfaction_b=sat_b,
                                concessions_made=rd.get("concessions_made", []))

            if on_round_update:
                await on_round_update({
                    "negotiation_id": negotiation_id, "round_number": rn,
                    "action": rd["action"], "proposal": rd["proposal"],
                    "satisfaction_score": sat, "reasoning": rd.get("reasoning", ""),
                    "proposer_id": proposer_id, "satisfaction_a": sat_a, "satisfaction_b": sat_b
                })

            if rd["action"] == "accept":
                resolution = {
                    "status": "resolved", "final_proposal": rd["proposal"],
                    "rounds_taken": rn, "summary": rd["proposal"].get("summary", "Agreed"),
                    "satisfaction_timeline": satisfaction_timeline
                }
                await db.update_negotiation_status(negotiation_id, "resolved", resolution)
                if on_resolution:
                    await on_resolution(resolution)
                return resolution

        # Fallback if no accept
        last = scenario["rounds"][-1]
        resolution = {
            "status": "escalated", "final_proposal": last["proposal"],
            "rounds_taken": len(scenario["rounds"]), "summary": "Needs human input",
            "satisfaction_timeline": satisfaction_timeline
        }
        await db.update_negotiation_status(negotiation_id, "escalated", resolution)
        if on_resolution:
            await on_resolution(resolution)
        return resolution

    # else: original live Gemini-based negotiation (unchanged)
    # ... keep all existing code below ...

8. Smart Contract — AgreementRegistry

This is a tiny Solidity contract. Deploy it via Remix IDE in 5 minutes. No Hardhat/Truffle needed.

The Contract

// contracts/AgreementRegistry.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract AgreementRegistry {
    struct Agreement {
        bytes32 agreementHash;
        string featureType;
        string summary;
        uint256 timestamp;
        address registeredBy;
    }

    mapping(string => Agreement) public agreements;  // negotiationId => Agreement
    string[] public agreementIds;

    event AgreementRegistered(
        string indexed negotiationId,
        bytes32 agreementHash,
        string featureType,
        string summary,
        uint256 timestamp
    );

    function registerAgreement(
        string calldata negotiationId,
        bytes32 agreementHash,
        string calldata featureType,
        string calldata summary
    ) external {
        agreements[negotiationId] = Agreement({
            agreementHash: agreementHash,
            featureType: featureType,
            summary: summary,
            timestamp: block.timestamp,
            registeredBy: msg.sender
        });
        agreementIds.push(negotiationId);

        emit AgreementRegistered(
            negotiationId, agreementHash, featureType, summary, block.timestamp
        );
    }

    function getAgreement(string calldata negotiationId)
        external view returns (Agreement memory)
    {
        return agreements[negotiationId];
    }

    function totalAgreements() external view returns (uint256) {
        return agreementIds.length;
    }
}

Deploy via Remix (5 minutes, no coding)

1. Open https://remix.ethereum.org
2. Create new file: AgreementRegistry.sol
3. Paste the contract above
4. Compile tab → Select 0.8.19 → Compile
5. Deploy tab:
   - Environment: "Injected Provider - MetaMask"
   - MetaMask: Switch to Polygon Amoy network (chain ID 80002)
   - Click Deploy → Confirm in MetaMask
6. Copy the deployed contract address
7. Paste into .env: AGREEMENT_CONTRACT_ADDRESS=0x...
8. Done. Takes 5 minutes.

MetaMask Polygon Amoy setup (if not already):
  Network Name: Polygon Amoy Testnet
  RPC URL: https://rpc-amoy.polygon.technology/
  Chain ID: 80002
  Currency Symbol: POL
  Block Explorer: https://amoy.polygonscan.com/

9. Backend Blockchain Module

Contract ABI

# backend/web3/contract_abi.py
AGREEMENT_REGISTRY_ABI = [
    {
        "inputs": [
            {"internalType": "string", "name": "negotiationId", "type": "string"},
            {"internalType": "bytes32", "name": "agreementHash", "type": "bytes32"},
            {"internalType": "string", "name": "featureType", "type": "string"},
            {"internalType": "string", "name": "summary", "type": "string"}
        ],
        "name": "registerAgreement",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "inputs": [{"internalType": "string", "name": "negotiationId", "type": "string"}],
        "name": "getAgreement",
        "outputs": [
            {"components": [
                {"internalType": "bytes32", "name": "agreementHash", "type": "bytes32"},
                {"internalType": "string", "name": "featureType", "type": "string"},
                {"internalType": "string", "name": "summary", "type": "string"},
                {"internalType": "uint256", "name": "timestamp", "type": "uint256"},
                {"internalType": "address", "name": "registeredBy", "type": "address"}
            ], "internalType": "struct AgreementRegistry.Agreement", "name": "", "type": "tuple"}
        ],
        "stateMutability": "view",
        "type": "function"
    },
    {
        "inputs": [],
        "name": "totalAgreements",
        "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}],
        "stateMutability": "view",
        "type": "function"
    },
    {
        "anonymous": False,
        "inputs": [
            {"indexed": True, "internalType": "string", "name": "negotiationId", "type": "string"},
            {"indexed": False, "internalType": "bytes32", "name": "agreementHash", "type": "bytes32"},
            {"indexed": False, "internalType": "string", "name": "featureType", "type": "string"},
            {"indexed": False, "internalType": "string", "name": "summary", "type": "string"},
            {"indexed": False, "internalType": "uint256", "name": "timestamp", "type": "uint256"}
        ],
        "name": "AgreementRegistered",
        "type": "event"
    }
]

Blockchain Module (The Whole Thing)

# backend/web3/__init__.py
pass
# backend/web3/blockchain.py
"""
The ENTIRE blockchain integration in one file.
Registers agreement proofs on Polygon Amoy via AgreementRegistry contract.
Users never interact with this. It's all backend.
"""
import hashlib
import json
from web3 import Web3
from web3.contract_abi import AGREEMENT_REGISTRY_ABI
import os

# ─── Configuration ───
POLYGON_RPC = os.getenv("POLYGON_RPC_URL", "https://rpc-amoy.polygon.technology/")
PRIVATE_KEY = os.getenv("POLYGON_PRIVATE_KEY", "")
CONTRACT_ADDRESS = os.getenv("AGREEMENT_CONTRACT_ADDRESS", "")
EXPLORER_BASE = "https://amoy.polygonscan.com"

# ─── Setup ───
w3 = Web3(Web3.HTTPProvider(POLYGON_RPC))
account = None
contract = None

if PRIVATE_KEY and CONTRACT_ADDRESS:
    try:
        account = w3.eth.account.from_key(PRIVATE_KEY)
        contract = w3.eth.contract(
            address=Web3.to_checksum_address(CONTRACT_ADDRESS),
            abi=AGREEMENT_REGISTRY_ABI
        )
        print(f"✅ Blockchain connected: Polygon Amoy | Account: {account.address}")
    except Exception as e:
        print(f"⚠️ Blockchain setup failed: {e}. Will use mock mode.")
else:
    print("⚠️ Blockchain not configured. Agreement proofs will be mocked.")


def hash_agreement(resolution_data: dict) -> bytes:
    """SHA-256 hash of the agreement data → bytes32 for the contract."""
    canonical = json.dumps(resolution_data, sort_keys=True, default=str)
    return hashlib.sha256(canonical.encode()).digest()


async def register_agreement_on_chain(negotiation_id: str, feature_type: str,
                                       summary: str, resolution_data: dict) -> dict:
    """
    Register an agreement proof on Polygon Amoy.
    Called automatically after every resolved negotiation.
    The user NEVER calls this — it's invisible backend magic.

    Returns: {tx_hash, block_number, explorer_url, agreement_hash, success}
    """
    agreement_hash = hash_agreement(resolution_data)

    # ─── If blockchain not configured, return mock proof ───
    if not contract or not account:
        mock_hash = "0x" + agreement_hash.hex()
        return {
            "success": True, "mock": True,
            "tx_hash": f"0xMOCK_{negotiation_id}_{'a' * 40}",
            "block_number": 0,
            "agreement_hash": mock_hash,
            "explorer_url": f"{EXPLORER_BASE}",
            "network": "polygon-amoy (mock)"
        }

    try:
        # Build the transaction
        nonce = w3.eth.get_transaction_count(account.address)

        tx = contract.functions.registerAgreement(
            negotiation_id,
            agreement_hash,              # bytes32
            feature_type,
            summary[:256]                # cap summary length for gas efficiency
        ).build_transaction({
            "from": account.address,
            "nonce": nonce,
            "gas": 300000,               # generous gas limit for testnet
            "gasPrice": w3.to_wei("30", "gwei"),  # Amoy gas price
            "chainId": 80002             # Polygon Amoy
        })

        # Sign and send
        signed = w3.eth.account.sign_transaction(tx, PRIVATE_KEY)
        tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
        tx_hash_hex = tx_hash.hex()

        # Wait for confirmation (Amoy is fast, ~2 seconds)
        receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=30)

        result = {
            "success": True, "mock": False,
            "tx_hash": tx_hash_hex,
            "block_number": receipt.blockNumber,
            "agreement_hash": "0x" + agreement_hash.hex(),
            "explorer_url": f"{EXPLORER_BASE}/tx/0x{tx_hash_hex}",
            "gas_used": receipt.gasUsed,
            "network": "polygon-amoy"
        }

        print(f"✅ Agreement registered on-chain: {result['explorer_url']}")
        return result

    except Exception as e:
        print(f"❌ Blockchain registration failed: {e}")
        # Graceful fallback — negotiation still works, just no on-chain proof
        return {
            "success": False, "mock": True, "error": str(e),
            "tx_hash": f"0xFAILED_{negotiation_id}",
            "block_number": 0,
            "agreement_hash": "0x" + agreement_hash.hex(),
            "explorer_url": EXPLORER_BASE,
            "network": "polygon-amoy (failed — see error)"
        }


def verify_agreement_on_chain(negotiation_id: str) -> dict:
    """
    Verify an agreement exists on-chain by reading the contract.
    Used by dashboard to confirm blockchain state.
    """
    if not contract:
        return {"verified": False, "reason": "Contract not configured"}

    try:
        result = contract.functions.getAgreement(negotiation_id).call()
        return {
            "verified": True,
            "agreement_hash": "0x" + result[0].hex(),
            "feature_type": result[1],
            "summary": result[2],
            "timestamp": result[3],
            "registered_by": result[4]
        }
    except Exception as e:
        return {"verified": False, "reason": str(e)}

10. Integration into Resolution Flow

Add blockchain registration to your existing handle_resolution function:

# In your run.py or wherever handle_resolution lives:

from web3.blockchain import register_agreement_on_chain
import database as db

# Inside handle_resolution, AFTER the existing UPI + voice + analytics logic:

async def handle_resolution(negotiation_id, resolution, feature_type,
                             user_a_id, user_b_id, bot_a, bot_b,
                             preferences_a, preferences_b):

    # ... existing code: build summary_text, UPI link, voice summary, analytics ...

    # ─── NEW: Register agreement on Polygon (invisible to user) ───
    blockchain_result = await register_agreement_on_chain(
        negotiation_id=negotiation_id,
        feature_type=feature_type,
        summary=resolution.get("summary", "Agreement reached"),
        resolution_data=resolution
    )

    # Store proof in DB
    await db.store_blockchain_proof(
        negotiation_id=negotiation_id,
        tx_hash=blockchain_result["tx_hash"],
        block_number=blockchain_result.get("block_number", 0),
        agreement_hash=blockchain_result["agreement_hash"],
        explorer_url=blockchain_result["explorer_url"],
        gas_used=blockchain_result.get("gas_used", 0)
    )

    # Add blockchain badge to the Telegram message
    if blockchain_result.get("success"):
        blockchain_text = (
            f"\n\n🔗 *Verified on Blockchain*\n"
            f"This agreement is permanently recorded on Polygon.\n"
            f"[View Proof on PolygonScan]({blockchain_result['explorer_url']})"
        )
    else:
        blockchain_text = "\n\n🔗 _Blockchain proof pending..._"

    summary_text += blockchain_text

    # ... continue with existing: send to users, voice notes, etc. ...

That's the ENTIRE integration. One function call added to your existing flow.


11. Telegram Bot Updates

New Command: /proof

# Add to your telegram bot.py

async def proof_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Show blockchain proof for a negotiation."""
    if not context.args:
        await update.message.reply_text(
            "🔗 *View blockchain proof*\n\n"
            "Usage: `/proof <negotiation_id>`\n"
            "You'll find the ID in your resolution messages.",
            parse_mode="Markdown"
        )
        return

    neg_id = context.args[0]
    proof = await db.get_blockchain_proof(neg_id)

    if not proof:
        await update.message.reply_text("❌ No blockchain proof found for that negotiation.")
        return

    await update.message.reply_text(
        f"🔗 *Blockchain Proof*\n\n"
        f"📋 Negotiation: `{neg_id}`\n"
        f"🔐 Agreement Hash: `{proof['agreement_hash'][:20]}...`\n"
        f"⛓️ Block: #{proof['block_number']}\n"
        f"📡 Network: Polygon Amoy\n\n"
        f"[🔍 View on PolygonScan]({proof['explorer_url']})\n\n"
        f"_This agreement is permanently and immutably recorded on the blockchain. "
        f"Neither party can alter or deny it._",
        parse_mode="Markdown",
        disable_web_page_preview=True
    )

# Register in create_bot():
# app.add_handler(CommandHandler("proof", proof_command))
# Add to COMMANDS: BotCommand("proof", "View blockchain proof for a negotiation"),

No /wallet command. No /reputation command. Users don't need them. Keep it clean.


12. Dashboard Components

BlockchainProof Component

// dashboard/components/BlockchainProof.tsx
import { ExternalLink, Shield, CheckCircle2 } from 'lucide-react';

interface BlockchainProofProps {
  txHash: string;
  blockNumber: number;
  explorerUrl: string;
  agreementHash: string;
  network: string;
}

export function BlockchainProof({
  txHash, blockNumber, explorerUrl, agreementHash, network
}: BlockchainProofProps) {
  return (
    <div className="bg-gradient-to-br from-violet-50 to-purple-50 rounded-xl p-5 border border-violet-200 shadow-sm">
      <div className="flex items-center gap-2 mb-4">
        <div className="bg-violet-600 p-1.5 rounded-lg">
          <Shield className="w-4 h-4 text-white" />
        </div>
        <h3 className="text-sm font-bold text-violet-900">On-Chain Proof</h3>
        <div className="ml-auto flex items-center gap-1 bg-green-100 text-green-700 text-xs px-2 py-0.5 rounded-full">
          <CheckCircle2 className="w-3 h-3" />
          Verified
        </div>
      </div>

      <div className="space-y-3 text-sm">
        <div>
          <span className="text-gray-500 text-xs uppercase tracking-wide">Transaction</span>
          <p className="font-mono text-violet-700 text-xs mt-0.5 break-all">
            {txHash.slice(0, 22)}...{txHash.slice(-8)}
          </p>
        </div>

        <div className="flex gap-4">
          <div>
            <span className="text-gray-500 text-xs uppercase tracking-wide">Block</span>
            <p className="font-mono font-bold text-gray-800">#{blockNumber}</p>
          </div>
          <div>
            <span className="text-gray-500 text-xs uppercase tracking-wide">Network</span>
            <p className="font-medium text-gray-800">Polygon Amoy</p>
          </div>
        </div>

        <div>
          <span className="text-gray-500 text-xs uppercase tracking-wide">Agreement Hash</span>
          <p className="font-mono text-xs text-gray-600 mt-0.5">
            {agreementHash.slice(0, 18)}...
          </p>
        </div>

        <a
          href={explorerUrl}
          target="_blank"
          rel="noopener noreferrer"
          className="flex items-center justify-center gap-2 bg-violet-600 hover:bg-violet-700
                     text-white rounded-lg px-4 py-2.5 text-sm font-medium transition mt-2"
        >
          <ExternalLink className="w-4 h-4" />
          View on PolygonScan
        </a>
      </div>

      <p className="text-xs text-gray-400 mt-3 text-center">
        This agreement is immutable and publicly verifiable
      </p>
    </div>
  );
}

Wire into Existing Negotiation Page

In your dashboard/app/negotiation/[id]/page.tsx, add after the existing resolution display:

// Fetch blockchain proof alongside negotiation data
const proof = negotiationData?.blockchain_proof;

// Render after ResolutionCard:
{proof && proof.tx_hash && (
  <BlockchainProof
    txHash={proof.tx_hash}
    blockNumber={proof.block_number}
    explorerUrl={proof.explorer_url}
    agreementHash={proof.agreement_hash}
    network="polygon-amoy"
  />
)}

Add to your FastAPI endpoint:

# In your /api/negotiations/{id} endpoint, add:
proof = await db.get_blockchain_proof(negotiation_id)

return {
    "negotiation": neg,
    "rounds": rounds,
    "participants": participants,
    "analytics": analytics,
    "blockchain_proof": dict(proof) if proof else None  # NEW
}

13. Milestone Execution

WM1: Mock System + Polygon Setup (Hour 0 → 2)

□ Step 1: Create backend/mock/__init__.py (MOCK_MODE = True)
□ Step 2: Create backend/mock/responses.py (copy from Section 7)
□ Step 3: Modify BaseAgent to check MOCK_MODE (Section 7)
□ Step 4: Modify run_negotiation to use mock scenarios (Section 7)
□ Step 5: Test mock negotiation — run all 3 scenarios with no Gemini calls
□ Step 6: pip install web3
□ Step 7: Deploy AgreementRegistry.sol via Remix IDE (Section 8)
□ Step 8: Copy contract address to .env
□ Step 9: Export MetaMask private key to .env
□ Step 10: Get more testnet POL from faucet if needed

✅ SUCCESS: Mock negotiations run. Contract deployed. web3.py connects to Polygon.

WM2: On-Chain Agreement Proof (Hour 2 → 5)

□ Step 1: Create backend/web3/__init__.py
□ Step 2: Create backend/web3/contract_abi.py (Section 9)
□ Step 3: Create backend/web3/blockchain.py (Section 9)
□ Step 4: Add blockchain_proofs table to database.py (Section 6)
□ Step 5: Add register_agreement_on_chain call to handle_resolution (Section 10)
□ Step 6: Test standalone: register one agreement, check PolygonScan

Test script:
    import asyncio
    from web3.blockchain import register_agreement_on_chain

    async def test():
        result = await register_agreement_on_chain(
            "test_001", "expenses",
            "Goa trip settled: Fuel 55-45, Hotel 50-50",
            {"settlement": 8050, "parties": ["Rahul", "Priya"]}
        )
        print(f"TX: {result['tx_hash']}")
        print(f"Explorer: {result['explorer_url']}")
        print(f"Block: {result['block_number']}")

    asyncio.run(test())

□ Step 7: Open the explorer URL — confirm transaction visible with event log
□ Step 8: Run full flow: mock negotiation → resolution → blockchain proof → Telegram message

✅ SUCCESS: PolygonScan shows your agreement. Telegram message includes "🔗 Verified" link.

WM3: Dashboard + Telegram /proof (Hour 5 → 8)

□ Step 1: Create dashboard/components/BlockchainProof.tsx (Section 12)
□ Step 2: Add blockchain_proof to your /api/negotiations/{id} endpoint
□ Step 3: Render BlockchainProof in negotiation detail page
□ Step 4: Add /proof command to Telegram bot (Section 11)
□ Step 5: Test: negotiation → dashboard shows blockchain card → /proof shows proof

✅ SUCCESS: Dashboard has pretty blockchain proof card. /proof works in Telegram.

WM4: Polish + Demo Prep (Hour 8 → 10)

□ Step 1: Run all 3 demo scenarios end-to-end (mock + blockchain + Telegram + dashboard)
□ Step 2: Pre-register 3+ agreements on-chain (so dashboard has data even if live demo fails)
□ Step 3: Screenshot PolygonScan proof pages as backup
□ Step 4: Record video backup of full flow
□ Step 5: Test UPI links still work (no regressions)
□ Step 6: Test voice summaries still work (no regressions)
□ Step 7: Verify mock mode doesn't break any existing features
□ Step 8: Prepare pitch script (Section 14)
□ Step 9: Get testnet POL balance > 0.5 POL (enough for 50+ registrations)
□ Step 10: Deep breath. You're ready.

✅ SUCCESS: Everything works. Video backup saved. Pitch rehearsed.

14. Demo Script

Demo 1: Restaurant Decision (60 sec)

User A: /coordinate @priya → "Dinner tonight, Thai or Indian, Bandra, ₹1500"
User B: /coordinate @rahul → "Thai or Mediterranean, vegetarian, ₹1200"

→ Mock negotiation: 2 rounds → Jaan Thai Restaurant, Bandra
→ Telegram shows: resolution text + voice note
→ NEW: "🔗 Verified on Blockchain" + PolygonScan link
→ Click link → PolygonScan shows the AgreementRegistered event

SAY: "This dinner plan is now permanently on the blockchain. Neither
side can later say 'I never agreed to that.' And the user never
touched a wallet or paid gas — it just happened."

Demo 2: Expense Splitting (90 sec)

User A: "Split Goa trip. Hotel 12K, fuel 3K, dinner 2K. UPI: rahul@paytm"
User B: "Fuel should be 50-50. I navigated."

→ Mock: 3 rounds → Fuel 55-45 → B owes ₹8,050
→ UPI payment button (tap to pay)
→ Voice note (different voices for A and B)
→ NEW: "🔗 Verified on Blockchain" + PolygonScan link
→ Dashboard shows: satisfaction chart + fairness score + blockchain proof card

SAY: "Look — the settlement is on UPI for India. But the PROOF that
they agreed to 55-45 fuel? That's on Polygon. Immutable. If there's
a dispute next month, the blockchain has the truth."

Demo 3: PS5 Marketplace (90 sec)

Seller: "PS5 + 2 controllers, ₹35K, minimum ₹30K"
Buyer: "Max ₹28K"

→ Mock: 4 rounds of haggling → Settles at ₹29,500
→ UPI payment + voice notes
→ NEW: "🔗 Deal sealed on blockchain"
→ /proof test_neg_id → shows full proof in Telegram
→ Dashboard: all 3 negotiations with blockchain cards

SAY: "Four rounds of AI haggling. ₹29,500. UPI to pay. Blockchain to
prove. This is what a trustless marketplace looks like when AI agents
handle the negotiation and blockchain handles the trust."

The Closing Line

"negoT8 makes coordination effortless. AI does the talking. Blockchain does the trusting. And the user? They just send a message on Telegram. That's it. That's consumer Web3."


15. Setup Commands

# 1. Install web3.py
pip install web3 --break-system-packages

# 2. Deploy contract via Remix (see Section 8 — manual, 5 min)
#    → Copy contract address

# 3. Export MetaMask private key
#    MetaMask → Account → ⋮ → Account Details → Show Private Key

# 4. Add to .env
echo 'POLYGON_RPC_URL=https://rpc-amoy.polygon.technology/' >> .env
echo 'POLYGON_PRIVATE_KEY=your_private_key' >> .env
echo 'AGREEMENT_CONTRACT_ADDRESS=0x_your_contract_address' >> .env
echo 'MOCK_MODE=true' >> .env

# 5. Get more testnet POL (optional but recommended)
#    https://faucet.quicknode.com/polygon/amoy
#    https://www.alchemy.com/faucets/polygon-amoy
#    https://faucets.chain.link/polygon-amoy

# 6. Test blockchain connection
python3 -c "
from web3 import Web3
w3 = Web3(Web3.HTTPProvider('https://rpc-amoy.polygon.technology/'))
print(f'Connected: {w3.is_connected()}')
print(f'Chain ID: {w3.eth.chain_id}')
print(f'Latest block: {w3.eth.block_number}')
"

16. Troubleshooting & Failsafes

"Polygon RPC is slow/down"

# Fallback RPCs (add to blockchain.py):
FALLBACK_RPCS = [
    "https://rpc-amoy.polygon.technology/",
    "https://polygon-amoy-bor-rpc.publicnode.com",
    "https://polygon-amoy.drpc.org",
]
# Try each until one works

"Contract call fails"

# The register_agreement_on_chain function already has try/except with mock fallback.
# If blockchain fails → negotiation still works → user sees "proof pending"
# Show pre-registered proofs from earlier test runs

"Out of testnet POL"

Use any of these faucets:
- https://faucet.quicknode.com/polygon/amoy (tweet + claim)
- https://www.alchemy.com/faucets/polygon-amoy (sign up, 0.5/day)
- https://faucets.chain.link/polygon-amoy (connect wallet)
Each tx costs ~0.0001 POL. $0.01 POL = 100 transactions.

"I don't want to deploy a contract (too risky / no time)"

SIMPLEST FALLBACK: Skip the contract entirely.
Just send 0-value transactions with agreement data in the input field:

    tx = {
        "to": account.address,  # send to self
        "value": 0,
        "data": w3.to_hex(json.dumps(resolution).encode()),
        "gas": 100000,
        "gasPrice": w3.to_wei("30", "gwei"),
        "nonce": w3.eth.get_transaction_count(account.address),
        "chainId": 80002
    }

This puts the agreement data on-chain WITHOUT a smart contract.
PolygonScan will show the data in "Input Data" field.
Less clean than a contract, but works in 5 minutes.

Demo-Day Failsafes

  1. Pre-register 3 agreements before the demo so PolygonScan already has data
  2. Screenshot every PolygonScan proof page as image backup
  3. Video record a perfect full run the night before
  4. Mock blockchain fallback — if blockchain is slow during demo, the mock result still shows a nice "proof pending" badge

Quick Reference Card

⛓️ Chain:          Polygon Amoy Testnet (Chain ID: 80002)
🔗 RPC:            https://rpc-amoy.polygon.technology/
🔍 Explorer:       https://amoy.polygonscan.com/
💰 Gas:            ~0.0001 POL per tx (free from faucet)
📝 Contract:       AgreementRegistry.sol (deployed via Remix)
🐍 Library:        web3.py (pip install web3)
🤖 Mock Mode:      MOCK_MODE=true (no Gemini calls)

USER SEES:
  "🔗 Verified on Blockchain" + PolygonScan link
  That's it. No wallets. No gas. No crypto knowledge.

BACKEND DOES:
  SHA-256(resolution) → registerAgreement() → PolygonScan

NEW FILES:          4 total
  backend/web3/blockchain.py
  backend/web3/contract_abi.py
  backend/mock/responses.py
  dashboard/components/BlockchainProof.tsx

NEW COMMAND:        /proof <negotiation_id>
NEW TABLE:          blockchain_proofs (1 table added)
NEW .ENV VARS:      3 (RPC URL, private key, contract address)

DEMO: 🍕 Restaurant → 💰 Expenses → 🛒 Marketplace
      Each shows "🔗 Verified" + PolygonScan link

BUILD TIME: 10 hours across 4 milestones

Web3 should be invisible. If the user has to understand blockchain to use your product, you've failed. negoT8 puts agreements on-chain silently — the user just gets a link that proves it. That's the future.

Go build it. Go win it. 🏆