# 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)](#1-10-hour-battle-plan) 2. [What We're Actually Building](#2-what-were-actually-building) 3. [Architecture — How Blockchain Stays Invisible](#3-architecture) 4. [Tech Stack Additions (Minimal)](#4-tech-stack-additions) 5. [Updated Project Structure](#5-updated-project-structure) 6. [Updated Database Schema](#6-updated-database-schema) 7. [Mock/Hardcoded Response System (No Gemini Calls)](#7-mock-system) 8. [Smart Contract — AgreementRegistry (Deploy via Remix)](#8-smart-contract) 9. [Backend Blockchain Module](#9-backend-blockchain-module) 10. [Integration into Resolution Flow](#10-integration-into-resolution-flow) 11. [Telegram Bot Updates](#11-telegram-bot-updates) 12. [Dashboard Blockchain Components](#12-dashboard-components) 13. [Milestone Execution (Hour-by-Hour)](#13-milestone-execution) 14. [Demo Script](#14-demo-script) 15. [Setup Commands](#15-setup-commands) 16. [Troubleshooting & Failsafes](#16-troubleshooting) --- ## 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 ```bash 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) ```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. ```sql -- 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`: ```python # 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: ```python 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. ```python # backend/mock/__init__.py MOCK_MODE = True # Set to True — bypasses ALL Gemini API calls ``` ```python # 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()`: ```python # 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()`: ```python # 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 ```solidity // 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 ```python # 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) ```python # backend/web3/__init__.py pass ``` ```python # 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: ```python # 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 ```python # 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 `\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 ```typescript // 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 (

On-Chain Proof

Verified
Transaction

{txHash.slice(0, 22)}...{txHash.slice(-8)}

Block

#{blockNumber}

Network

Polygon Amoy

Agreement Hash

{agreementHash.slice(0, 18)}...

View on PolygonScan

This agreement is immutable and publicly verifiable

); } ``` ### Wire into Existing Negotiation Page In your `dashboard/app/negotiation/[id]/page.tsx`, add after the existing resolution display: ```typescript // Fetch blockchain proof alongside negotiation data const proof = negotiationData?.blockchain_proof; // Render after ResolutionCard: {proof && proof.tx_hash && ( )} ``` Add to your FastAPI endpoint: ```python # 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 ```bash # 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" ```python # 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" ```python # 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 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. 🏆**