From 88da5a052565b553181b8264d4ac003edf4e6fa7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 14 Feb 2026 20:42:38 +0000 Subject: [PATCH] Sovereign V2 Genesis Push: Consolidated fairscale and notary features with conflict resolution Co-authored-by: Freedomwithin <166790647+Freedomwithin@users.noreply.github.com> --- backend/.env.example | 8 + backend/package.json | 7 +- backend/services/notary_sync.ts | 174 ++++++++++++++++++ docs/FAIRSCALE_API.md | 78 ++++++++ frontend/src/components/Guard.tsx | 48 +++++ frontend/src/constants/integrity.ts | 2 + frontend/src/hooks/useIntegrity.ts | 70 +++++++ frontend/vercel.json | 7 + on-chain/trustchain-notary/Anchor.toml | 18 ++ on-chain/trustchain-notary/Cargo.toml | 13 ++ .../programs/trustchain-notary/Cargo.toml | 19 ++ .../programs/trustchain-notary/src/lib.rs | 62 +++++++ 12 files changed, 504 insertions(+), 2 deletions(-) create mode 100644 backend/.env.example create mode 100644 backend/services/notary_sync.ts create mode 100644 docs/FAIRSCALE_API.md create mode 100644 frontend/src/components/Guard.tsx create mode 100644 frontend/src/constants/integrity.ts create mode 100644 frontend/src/hooks/useIntegrity.ts create mode 100644 frontend/vercel.json create mode 100644 on-chain/trustchain-notary/Anchor.toml create mode 100644 on-chain/trustchain-notary/Cargo.toml create mode 100644 on-chain/trustchain-notary/programs/trustchain-notary/Cargo.toml create mode 100644 on-chain/trustchain-notary/programs/trustchain-notary/src/lib.rs diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..57b5e33 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,8 @@ +# TrustChain Backend Environment Variables + +# Solana RPC URL +SOLANA_RPC_URL=https://api.mainnet-beta.solana.com + +# Notary Secret Key (JSON Array of numbers or Hex String) +# CRITICAL: Do not commit the actual secret key to the repository! +NOTARY_SECRET=[1,2,3,...] diff --git a/backend/package.json b/backend/package.json index f2ac7d9..b42a2d3 100644 --- a/backend/package.json +++ b/backend/package.json @@ -9,14 +9,17 @@ "start": "node server.js", "dev": "node server.js", "test": "node test.js && node tests/security.test.js", - "test:engine": "node test.js" + "test:engine": "node test.js", + "sync:notary": "tsx services/notary_sync.ts" }, "dependencies": { + "@coral-xyz/anchor": "^0.29.0", "@solana/web3.js": "^1.98.4", "axios": "^1.6.8", "cors": "^2.8.6", "dotenv": "^17.3.1", - "express": "^4.18.2" + "express": "^4.18.2", + "tweetnacl": "^1.0.3" }, "devDependencies": { "@types/node": "^20.0.0", diff --git a/backend/services/notary_sync.ts b/backend/services/notary_sync.ts new file mode 100644 index 0000000..e338409 --- /dev/null +++ b/backend/services/notary_sync.ts @@ -0,0 +1,174 @@ +import { + Connection, + Keypair, + PublicKey, + Transaction, + SystemProgram, + TransactionInstruction, +} from "@solana/web3.js"; +import * as nacl from "tweetnacl"; +import crypto from "crypto"; +import dotenv from "dotenv"; + +dotenv.config(); + +// Mock data +const MOCK_WALLET = new PublicKey("11111111111111111111111111111111"); + +// Load Notary Secret from Environment Variable +if (!process.env.NOTARY_SECRET) { + throw new Error("NOTARY_SECRET environment variable not set. Please set it in .env file."); +} + +// Support both JSON array format and Hex string format for convenience +let notarySecret: Uint8Array; +try { + const parsed = JSON.parse(process.env.NOTARY_SECRET); + if (Array.isArray(parsed)) { + notarySecret = new Uint8Array(parsed); + } else { + throw new Error("NOTARY_SECRET JSON is not an array"); + } +} catch (e) { + // If not JSON, try Hex + // Remove '0x' prefix if present + const cleanHex = process.env.NOTARY_SECRET.replace(/^0x/, ''); + if (/^[0-9a-fA-F]+$/.test(cleanHex)) { + notarySecret = Uint8Array.from(Buffer.from(cleanHex, 'hex')); + } else { + throw new Error("Invalid NOTARY_SECRET format. Must be JSON array of numbers or Hex string."); + } +} + +const NOTARY_KEYPAIR = Keypair.fromSecretKey(notarySecret); + +// Placeholder Program ID from lib.rs +const PROGRAM_ID = new PublicKey("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); + +interface IntegrityScore { + gini: number; + hhi: number; + status: string; +} + +// Mock function to fetch score from Integrity Engine +async function fetchVerifiedScore(wallet: PublicKey): Promise { + // In reality, this would call backend/integrityEngine.js logic + console.log(`Fetching score for ${wallet.toBase58()}...`); + return { + gini: 0.25, + hhi: 0.15, + status: "VERIFIED", + }; +} + +async function updateOnChainPDA(wallet: PublicKey, score: IntegrityScore) { + // Connect to devnet (or localnet) + const connection = new Connection("https://api.devnet.solana.com", "confirmed"); + + // PDA derivation: seeds = [b"integrity", user.key().as_ref()] + const [pda] = PublicKey.findProgramAddressSync( + [Buffer.from("config"), wallet.toBuffer()], + PROGRAM_ID + ); + + console.log(`PDA for wallet ${wallet.toBase58()}: ${pda.toBase58()}`); + + // Calculate Discriminator for "global:update_integrity" + // Anchor uses sha256("global:") and takes first 8 bytes + const discriminator = crypto.createHash("sha256").update("global:update_integrity").digest().subarray(0, 8); + + const giniBuffer = Buffer.alloc(2); + giniBuffer.writeUInt16LE(Math.floor(score.gini * 10000)); // Scale by 10000 + + const hhiBuffer = Buffer.alloc(2); + hhiBuffer.writeUInt16LE(Math.floor(score.hhi * 10000)); // Scale by 10000 + + const statusBuffer = Buffer.alloc(1); + // status: VERIFIED = 1, PROBATIONARY = 2, ERROR = 0 (Example mapping) + let statusVal = 0; + if (score.status === 'VERIFIED') statusVal = 1; + else if (score.status === 'PROBATIONARY') statusVal = 2; + + statusBuffer.writeUInt8(statusVal); + + const data = Buffer.concat([discriminator, giniBuffer, hhiBuffer, statusBuffer]); + + // Construct instruction + // Accounts must match the UpdateIntegrity struct in lib.rs: + // 1. user_integrity (PDA, mut) + // 2. user (Unchecked, not mut) + // 3. notary (Signer, mut) + // 4. system_program + const instruction = new TransactionInstruction({ + keys: [ + { pubkey: pda, isSigner: false, isWritable: true }, // user_integrity + { pubkey: wallet, isSigner: false, isWritable: false }, // user + { pubkey: NOTARY_KEYPAIR.publicKey, isSigner: true, isWritable: true }, // notary + { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, // system_program + ], + programId: PROGRAM_ID, + data: data, + }); + + const transaction = new Transaction().add(instruction); + + // Sign transaction (Offline signing simulation) + // We need a recent blockhash to sign. + try { + const { blockhash } = await connection.getLatestBlockhash(); + transaction.recentBlockhash = blockhash; + } catch (err) { + console.warn("Could not fetch blockhash (network issue?), using mock blockhash for simulation."); + transaction.recentBlockhash = Keypair.generate().publicKey.toBase58(); // Mock blockhash + } + + transaction.feePayer = NOTARY_KEYPAIR.publicKey; + transaction.sign(NOTARY_KEYPAIR); + + // --------------------------------------------------------- + // Ed25519 Verification Step + // --------------------------------------------------------- + + // 1. Verify that the transaction object itself considers the signatures valid + if (!transaction.verifySignatures()) { + throw new Error("Transaction signature verification failed!"); + } + + // 2. Explicitly verify using nacl to ensure the Notary signed it + const signatureObj = transaction.signatures.find(s => s.publicKey.equals(NOTARY_KEYPAIR.publicKey)); + + if (!signatureObj || !signatureObj.signature) { + throw new Error("Notary signature missing from transaction"); + } + + const message = transaction.serializeMessage(); + const isValid = nacl.sign.detached.verify( + message, + signatureObj.signature, + NOTARY_KEYPAIR.publicKey.toBuffer() + ); + + if (isValid) { + console.log("✅ Ed25519 Signature Verified: Update authorized by TrustChain Notary."); + console.log("Notary Public Key:", NOTARY_KEYPAIR.publicKey.toBase58()); + console.log("Signature:", Buffer.from(signatureObj.signature).toString('hex')); + } else { + console.error("❌ Invalid Signature: Unauthorized update attempt."); + throw new Error("Security Violation: Invalid Signature"); + } + + console.log("Transaction successfully constructed with correct discriminator and accounts."); +} + +async function main() { + try { + const score = await fetchVerifiedScore(MOCK_WALLET); + await updateOnChainPDA(MOCK_WALLET, score); + } catch (e) { + console.error("Error during notary sync:", e); + process.exit(1); + } +} + +main(); diff --git a/docs/FAIRSCALE_API.md b/docs/FAIRSCALE_API.md new file mode 100644 index 0000000..0feb854 --- /dev/null +++ b/docs/FAIRSCALE_API.md @@ -0,0 +1,78 @@ +# FairScale API Documentation (Reconstructed) + +**Note:** This documentation is reconstructed from the project context and `README.md` as the original source (`https://docs.fairscale.xyz/`) was inaccessible during the environment setup. + +## Overview + +FairScale provides a reputation tiering system for wallet addresses, allowing protocols to assess the long-term behavior and fairness of liquidity providers and traders. + +## Endpoints + +### Get Wallet Reputation + +Retrieves the reputation tier and score for a specific wallet address. + +- **URL:** `https://api.fairscale.xyz/v1/reputation/:address` (Hypothetical endpoint) +- **Method:** `GET` +- **Params:** + - `address`: The Solana wallet address (Base58 encoded). + +#### Response + +```json +{ + "address": "WalletAddressHere", + "tier": 3, + "score": 85, + "lastUpdated": "2023-10-27T10:00:00Z" +} +``` + +- `tier`: Integer (1-5). Higher is better. + - Tier 1: New / Low Reputation (High Risk) + - Tier 2: Probationary + - Tier 3: Verified / Trusted + - Tier 4: High Reputation + - Tier 5: Elite +- `score`: Integer (0-100). Granular reputation score. + +## Integration Logic + +TrustChain integrates FairScale with its internal Gini coefficient analysis to determine eligibility for rewards and protected swaps. + +### Logic Flow + +1. **Query FairScale API** to get the `tier`. +2. **Calculate Gini Coefficient** locally or via TrustChain backend (`/api/verify`). +3. **Apply Gatekeeper Logic**: + +```javascript +const GINI_THRESHOLD = 0.5; // TrustChain Probationary Threshold +const FAIRSCALE_TIER_THRESHOLD = 2; // Minimum Tier required + +if (giniScore >= GINI_THRESHOLD || fairScoreTier < FAIRSCALE_TIER_THRESHOLD) { + return "BLOCKED"; +} +return "ALLOWED"; +``` + +### Pseudocode Reference + +From `README.md`: + +```python +async def check_lp_eligibility(wallet_address, wallet_trades): + fairscore_tier = await fairscale_api(wallet_address) + gini_score = calculate_gini_coefficient(wallet_trades) + + if gini_score > 0.3 or fairscore_tier < 2: + return "SYBIL_BLOCKED - Ineligible for LP rewards" + return "LP_REWARD_ELIGIBLE - Fair provider verified" +``` + +## Environment Variables + +Ensure the following are configured in your environment: + +- `FAIRSCALE_API_KEY`: API Key for accessing FairScale services (if required). +- `FAIRSCALE_API_URL`: Base URL for the API (default: `https://api.fairscale.xyz`). diff --git a/frontend/src/components/Guard.tsx b/frontend/src/components/Guard.tsx new file mode 100644 index 0000000..b7eba38 --- /dev/null +++ b/frontend/src/components/Guard.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { useIntegrity } from '../hooks/useIntegrity'; +import { PROBATIONARY_THRESHOLD, SYBIL_STATUS } from '../constants/integrity'; + +interface GuardProps { + children: React.ReactNode; + fallback?: React.ReactNode; + threshold?: number; + sybilStatus?: string; +} + +export const Guard: React.FC = ({ + children, + fallback, + threshold = PROBATIONARY_THRESHOLD, + sybilStatus = SYBIL_STATUS +}) => { + const { giniScore, loading, error, status } = useIntegrity(); + + if (loading) { + return
Verifying integrity...
; + } + + if (error) { + return
Error verifying wallet: {error}
; + } + + // Logic: Block if Gini > threshold or status is explicitly sybilStatus + const isSybil = (giniScore !== null && giniScore > threshold) || status === sybilStatus; + + if (isSybil) { + if (fallback) { + return <>{fallback}; + } + return ( +
+

Protected Swap: Access Denied

+

Your wallet integrity score indicates high risk (Sybil detected).

+
+ Gini Score: {giniScore?.toFixed(3)} + (Threshold: {threshold}) +
+
+ ); + } + + return <>{children}; +}; diff --git a/frontend/src/constants/integrity.ts b/frontend/src/constants/integrity.ts new file mode 100644 index 0000000..6d2bfe4 --- /dev/null +++ b/frontend/src/constants/integrity.ts @@ -0,0 +1,2 @@ +export const PROBATIONARY_THRESHOLD = 0.5; +export const SYBIL_STATUS = 'SYBIL'; diff --git a/frontend/src/hooks/useIntegrity.ts b/frontend/src/hooks/useIntegrity.ts new file mode 100644 index 0000000..c35ea6e --- /dev/null +++ b/frontend/src/hooks/useIntegrity.ts @@ -0,0 +1,70 @@ +import { useState, useEffect } from 'react'; +import { useWallet } from '@solana/wallet-adapter-react'; + +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'https://trustchain-2-backend.vercel.app'; + +export interface IntegrityData { + giniScore: number | null; + hhiScore: number | null; + status: string | null; + loading: boolean; + error: string | null; +} + +export function useIntegrity(): IntegrityData { + const { publicKey, connected } = useWallet(); + const [giniScore, setGiniScore] = useState(null); + const [hhiScore, setHhiScore] = useState(null); + const [status, setStatus] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + let abortController = new AbortController(); + + if (connected && publicKey) { + setLoading(true); + setError(null); + + fetch(`${API_BASE_URL}/api/verify`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ address: publicKey.toBase58() }), + signal: abortController.signal + }) + .then(res => { + if (!res.ok) { + throw new Error(`Failed to fetch integrity score: ${res.status} ${res.statusText}`); + } + return res.json(); + }) + .then(data => { + if (!abortController.signal.aborted) { + setGiniScore(data.giniScore != null ? parseFloat(data.giniScore) : null); + setHhiScore(data.hhiScore != null ? parseFloat(data.hhiScore) : null); + setStatus(data.status); + setLoading(false); + } + }) + .catch(err => { + if (!abortController.signal.aborted) { + console.error('Verify error:', err); + setError(err instanceof Error ? err.message : 'Unknown error'); + setLoading(false); + } + }); + } else { + setGiniScore(null); + setHhiScore(null); + setStatus(null); + setError(null); + setLoading(false); + } + + return () => { + abortController.abort(); + }; + }, [connected, publicKey]); + + return { giniScore, hhiScore, status, loading, error }; +} diff --git a/frontend/vercel.json b/frontend/vercel.json new file mode 100644 index 0000000..14df7a4 --- /dev/null +++ b/frontend/vercel.json @@ -0,0 +1,7 @@ +{ + "framework": "vite", + "buildCommand": "vite build", + "outputDirectory": "build", + "devCommand": "vite", + "installCommand": "npm install" +} diff --git a/on-chain/trustchain-notary/Anchor.toml b/on-chain/trustchain-notary/Anchor.toml new file mode 100644 index 0000000..fe2e47f --- /dev/null +++ b/on-chain/trustchain-notary/Anchor.toml @@ -0,0 +1,18 @@ +[toolchain] + +[features] +seeds = false +skip-lint = false + +[programs.localnet] +trustchain_notary = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS" + +[registry] +url = "https://api.apr.dev" + +[provider] +cluster = "Localnet" +wallet = "~/.config/solana/id.json" + +[scripts] +test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" diff --git a/on-chain/trustchain-notary/Cargo.toml b/on-chain/trustchain-notary/Cargo.toml new file mode 100644 index 0000000..d2861be --- /dev/null +++ b/on-chain/trustchain-notary/Cargo.toml @@ -0,0 +1,13 @@ +[workspace] +members = [ + "programs/trustchain-notary" +] + +[profile.release] +overflow-checks = true +lto = "fat" +codegen-units = 1 +[profile.release.build-override] +opt-level = 3 +incremental = false +codegen-units = 1 diff --git a/on-chain/trustchain-notary/programs/trustchain-notary/Cargo.toml b/on-chain/trustchain-notary/programs/trustchain-notary/Cargo.toml new file mode 100644 index 0000000..367a166 --- /dev/null +++ b/on-chain/trustchain-notary/programs/trustchain-notary/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "trustchain-notary" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "trustchain_notary" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] + +[dependencies] +anchor-lang = "0.29.0" diff --git a/on-chain/trustchain-notary/programs/trustchain-notary/src/lib.rs b/on-chain/trustchain-notary/programs/trustchain-notary/src/lib.rs new file mode 100644 index 0000000..0a297eb --- /dev/null +++ b/on-chain/trustchain-notary/programs/trustchain-notary/src/lib.rs @@ -0,0 +1,62 @@ +use anchor_lang::prelude::*; + +declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); + +// Hardcoded Notary Public Key +pub const NOTARY_PUBKEY: Pubkey = pubkey!("Eg39fu7jw4ynYg3N3mzqusb1qLEZBH2ov62T564BFdYF"); + +#[program] +pub mod trustchain_notary { + use super::*; + + pub fn update_integrity(ctx: Context, gini_score: u16, hhi_score: u16, status: u8) -> Result<()> { + // Verify signer is the Notary + require_keys_eq!(ctx.accounts.notary.key(), NOTARY_PUBKEY, TrustChainError::UnauthorizedNotary); + + let user_integrity = &mut ctx.accounts.user_integrity; + user_integrity.gini_score = gini_score; + user_integrity.hhi_score = hhi_score; + user_integrity.status = status; + user_integrity.last_updated = Clock::get()?.unix_timestamp; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct UpdateIntegrity<'info> { + #[account( + init_if_needed, + payer = notary, + space = 8 + 2 + 2 + 1 + 8 + 64, // Added 64 bytes padding for future upgrades + seeds = [b"config", user.key().as_ref()], + bump + )] + pub user_integrity: Account<'info, UserIntegrity>, + + /// CHECK: The user address we are updating the score for. + pub user: UncheckedAccount<'info>, + + #[account(mut)] + pub notary: Signer<'info>, + + pub system_program: Program<'info, System>, +} + +#[account] +pub struct UserIntegrity { + pub gini_score: u16, + pub hhi_score: u16, + pub status: u8, + pub last_updated: i64, +} + +#[error_code] +pub enum TrustChainError { + #[msg("Signer is not the authorized Notary.")] + UnauthorizedNotary, +} + +#[account] +pub struct GlobalConfig { + pub bump: u8, +}