Skip to content

N1KH1LT0X1N/Synapse

Repository files navigation

Synapse

A decentralized marketplace where model creators mint their weights as NFTs, compute providers stake to serve inference, and every call settles on-chain.

Synapse is a full-stack on-chain model marketplace on Ethereum Sepolia. Model creators publish encrypted weights to IPFS, mint an ERC-721 that represents ownership and pricing, and split revenue with up to ten collaborators. Users pay per-token or subscribe, and independent compute nodes stake ETH, serve the HuggingFace pipeline locally, and settle token counts + result hashes back to the contract.

license network solidity node python ipfs next react PyPI - synapse-langchain

The Problem

Model creators hand their weights to closed APIs and lose provenance, pricing control, and any downstream revenue. Buyers cannot prove which model served their request, cannot audit who got paid, and cannot take their access with them. Compute providers have no neutral market to monetize spare GPUs without signing vendor contracts. Every existing "AI marketplace" is a billing UI on top of someone else's cluster — ownership, settlement and provenance are off-chain.

What We Built

A monorepo with six Solidity contracts, a two-role FastAPI backend (orchestrator + compute node), a Ponder v7 indexer, a Lit Protocol v7 encryption sidecar, and a Next.js 15 frontend. Every paid inference emits InferenceRequested / InferenceSettled, every mint emits ModelMinted, and every payout accrues to pendingWithdrawals for pull-payment.

Capability Implementation
Model ownership as an NFT with versioning packages/hardhat/contracts/ModelNFT.sol (mintModel, addVersion)
Per-call inference escrow + pull-payment settlement packages/hardhat/contracts/Marketplace.sol (purchaseInference, settleInference, withdraw)
Subscriptions with monotonic expiry Marketplace.subscribe + subscriptionExpiry mapping
Staked compute nodes with weighted selection ComputeRegistry.sol (registerNode, selectNode)
Deposit account with daily/monthly spend caps UserWallet.sol (deposit, pay, setLimits)
Per-token encryption ACL services/lit-agent/src/server.ts (buildACL → Lit v7 evmContractConditions)
IPFS pinning with multi-gateway fallback backend/shared/ipfs.py (Pinata v3 + dweb.link + ipfs.io)
On-chain event timeline backend/orchestrator/routes_events.py (eth_getLogs over ModelNFT + Marketplace)
Real HuggingFace pipeline execution backend/node/inference.py + backend/node/model_cache.py
Soulbound achievement badges BadgeSBT.sol (ERC-5192, checkAndAward)
Ratings tied to real usage Reputation.sol (onInferenceServed, rateModel)
GraphQL indexer of every contract event packages/ponder/src/index.ts
Cross-machine proof-of-inference miner backend/orchestrator/routes_mining.py + components/mine/MiningDashboard.tsx

Architecture

graph TD
    subgraph Frontend_Next_js_15
        Landing[app/page.tsx]
        AppShell[app/app/layout.tsx]
        ModelPage[app/app/model/id/page.tsx]
        UploadPage[app/app/upload/page.tsx]
        NodesPage[app/app/nodes/page.tsx]
        WalletCtx[lib/wallet.tsx::WalletProvider]
        ApiClient[lib/api.ts]
        ContractsClient[lib/contracts.ts]
        PonderClient[lib/ponder.ts]
    end

    subgraph Backend_FastAPI
        Orchestrator[backend/main.py ROLE=orchestrator]
        ComputeNode[backend/main.py ROLE=node]
        RoutesInfer[routes_infer.py]
        RoutesModels[routes_models.py]
        RoutesPlatform[routes_platform.py]
        RoutesEvents[routes_events.py]
        RoutesNodes[routes_nodes.py]
        RoutesMining[routes_mining.py]
        NodeExecute[node/routes_node.py::/node/execute]
    end

    subgraph Blockchain_Sepolia
        ModelNFT[ModelNFT.sol]
        Marketplace[Marketplace.sol]
        ComputeRegistry[ComputeRegistry.sol]
        UserWallet[UserWallet.sol]
        Reputation[Reputation.sol]
        BadgeSBT[BadgeSBT.sol]
    end

    subgraph Storage
        Pinata[Pinata v3 uploads.pinata.cloud/v3/files]
        Gateways[IPFS gateways CSV fallback]
        Ponder[Ponder v7 GraphQL localhost:42069]
    end

    subgraph Compute_Lit
        LitAgent[services/lit-agent/src/server.ts]
        HFCache[node/model_cache.py]
        Pipeline[node/inference.py::load_pipeline_from_metadata]
    end

    UploadPage -->|uploadModel| ApiClient
    ApiClient -->|POST /api/models/upload| RoutesModels
    RoutesModels -->|pin_file/pin_json| Pinata
    RoutesModels -->|encrypt_for_token| LitAgent
    RoutesModels -->|totalSupply| ModelNFT
    UploadPage -->|mintModel| ContractsClient
    ContractsClient -->|mintModel CIDs| ModelNFT

    ModelPage -->|purchaseInference| ContractsClient
    ContractsClient -->|purchaseInference| Marketplace
    Marketplace -->|selectNode pipelineTag| ComputeRegistry
    Marketplace -->|credit refund| UserWallet

    ModelPage -->|infer| ApiClient
    ApiClient -->|POST /api/infer| RoutesInfer
    RoutesInfer -->|sessions sessionId| Marketplace
    RoutesInfer -->|node endpoint| ComputeRegistry
    RoutesInfer -->|POST /node/execute| NodeExecute
    NodeExecute -->|ensure_model_local| HFCache
    HFCache -->|fetch_cid| Gateways
    NodeExecute -->|decrypt| LitAgent
    NodeExecute -->|load_pipeline_from_metadata| Pipeline
    NodeExecute -->|settleInference| Marketplace
    Marketplace -->|onInferenceServed| Reputation
    Marketplace -->|checkAndAward| BadgeSBT

    ModelPage -->|GET /platform/models/id/events| RoutesEvents
    RoutesEvents -->|eth_getLogs| ModelNFT
    RoutesEvents -->|eth_getLogs| Marketplace

    NodesPage -->|GET /api/nodes/probe| RoutesNodes
    RoutesNodes -->|nodeList node| ComputeRegistry

    PonderClient -->|GraphQL| Ponder
    Ponder -->|indexes events| ModelNFT
    Ponder -->|indexes events| Marketplace
    Ponder -->|indexes events| ComputeRegistry
    Ponder -->|indexes events| UserWallet
    Ponder -->|indexes events| BadgeSBT
    Ponder -->|indexes events| Reputation

    RoutesPlatform -->|fallback queries| Ponder
Loading

Frontend (app/, components/, lib/) runs on Next.js 15 with the App Router. lib/wallet.tsx owns the window.ethereum session, caches a SIWE token for 240 s, and hands both to lib/contracts.ts (writes via viem walletClient) and lib/api.ts (reads + SIWE-gated downloads to the orchestrator). lib/ponder.ts is a thin GraphQL client over the indexer for live counters.

Backend (backend/) ships one FastAPI app selected at import time by the ROLE env var. ROLE=orchestrator mounts routes_models, routes_infer, routes_platform, routes_events, routes_nodes, routes_mining. ROLE=node mounts routes_node (/node/execute, /node/info, /node/health). All writes use web3.py 7.5 against the Alchemy Sepolia RPC.

Blockchain (packages/hardhat/contracts/) is Solidity 0.8.26 on top of OpenZeppelin v5. Six contracts (ModelNFT, Marketplace, ComputeRegistry, UserWallet, Reputation, BadgeSBT) deployed to Sepolia via hardhat-deploy scripts in packages/hardhat/deploy/. Wiring is done in 04_Wiring.ts and is idempotent.

Storage. Weights and metadata JSON are pinned via Pinata v3 (backend/shared/ipfs.py::pin_file) and read through the CSV fallback chain in IPFS_GATEWAY_URLS. Event data is mirrored into Ponder (packages/ponder/src/index.ts) for sub-second UI aggregations; the backend falls back to direct eth_getLogs when Ponder lags.

Compute. services/lit-agent/ is a Node.js sidecar that wraps Lit Protocol v7 (datil-dev) and enforces a per-token ACL of Marketplace.hasAccess(tokenId, :userAddress) OR ComputeRegistry.isActiveNode(:userAddress). Nodes run backend/node/inference.py which materialises HuggingFace pipelines from a local cache.


Smart Contract Reference

sequenceDiagram
    autonumber
    participant User as EOA (MetaMask)
    participant UP as app/upload/page.tsx
    participant API as backend/routes_models.py
    participant LIT as lit-agent/server.ts
    participant IPFS as Pinata v3
    participant MNFT as ModelNFT.sol
    participant MP as Marketplace.sol
    participant CR as ComputeRegistry.sol
    participant NODE as node/routes_node.py
    participant REP as Reputation.sol
    participant SBT as BadgeSBT.sol

    rect rgb(245,245,245)
    Note over User,SBT: Flow A — Developer publishes a model
    User->>+UP: select file + metadata
    UP->>+API: POST /api/models/upload (multipart)
    API->>+MNFT: totalSupply() (predict next tokenId)
    MNFT-->>-API: n
    API->>+LIT: POST /encrypt {dataB64, tokenId=n+1, addresses}
    LIT-->>-API: {ciphertext, dataToEncryptHash, evmContractConditions}
    API->>+IPFS: POST /v3/files (ciphertext)
    IPFS-->>-API: modelCID
    API->>+IPFS: POST /v3/files (metadata.json)
    IPFS-->>-API: metadataCID
    API-->>-UP: {modelCID, metadataCID, predictedTokenId}
    UP->>+MNFT: mintModel(modelCID, metadataCID, framework, pipelineTag, license, tags)
    MNFT->>SBT: checkAndAward(creator, tokenId)
    MNFT-->>-UP: tokenId
    UP->>+MP: list(tokenId, ppIn, ppOut, subDay)
    MP-->>-UP: ok
    end

    rect rgb(240,240,245)
    Note over User,SBT: Flow B — Buyer pays and runs inference
    User->>+MP: purchaseInference(tokenId, maxTokens) {value: ceilingCost}
    MP->>CR: selectNode(pipelineTag)
    CR-->>MP: nodeAddress
    MP->>CR: bumpOpenSessions(nodeAddress, true)
    MP-->>-User: sessionId

    User->>+API: POST /api/infer {sessionId, tokenId, prompt, user}
    API->>MP: sessions(sessionId)
    API->>CR: node(selectedNode)
    API->>+NODE: POST /node/execute
    NODE->>MP: sessions(sessionId)
    NODE->>NODE: ensure_model_local + run HF pipeline
    NODE->>+MP: settleInference(sessionId, in, out, latencyMs, resultHash)
    MP->>MP: _distribute(tokenId, actualCost, node)
    MP->>REP: onInferenceServed(tokenId, user)
    MP->>CR: recordCall(node, true, latencyMs)
    MP->>CR: bumpOpenSessions(node, false)
    MP->>SBT: checkAndAward(user, tokenId)
    MP-->>-NODE: ok
    NODE-->>-API: {outputStr, tokens, settlementTxHash, resultHash}
    API-->>-User: InferenceResponse
    User->>+MP: withdraw()
    MP-->>-User: ETH
    end
Loading
Contract Purpose Key Functions
ModelNFT.sol ERC-721 ownership + versioning of models mintModel, mintModelFor, addVersion, model, versions, withdraw
Marketplace.sol Listings, escrow, settlement, pull-payment distribution list, subscribe, purchaseInference, settleInference, reclaimExpiredSession, setFees, withdraw
ComputeRegistry.sol Node staking + weighted node selection registerNode, deregister, selectNode, recordCall, bumpOpenSessions, node
UserWallet.sol Credit account + daily/monthly limits + sponsored calls deposit, withdraw, setLimits, pay, credit, spendAndCheck, setSponsoredTarget
Reputation.sol Usage-gated ratings onInferenceServed, rateModel, averageRating, usageCount
BadgeSBT.sol Soulbound ERC-5192 achievement badges checkAndAward, awardEarlyAdopter, locked, burnBadge

ModelNFT.sol

  • Purpose: Mint and version models as ERC-721 tokens. Non-burnable.
  • Inherits from: ERC721, ERC721Enumerable, ERC721URIStorage, Ownable, ReentrancyGuardTransient, IModelNFT.
Function Visibility Parameters Returns Description
mintModel external payable string modelCID, string metadataCID, bytes32 framework, bytes32 pipelineTag, bytes32 license, bytes32[] tags_ uint256 tokenId Mint a new model NFT to msg.sender. Requires msg.value >= mintFee (default 0.001 ether).
mintModelFor external payable address creator, string modelCID, string metadataCID, bytes32 framework, bytes32 pipelineTag, bytes32 license, bytes32[] tags_ uint256 tokenId Sponsored mint; only callable by wired UserWallet.
addVersion external uint256 tokenId, string modelCID, string metadataCID, string changelog Append a version to an existing model; only NFT owner.
withdraw external Claim pendingWithdrawals[msg.sender] via pull-payment.
setBadgeSBT external onlyOwner IBadgeSBT b Wire the BadgeSBT contract.
setTreasury external onlyOwner address payable t Update fee sink.
setMintFee external onlyOwner uint96 f Update the flat mint fee.
setUserWallet external onlyOwner address w Enable/disable mintModelFor.
model external view uint256 tokenId ModelMetadata Latest metadata struct for a token.
versions external view uint256 tokenId Version[] Full version history.
versionsPaged external view uint256 tokenId, uint256 offset, uint256 limit Version[] Paginated history.
latestMetadataCID external view uint256 tokenId string Top-level metadata CID.
tags external view uint256 tokenId bytes32[] Tags set at mint.

Events: ModelMinted(uint256,address,string,string,bytes32,bytes32,bytes32), VersionAdded(uint256,uint256,string,string,string), ModelTagged(uint256,bytes32).

Marketplace.sol

  • Purpose: Listing, subscription, per-call escrow, settlement, and pull-payment distribution.
  • Inherits from: Ownable, Pausable, ReentrancyGuardTransient.
Function Visibility Parameters Returns Description
list external whenNotPaused uint256 tokenId, uint96 ppIn, uint96 ppOut, uint96 subDay NFT owner lists a model with per-token and per-day prices.
unlist external uint256 tokenId NFT owner removes the listing.
setCollaborators external whenNotPaused uint256 tokenId, address[] payees, uint16[] bps Revenue split in basis points; sum must equal 10000; up to 10 payees.
collaboratorsOf external view uint256 tokenId (address[], uint16[]) Current split.
subscribe external payable whenNotPaused nonReentrant uint256 tokenId, uint32 daysCount Buyer pays daysCount * subscriptionPricePerDay.
subscribeFor external payable onlyUserWallet address user, uint256 tokenId, uint32 daysCount Sponsored subscription.
hasActiveSubscription public view uint256 tokenId, address user bool
quote external view uint256 tokenId, uint32 maxTokens uint256 Ceiling cost for a per-call session.
purchaseInference external payable whenNotPaused nonReentrant uint256 tokenId, uint32 maxTokens bytes32 sessionId Escrow ceiling cost and open a session.
purchaseInferenceFor external payable onlyUserWallet address user, uint256 tokenId, uint32 maxTokens bytes32 sessionId Sponsored per-call.
settleInference external nonReentrant bytes32 sessionId, uint32 inputTokens, uint32 outputTokens, uint32 latencyMs, bytes32 resultHash Only assigned node; settles, distributes, refunds, awards badges.
reclaimExpiredSession external nonReentrant bytes32 sessionId Anyone; refunds full escrow after SESSION_TIMEOUT (1h) and slashes node success rate.
withdraw external nonReentrant Claim pendingWithdrawals[msg.sender].
hasAccess external view uint256 tokenId, address user bool Ownership or active sub — used by Lit ACL.
setFees external onlyOwner uint16 _protocolFeeBps, uint16 _nodeFeeBps Rate-limited: sum ≤ 3000 bps, each step ≤ 100 bps up.
setTreasury external onlyOwner address payable t
pause / unpause external onlyOwner Gate new spend; unwinds stay open.

Events: ModelListed, CollaboratorsSet, Subscribed, InferenceRequested, InferenceSettled, Distributed, Withdrawable, FeesChanged.

ComputeRegistry.sol

  • Purpose: Stake-gated node registry with weighted selection on pipelineTag.
  • Inherits from: Ownable, ReentrancyGuardTransient.
Function Visibility Parameters Returns Description
registerNode external payable string endpoint, bytes32[] tasks Stake ≥ MIN_STAKE (0.001 ETH).
registerNodeFor external payable onlyUserWallet address operator, string endpoint, bytes32[] tasks Sponsored node registration.
deregister external nonReentrant Refund stake; reverts while openSessions > 0.
selectNode external view bytes32 pipelineTag address best Weighted score over up to 50 candidates.
recordCall external onlyMarketplace address node_, bool success, uint32 latencyMs EWMA latency + success counters.
bumpOpenSessions external onlyMarketplace address node_, bool inc
isActiveNode external view address a bool
node external view address a Node Full tuple used by the orchestrator.
nodeListLength external view uint256
setMarketplace external onlyOwner address mp
setUserWallet external onlyOwner address w

Events: NodeRegistered, NodeDeregistered, CallRecorded.

UserWallet.sol

  • Purpose: ETH credit balance with per-user daily/monthly spend caps and a pay(target, amount, data) allow-listed relay.
  • Inherits from: Ownable, ReentrancyGuardTransient.
Function Visibility Parameters Returns Description
deposit external payable Top up caller balance.
withdraw external nonReentrant uint256 amount Drain caller balance.
setLimits external uint128 daily, uint128 monthly 0 means unlimited.
pay external nonReentrant address target, uint256 amount, bytes data bytes ret Call target with value: amount; records spend. Target must be in sponsoredTargets.
credit external payable onlyMarketplace address user, uint256 amount Refund path used by Marketplace.
spendAndCheck external onlyMarketplace address user, uint256 amount Window bookkeeping for legacy EOA path.
setMarketplace external onlyOwner address mp Auto-allow-lists the marketplace.
setSponsoredTarget external onlyOwner address target, bool allowed Manage pay() allow-list.
remainingDaily / remainingMonthly external view address user uint256

Events: Deposited, Withdrew, Credited, LimitsSet, WalletSpend, SponsoredTargetSet, MarketplaceSet.

Reputation.sol

  • Purpose: 1–5 star ratings gated on actual inference usage.
  • Inherits from: Ownable.
Function Visibility Parameters Returns Description
onInferenceServed external onlyMarketplace uint256 tokenId, address user Marks hasUsed and increments usageCount.
rateModel external uint256 tokenId, uint8 score Caller must have hasUsed, one rating per user.
averageRating external view uint256 tokenId uint256 Returns average × 100.
totalRatings / sumRatings / usageCount external view uint256 tokenId uint32 / uint64 / uint64
setMarketplace external onlyOwner address mp

Events: ModelRated.

BadgeSBT.sol

  • Purpose: Soulbound (ERC-5192) achievement badges.
  • Inherits from: ERC721, ERC721URIStorage, Ownable, IERC5192.
Function Visibility Parameters Returns Description
checkAndAward external address user, uint256 tokenId Callable by Marketplace, ModelNFT, or owner; idempotent per (user, kind).
awardEarlyAdopter external onlyOwner address user Seed-time grant.
burnBadge external onlyOwner uint256 tokenId Demo repair; has is preserved.
locked external pure uint256 bool Always true (ERC-5192).
setMarketplace external onlyOwner address mp

Events: BadgeAwarded(address,uint8,uint256), Locked(uint256).

Kinds: FIRST_MODEL=0, TEN_MODELS=1, HUNDRED_INFERENCES_SERVED=2, TOP_RATED=3, EARLY_ADOPTER=4.


Data Flow: Model Lifecycle

flowchart LR
    DEV[Developer: app/upload/page.tsx] -->|multipart file + metadata_json| ORCH[backend orchestrator routes_models.upload_model]
    ORCH -->|_validate_metadata pipeline_tag + tags regex| VAL{valid}
    VAL -->|no| ERR[HTTP 400]
    VAL -->|yes| PRED[_predict_next_token_id modelnft.totalSupply + 1]
    PRED -->|encrypt true| LIT[services lit-agent POST /encrypt]
    LIT -->|Lit v7 encryptString evmContractConditions ACL| CIPHER[ciphertext + dataToEncryptHash]
    CIPHER -->|pin_file ciphertext.enc| PINATA1[Pinata v3]
    PINATA1 -->|data.cid| MCID[modelCID]
    ORCH -->|synapse.lit_data_hash and model_cid| META[metadata.json]
    META -->|pin_json| PINATA2[Pinata v3]
    PINATA2 -->|data.cid| MDCID[metadataCID]
    MCID --> RET[UploadResponse]
    MDCID --> RET
    RET -->|ContractsClient.mintModel| MNFT[ModelNFT.sol]
    MNFT -->|emit ModelMinted| PONDER[Ponder v7 model table]
Loading
  1. Frontend sends the weights blob + metadata JSON to POST /api/models/upload.
  2. _validate_metadata enforces the pipeline tag + license + tags whitelist from routes_models.py.
  3. _predict_next_token_id calls ModelNFT.totalSupply() and the predicted id (n+1) is baked into the Lit ACL so a race losing the mint triggers a clean re-upload.
  4. If encrypt=true and LIT_KILL_SWITCH is false, encrypt_for_token POSTs to the Lit sidecar, which builds evmContractConditions = [hasAccess(tokenId, :userAddress), or, isActiveNode(:userAddress)] and encrypts the base64 payload.
  5. The ciphertext is pinned via Pinata v3 (uploads.pinata.cloud/v3/files, network=public) and the resulting CID is embedded in metadata.synapse.model_cid.
  6. The metadata JSON is pinned as a second object and returned as metadataCID.
  7. Frontend calls ModelNFT.mintModel(modelCID, metadataCID, framework, pipelineTag, license, tags) from the user's wallet — emitting ModelMinted(tokenId, creator, modelCID, metadataCID, pipelineTag, framework, license). Ponder indexes the event into the model table.

Compute Network

sequenceDiagram
    autonumber
    participant U as User (frontend)
    participant MP as Marketplace.sol
    participant CR as ComputeRegistry.sol
    participant ORCH as orchestrator/routes_infer.py
    participant NODE as node/routes_node.py
    participant LIT as lit-agent/decrypt
    participant HF as HuggingFace pipeline
    participant REP as Reputation.sol
    participant SBT as BadgeSBT.sol

    U->>MP: purchaseInference(tokenId, maxTokens)
    MP->>CR: selectNode(pipelineTag)
    CR-->>MP: bestNode
    MP->>CR: bumpOpenSessions(bestNode, true)
    MP-->>U: sessionId (InferenceRequested)
    U->>+ORCH: POST /api/infer {sessionId, tokenId, prompt, user}
    ORCH->>MP: sessions(sessionId)
    ORCH->>CR: node(bestNode)
    ORCH->>+NODE: POST /node/execute
    NODE->>MP: sessions(sessionId)
    NODE->>NODE: ensure_model_local(tokenId, metadata)
    alt encrypted
        NODE->>LIT: POST /decrypt (operator self-auth)
        LIT-->>NODE: plaintextB64
    end
    NODE->>HF: pipeline(prompt, max_new_tokens=maxTokens)
    HF-->>NODE: raw result
    NODE->>NODE: extract_output_text + count_tokens + keccak resultHash
    NODE->>+MP: settleInference(sessionId, in, out, latencyMs, resultHash)
    MP->>MP: _distribute(tokenId, actualCost, node)
    MP->>REP: onInferenceServed(tokenId, user)
    MP->>CR: recordCall(node, true, latencyMs)
    MP->>CR: bumpOpenSessions(node, false)
    MP->>SBT: checkAndAward(user, tokenId)
    MP->>SBT: checkAndAward(ownerOf(tokenId), tokenId)
    MP-->>-NODE: tx hash
    NODE-->>-ORCH: ExecuteResponse
    ORCH-->>-U: InferenceResponse
Loading

The compute layer is not simulated. backend/node/routes_node.py::execute loads a real HuggingFace transformers pipeline (inference.py::load_pipeline_from_metadata), runs the prompt through it, counts tokens via tokens.count_tokens, and signs settleInference from NODE_PRIVATE_KEY with a per-process nonce lock. Payment settlement is therefore a live on-chain transaction, not a synthetic event. Routes in routes_platform.py contain a seeded catalog and a _synth_nodes generator that drive the landing-page marquee and the /platform/nodes endpoint when the chain is empty — this is flagged in Known Limitations so nothing is hidden.


Frontend

stateDiagram-v2
    [*] --> Landing
    Landing: app/page.tsx (marketing)
    Landing --> Disconnected: enter app/*

    state Disconnected {
        [*] --> BrowseRO
        BrowseRO: app/app/browse/page.tsx
        BrowseRO --> ModelRO: click card
        ModelRO: app/app/model/id/page.tsx
    }

    Disconnected --> Connected: WalletProvider.connect()
    Connected: lib/wallet.tsx window.ethereum

    state Connected {
        [*] --> Dashboard
        Dashboard: app/app/dashboard
        Dashboard --> Browse: nav
        Browse --> Model: select
        Model --> SignSIWE: start inference
        SignSIWE: WalletProvider.siwe() cached 240s
        SignSIWE --> Paying: purchaseInference tx
        Paying: contracts.ts Marketplace.purchaseInference
        Paying --> Awaiting: receipt + sessionId
        Awaiting --> Result: POST /api/infer
        Result: settled on-chain + resultHash
        Result --> Model

        Model --> Upload: nav
        Upload: POST /api/models/upload + ModelNFT.mintModel
        Model --> NodesPage: nav
        NodesPage: GET /api/nodes/probe
        NodesPage --> RegisterNode: op path
        RegisterNode: ComputeRegistry.registerNode
        Dashboard --> Mine: nav
        Mine: POST /api/mine/submit
        Dashboard --> Profile
        Profile: GET /api/platform/profile/address
    }

    Connected --> Disconnected: WalletProvider.disconnect()
Loading
Route Component Description Contract Calls
/ app/page.tsx Marketing landing with Three.js globe and stats marquee
/browse app/(app)/browse/page.tsx Model catalog with filters — (reads via /api/platform/models)
/model/[id] app/(app)/model/[id]/page.tsx Model detail + timeline + pricing Marketplace.purchaseInference, Marketplace.subscribe
/model/[id]/inference app/(app)/model/[id]/inference/page.tsx Prompt UI + on-chain settlement view Marketplace.purchaseInference
/upload app/(app)/upload/page.tsx Upload weights, mint NFT, list ModelNFT.mintModel, Marketplace.list, Marketplace.setCollaborators
/mine app/(app)/mine/page.tsx Proof-of-inference miner + owner earnings Marketplace.withdraw, ModelNFT.withdraw
/nodes app/(app)/nodes/page.tsx Registered compute nodes + probe
/nodes/register app/(app)/nodes/register/page.tsx Stake and register as a node ComputeRegistry.registerNode
/nodes/me app/(app)/nodes/me/page.tsx Operator view of your node ComputeRegistry.deregister
/dashboard app/(app)/dashboard/page.tsx Per-address aggregates
/profile/[address] app/(app)/profile/[address]/page.tsx Public profile
/profile/me app/(app)/profile/me/page.tsx Your profile redirect
/arena app/(app)/arena/page.tsx Side-by-side model battle Marketplace.purchaseInference
/compare app/(app)/compare/page.tsx Model comparison
/architecture app/(app)/architecture/page.tsx In-app architecture diagram
/flows app/(app)/flows/page.tsx React Flow tx diagrams
/dna/[id] app/(app)/dna/[id]/page.tsx Neural-DNA fingerprint view
/live app/(app)/live/page.tsx Live inference feed
/run app/(app)/run/page.tsx Quick-run playground Marketplace.purchaseInference
/settings app/(app)/settings/page.tsx Wallet + limits UserWallet.setLimits, UserWallet.deposit, UserWallet.withdraw
/login app/(app)/login/page.tsx SIWE-only login

Tech Stack

Layer Technology Version Why This Choice
Contracts language Solidity 0.8.26 Native transient-storage primitives used by ReentrancyGuardTransient
Contracts framework Hardhat 2.22.17 (packages/hardhat/package.json) hardhat-deploy replayable scripts + TypeChain v8
Contract libs OpenZeppelin Contracts 5.1.0 ERC-721/5192 + Ownable v5 APIs
Indexer Ponder see packages/ponder/package.json Typed GraphQL over multi-contract event set
Frontend framework Next.js (App Router) 15.0.3 React 19 server components + edge routing
React React 19.0.0 Concurrent features, use() in server components
Wallet client viem 2.21.45 Typed publicClient / walletClient with chain definitions
SIWE (frontend) siwe 2.3.2 Browser-side message builder
3D three + @react-three/fiber 0.169.0 / 9.6.0 Hero globe
UI animation framer-motion 11.11.1 Section transitions
Icons lucide-react 0.454.0
CSS Tailwind CSS 3.4.14
Backend framework FastAPI 0.115.4 (backend/requirements.txt) Typed pydantic v2 models + async
ASGI server uvicorn[standard] 0.32.0
Config pydantic-settings 2.6.1 Typed env matrix
Ethereum client web3.py 7.5.0 raw_transaction snake_case and EIP-1559 helpers
ETH account eth-account 0.13.4
ML transformers + tokenizers 4.46.2 / 0.20.3 HF pipelines used by node/inference.py
ML runtime torch (CPU) 2.6.0+cpu From PyTorch CPU index
SIWE (backend) siwe (Python) 4.4.0
Lit sidecar @lit-protocol/lit-node-client, @lit-protocol/encryption Lit v7 (LIT_NETWORK.DatilDev) Custom-contract-call ACL
IPFS Pinata v3 (uploads.pinata.cloud/v3/files) v3 Free-tier 100 MB pins

Project Structure

synergy-ggw/
├── app/                                — Next.js 15 App Router
│   ├── layout.tsx                      — Root HTML + WalletProvider
│   ├── page.tsx                        — Marketing landing
│   ├── globals.css                     — Tailwind base
│   └── (app)/                          — Authenticated app shell
│       ├── layout.tsx                  — Sidebar + TopBar
│       ├── architecture/               — In-app architecture diagram page
│       ├── arena/                      — Side-by-side model battle
│       ├── browse/                     — Model catalog
│       ├── compare/                    — Model comparison
│       ├── dashboard/                  — Per-address dashboard
│       ├── dna/[id]/                   — Neural-DNA fingerprint
│       ├── flows/                      — React Flow tx diagrams
│       ├── live/                       — Live inference feed
│       ├── login/                      — SIWE login
│       ├── mine/                       — Owner earnings + miner
│       ├── model/[id]/                 — Model detail + inference
│       ├── nodes/                      — Registry, register, me
│       ├── profile/                    — Public / me
│       ├── run/                        — Quick-run playground
│       ├── settings/                   — Wallet + limits
│       └── upload/                     — Upload + mint
├── components/
│   ├── Navbar.tsx · HeroSection.tsx · GlobeSection.tsx · EarthGlobe.tsx
│   ├── StatsMarquee.tsx · AdvantagesSection.tsx · InnovationShowcase.tsx
│   ├── IntegrationsSection.tsx · DevelopmentSection.tsx · SecuritySection.tsx
│   ├── SolutionsCarousel.tsx · FinalCtaSection.tsx · Footer.tsx
│   ├── FeatureCard.tsx · FeatureSplitSection.tsx · FloatingCard.tsx
│   ├── CornerBrackets.tsx · IridescentArt.tsx · Logo.tsx
│   ├── GlobeSectionClient.tsx
│   ├── app-shell/                      — Authenticated shell (Sidebar, TopBar, ModelActions, ...)
│   ├── arena/BattleArena.tsx
│   ├── dna/NeuralDNA.tsx
│   ├── flows/nodes.tsx
│   └── mine/                           — Proof-of-inference UI (worker + dashboard)
├── lib/
│   ├── api.ts                          — Orchestrator client (/api/*)
│   ├── contracts.ts                    — viem writes to all 6 contracts
│   ├── wallet.tsx                      — WalletProvider + SIWE cache
│   ├── ponder.ts                       — GraphQL client for Ponder
│   ├── platform.ts                     — Typed /platform/* fetchers
│   ├── providerStore.ts                — Compute provider local state
│   ├── mock.ts · simulate.ts           — Demo-mode helpers
│   └── abis/                           — Exported contract ABIs
├── backend/
│   ├── main.py                         — FastAPI entrypoint (ROLE switch)
│   ├── settings.py                     — Pydantic-settings env matrix
│   ├── requirements.txt · pyproject.toml · Dockerfile · docker-compose.yml
│   ├── .env.example · .env.node1.example · .env.node2.example
│   ├── orchestrator/
│   │   ├── routes_infer.py             — POST /api/infer (rate-limited)
│   │   ├── routes_models.py            — upload / version / download
│   │   ├── routes_platform.py          — /platform/* aggregates
│   │   ├── routes_events.py            — /platform/models/{id}/events
│   │   ├── routes_nodes.py             — /api/nodes/probe
│   │   ├── routes_mining.py            — /api/mine/{challenge,submit,leaderboard,me}
│   │   ├── ponder_client.py            — GraphQL calls to Ponder
│   │   └── deps.py                     — SIWE + rate limit deps
│   ├── node/
│   │   ├── routes_node.py              — /node/{execute,info,health}
│   │   ├── inference.py                — HF pipeline loader
│   │   ├── model_cache.py              — IPFS → HF cache materialisation
│   │   └── tokens.py                   — count_tokens
│   ├── shared/
│   │   ├── chain.py                    — web3.py + bootstrap_chain
│   │   ├── ipfs.py                     — Pinata v3 + gateway fallback
│   │   ├── lit_client.py               — Lit sidecar HTTP client
│   │   ├── schemas.py                  — pydantic request/response models
│   │   └── siwe.py                     — SIWE verification
│   ├── abis/                           — Mirrored ABIs consumed by web3.py
│   └── tests/                          — pytest suite
├── packages/
│   ├── hardhat/                        — Solidity + Hardhat
│   │   ├── contracts/                  — ModelNFT, Marketplace, ComputeRegistry, UserWallet, Reputation, BadgeSBT + interfaces
│   │   ├── deploy/                     — 00_ModelNFT → 04_Wiring (hardhat-deploy)
│   │   ├── hardhat.config.ts
│   │   ├── test/                       — Hardhat tests
│   │   └── deployments/                — hardhat-deploy artefacts
│   ├── ponder/                         — Ponder v7 indexer (src/index.ts)
│   └── shared/abis/                    — Shared ABI export
├── services/
│   └── lit-agent/                      — Lit sidecar (Express + Lit v7)
│       └── src/server.ts               — /encrypt · /decrypt · /health
├── integrations/
│   └── langchain-synapse/               — LangChain LLM wrapper (PyPI: synapse-langchain)
├── scripts/
│   ├── dev-up.ps1 · dev-up.sh          — local orchestration
│   ├── sync-env.ts                     — writes addresses into every .env after deploy
│   ├── fund-nodes.ts                   — funds NODE_*_ADDRESS with Sepolia ETH
│   ├── register-nodes.ts               — calls ComputeRegistry.registerNode
│   ├── seed.ts                         — mints demo models + subscriptions
│   ├── reclaim-stuck-sessions.ts       — calls Marketplace.reclaimExpiredSession
│   └── e2e-one.ts · e2e_real_upload.py — end-to-end smoke tests
├── next.config.mjs · tailwind.config.ts · postcss.config.mjs
├── tsconfig.json
├── package.json                        — root workspace
├── CHANGES.md · HANDOVER.md · HANDOVER_Backend.md
├── HANDOVER_MultiLaptop.md · HANDOVER_ProviderNetwork.md
├── INFRA_INTEGRATION.md · TESTER_PROMPT.md
└── README.md

Getting Started

Prerequisites

  • Node.js ≥ 20 — for Next.js 15 and Hardhat 2.22.
  • Python 3.11+ — pinned package set in backend/requirements.txt (FastAPI 0.115, web3.py 7.5, transformers 4.46, torch 2.6.0+cpu).
  • MetaMask or any EIP-1193 wallet — Sepolia funded.
  • Pinata v3 JWT — or run in dev-mock mode (see below).
  • Alchemy Sepolia RPC — any Sepolia RPC that supports eth_getLogs for reads; writes go through MetaMask.
  • Lit operator key — for services/lit-agent, an EOA on Chronicle Yellowstone that is also registered on ComputeRegistry with an empty task list.

Clone & Install

git clone <repo-url> synapse && cd synapse

# Root (Next.js + workspaces)
npm install

# Backend (Python)
python -m venv .venv
.venv\Scripts\activate                 # PowerShell (Windows)
pip install -r backend/requirements.txt

# Lit sidecar
npm install --workspace @synapse/lit-agent

Environment Setup

Every variable below lives in one of the committed .env.example files or is consumed by source code directly.

Variable Description Example Required
ROLE Backend role switch (orchestrator | node) orchestrator yes (backend)
PORT FastAPI port 8000 no
FRONTEND_URL CORS allow-origin http://localhost:3000 yes
VERSION Advertised in /health 0.5.0 no
ALCHEMY_SEPOLIA_RPC Sepolia RPC for writes + non-log reads https://eth-sepolia.g.alchemy.com/v2/... yes
CHAIN_ID EVM chain id 11155111 no
MODEL_NFT_ADDRESS Deployed ModelNFT 0x61Ef...1132 yes
MARKETPLACE_ADDRESS Deployed Marketplace 0xd34d...9d75 yes
USER_WALLET_ADDRESS Deployed UserWallet 0x6a23...127b yes
COMPUTE_REGISTRY_ADDRESS Deployed ComputeRegistry 0x9D24...470d yes
REPUTATION_ADDRESS Deployed Reputation 0xf602...D52E yes
BADGE_SBT_ADDRESS Deployed BadgeSBT 0xE786...5d93 yes
CONTRACTS_DEPLOY_BLOCK fromBlock for eth_getLogs 10686153 no
EVENTS_RPC_URL Wide-range log RPC https://ethereum-sepolia-rpc.publicnode.com no
PINATA_JWT Pinata v3 bearer eyJhbGciOi... yes (or use dev mock)
IPFS_GATEWAY_URLS CSV fallback list https://gateway.pinata.cloud/ipfs/,https://dweb.link/ipfs/,https://ipfs.io/ipfs/ no
LIT_SIDECAR URL of services/lit-agent http://localhost:7777 yes
LIT_KILL_SWITCH Bypass encryption end-to-end 0 no
HF_HUB_CACHE Local HF cache dir (node) ./.hf-cache no
SIWE_EXPECTED_DOMAIN Must equal window.location.host at signing time localhost:3000 yes
SIWE_MAX_AGE_SECONDS SIWE message age cap 300 no
INFER_RATE_PER_MIN Rate limit on /api/infer 30 no
PONDER_URL Ponder GraphQL http://localhost:42069 no
DEV_MOCK_IPFS Bypass Pinata → disk (.ipfs-mock/) false no
DEV_MOCK_IPFS_DIR Mock dir ./.ipfs-mock no
NODE_PRIVATE_KEY EOA used by compute node to sign settleInference 0xabc… yes (node)
NODE_ENDPOINT HTTPS URL advertised on-chain https://node-1.example.com yes (node)
NODE_TASKS CSV of pipeline tags served text-classification,summarization,text-generation,question-answering no
NODE_KILL_SWITCH Node returns 503 on /node/execute false no
DEPLOYER_PRIVATE_KEY Hardhat deployer (packages/hardhat/.env) 0xdef… yes (deploy)
ETHERSCAN_API_KEY For verify:sepolia ABCD... no
BUYER_PRIVATE_KEY Used by scripts/seed.ts 0x... no (demo)
NODE_1_ADDRESS / NODE_1_PRIVATE_KEY / NODE_1_ENDPOINT Seeded node 1 no (demo)
NODE_2_ADDRESS / NODE_2_PRIVATE_KEY / NODE_2_ENDPOINT Seeded node 2 no (demo)
LIT_OPERATOR_ADDRESS / LIT_OPERATOR_PRIVATE_KEY Operator signer for services/lit-agent yes (lit)
ORCHESTRATOR_URL For seed.ts smoke calls http://localhost:8000 no
NEXT_PUBLIC_ORCHESTRATOR_URL Frontend → backend http://localhost:8000 no
NEXT_PUBLIC_PONDER_URL Frontend → Ponder http://localhost:42069 no
NEXT_PUBLIC_SEPOLIA_RPC Frontend RPC https://eth-sepolia.g.alchemy.com/v2/... no (default hardcoded)
NEXT_PUBLIC_SIWE_DOMAIN Domain used in SIWE message localhost:3000 no
NEXT_PUBLIC_SIWE_URI URI used in SIWE message http://localhost:3000 no
NEXT_PUBLIC_MODEL_NFT_ADDRESS Frontend ModelNFT 0x61Ef… no (default hardcoded)
NEXT_PUBLIC_MARKETPLACE_ADDRESS Frontend Marketplace 0xd34d… no (default hardcoded)
NEXT_PUBLIC_COMPUTE_REGISTRY_ADDRESS Frontend ComputeRegistry 0x9D24… no (default hardcoded)
NEXT_PUBLIC_USER_WALLET_ADDRESS Frontend UserWallet 0x6a23… no (default hardcoded)
NEXT_PUBLIC_REPUTATION_ADDRESS Frontend Reputation 0xf602… no (default hardcoded)
NEXT_PUBLIC_BADGE_SBT_ADDRESS Frontend BadgeSBT 0xE786… no (default hardcoded)

WARNING: The six Sepolia contract addresses are currently hardcoded in lib/contracts.ts at lines 48–59. They must be moved to environment configuration before production deployment.

WARNING: A default Alchemy Sepolia RPC URL is hardcoded in lib/wallet.tsx line 70 and lib/contracts.ts line 76. This key must be rotated and moved out of source before production deployment.

Deploy Contracts

# (packages/hardhat/.env) — fill DEPLOYER_PRIVATE_KEY, ETHERSCAN_API_KEY, SEPOLIA_RPC_URL
npm run chain:deploy:sepolia     # hardhat deploy --network sepolia + sync-env

npm run chain:deploy:sepolia (root package.json) runs hardhat deploy against Sepolia, executes the 5-step deploy in packages/hardhat/deploy/, and then runs scripts/sync-env.ts which writes the new addresses into backend/.env, packages/ponder/.env, and packages/nextjs/.env.local.

To fund and register compute nodes afterwards:

npm run fund-nodes      # scripts/fund-nodes.ts
npm run register-nodes  # scripts/register-nodes.ts
npm run seed            # scripts/seed.ts (mints demo models)

Run Backend

# orchestrator
.venv\Scripts\activate
ROLE=orchestrator PORT=8000 uvicorn backend.main:app --reload --port 8000

# node 1 (separate shell)
uvicorn backend.main:app --port 3001 --env-file backend/.env.node1

# node 2 (separate shell)
uvicorn backend.main:app --port 3002 --env-file backend/.env.node2

# lit sidecar (separate shell)
npm run lit:dev

# ponder (separate shell)
npm run ponder:dev

On Windows there is a one-shot PowerShell harness:

.\scripts\dev-up.ps1

Run Frontend

npm run dev           # next dev -p 3000

Open http://localhost:3000.


API Reference

All orchestrator routes are mounted under /api. Node routes are mounted under /node on the node process.

GET /health

  • Description: Common health for both roles. Returns role + port + version.
  • Auth: not required.
{
  "status": "string — always 'ok'",
  "role": "string — 'orchestrator' | 'node'",
  "port": "number — listening port",
  "version": "string — settings.version"
}

GET /api/health

  • Description: Orchestrator-only health.
  • Auth: not required.
{ "status": "string", "version": "string" }

POST /api/models/upload

  • Description: Multipart upload: pins weights + metadata, optionally encrypts via Lit, returns CIDs plus predictedTokenId that the caller must mint.
  • Auth: not required (caller signs the mint tx themselves).
  • Request (multipart/form-data):
{
  "file": "binary — model weights (<=100MB) or empty when synapse.hf_hub_id is set",
  "metadata_json": "string — JSON blob per SCHEMAS §1",
  "creator": "string — 0x-address minting the NFT",
  "marketplace_address": "string — Marketplace contract",
  "modelnft_address": "string — ModelNFT contract",
  "is_tar_archive": "boolean — whether 'file' is a tar bundle",
  "encrypt": "boolean — default true; respected unless LIT_KILL_SWITCH"
}
  • Response:
{
  "modelCID": "string — IPFS CID of the (encrypted) weights blob",
  "metadataCID": "string — IPFS CID of the metadata JSON",
  "predictedTokenId": "string — totalSupply()+1",
  "encrypted": "boolean",
  "litDataHash": "string | null",
  "litAccessControlConditionsCid": "string | null — CID of the pinned ACL"
}

POST /api/models/{token_id}/version

  • Description: Owner-signed version bump. Frontend still submits ModelNFT.addVersion(...) afterwards.
  • Auth: Authorization: SIWE <base64(message)>.<signature>; SIWE signer must equal ModelNFT.ownerOf(token_id).
  • Request: same shape as /models/upload plus changelog.
  • Response:
{
  "modelCID": "string",
  "metadataCID": "string",
  "tokenId": "string",
  "versionIndexHint": "number — authoritative value comes from VersionAdded event",
  "encrypted": "boolean",
  "litDataHash": "string | null"
}

GET /api/models/{token_id}/download

  • Description: Streams decrypted weights to the authenticated caller. Caller must be the NFT owner or an active subscriber (enforced via Marketplace.hasAccess).
  • Auth: SIWE required.
  • Response: application/octet-stream or application/x-tar chunked stream.

POST /api/infer

  • Description: Validates the on-chain session, forwards the prompt to the assigned node, returns the settled response. Rate-limited per IP by INFER_RATE_PER_MIN.
  • Auth: not required to match the schema; backend cross-checks user against Marketplace.sessions(sessionId).user.
  • Request:
{
  "sessionId": "string — 0x-prefixed 32-byte session id",
  "tokenId": "string | number — must match on-chain session.tokenId",
  "user": "string — 0x-address",
  "prompt": "string",
  "maxTokens": "number — 1..4096, default 128",
  "parameters": "object — pipeline kwargs (optional)"
}
  • Response:
{
  "output": "string",
  "outputStr": "string",
  "inputTokens": "number",
  "outputTokens": "number",
  "latencyMs": "number",
  "settlementTxHash": "string",
  "resultHash": "string",
  "nodeAddress": "string",
  "verified": "boolean | null"
}

GET /api/platform/stats

  • Description: Platform-wide counters. Ponder first; seeded synthetic fallback keeps the UI alive.
  • Response:
{
  "models": "number",
  "onchainModels": "number",
  "inferences": "number",
  "inferencesPerMinute": "number",
  "nodes": "number",
  "onlineNodes": "number",
  "onchainNodes": "number",
  "totalEarnedEth": "number",
  "uptimeSeconds": "number",
  "chainId": "number"
}

GET /api/platform/models

  • Description: Paginated model catalog. Supports pipeline, license, owner, q, sort=popular|newest|rated|cheapest, limit.

GET /api/platform/models/{id}

  • Description: Per-model detail. 3-layer fallback: Ponder → chain-direct ModelNFT.model(id) + Marketplace.pricing(id) + IPFS metadata → seeded catalog.

GET /api/platform/models/{id}/events

  • Description: Live on-chain event timeline from eth_getLogs over ModelNFT + Marketplace, filtered by tokenId. 15 s in-process TTL cache.

GET /api/platform/nodes

  • Description: Merged node list: Ponder + direct ComputeRegistry + seeded synthetic.

GET /api/platform/inferences/recent

  • Description: Most recent settled inferences. Params: limit, modelId, address.

GET /api/platform/profile/{address}

  • Description: Public aggregates for an address. Includes activity heatmap + earned SBT badges.

GET /api/platform/dashboard/{address}

  • Description: Connected-user dashboard: owned models, running subscriptions, open sessions, SBTs, miner credits.

GET /api/platform/node/{address}

  • Description: Per-operator node view: stake, earnings, jobs served.

GET /api/nodes/probe

  • Description: Live reachability probe: walks ComputeRegistry.nodeList, GETs <endpoint>/health with a 3 s deadline, and returns a merged view with cache TTL 20 s.

GET /api/mine/challenge

  • Description: Returns the current mining challenge + difficulty + reward formula. Rotates every ~10 s.

POST /api/mine/submit

  • Description: Submits a share. Request: { address, challenge, nonce, difficulty, hashesInTick }. Server verifies mix32(fnv1a(challenge) ^ nonce) <= (0xffffffff >> difficulty), dedupes by (challenge,nonce,difficulty), credits 2^(difficulty-16) * 10 to the address.

GET /api/mine/leaderboard

  • Description: Top miners by credits.

GET /api/mine/me/{address}

  • Description: Per-address miner stats.

POST /node/execute (node process, not orchestrator)

  • Description: Re-validates the session, ensures the model is materialised locally, runs the HF pipeline, signs settleInference, returns the response.
  • Request:
{
  "sessionId": "string",
  "tokenId": "number | string",
  "prompt": "string",
  "maxTokens": "number",
  "metadataCID": "string",
  "pipelineTag": "string",
  "parameters": "object (optional)"
}
  • Response: see /api/infer response (identical schema; the orchestrator passes it through).

GET /node/info

  • Description: Node self-report: EOA address, advertised endpoint, tasks, currently-loaded models.

GET /node/health

  • Description: 200 if the node is alive. Returns 503 when NODE_KILL_SWITCH=1.

Known Limitations & Future Work

Hackathon-scope shortcuts we will ship

  • Hardcoded Sepolia contract addresses and RPC key in lib/contracts.ts and lib/wallet.tsx — easy unblock for judging, must move to env before mainnet.
  • Seeded catalog in backend/orchestrator/routes_platform.py_build_catalog, _synth_nodes, _PROMPTS, _OUTPUTS, and _activity_heatmap are deterministic pseudo-data used as a fallback for /platform/stats, /platform/models, /platform/nodes, and /platform/inferences/recent when Ponder and the contracts return nothing. The real on-chain paths always win when data exists; the synthetic rows are flagged with "onchain": false.
  • Dev mock IPFS — when PINATA_JWT is missing or DEV_MOCK_IPFS=1, backend/shared/ipfs.py writes pins to ./.ipfs-mock/ with a deterministic fake CID prefix bafkmock…. Lets the stack run without a Pinata account but breaks inter-machine resolution.
  • Lit kill-switchLIT_KILL_SWITCH=1 (set in both backend and services/lit-agent) makes upload and download skip Lit entirely and pass ciphertext through as plaintext base64. Useful when the Lit datil-dev network flakes, unsafe for real users.
  • Mining pool in a JSON filebackend/.state/mining-pool.json is sufficient for a single orchestrator; multi-orchestrator deployment requires Postgres.
  • versionIndexHint in /models/{id}/version — server-side best guess; authoritative value comes from the VersionAdded event. Fixed once the frontend reads the event directly.
  • No slashing on reclaimExpiredSession — the session refunds the user and decrements the node's success counters, but does not slash the stake. Slashing is wired in the contract interface but not yet enforced at the value level.
  • torch==2.6.0+cpu only — GPU acceleration is not configured. Works for small HF models; will be painful on 7B+ ones.
  • Dead-node dispatch — if Marketplace.selectNode picks a node whose uvicorn is down, /api/infer returns HTTP 502 with a clear message and the user's escrow unlocks after SESSION_TIMEOUT via Marketplace.reclaimExpiredSession. No active liveness gating at contract level yet.
  • services/lit-agent 500 MB request body limit — streaming encrypt/decrypt is not wired, capping the on-chain model size to what fits in one Node.js express.json call.
  • packages/nextjs/.env.local path in scripts/sync-env.ts — the sync script still writes to the legacy packages/nextjs/ path; the app at the repo root reads .env.local directly. You may need to copy manually until the script is updated.

Future work

  • Move all hardcoded addresses and RPC URLs out of source.
  • Implement contract-level slashing on session expiry + a proper dispute-resolution window.
  • GPU-enabled node image (torch==2.6.0+cu124) with CUDA-aware model_cache.py.
  • Postgres-backed mining pool and SIWE nonce store so the orchestrator can scale horizontally.
  • Stream-friendly encryption path so services/lit-agent does not need to buffer the whole model in memory.
  • TEE / attestation hooks so InferenceSettled.resultHash can be backed by a remote attestation from the node.
  • Replace the seeded catalog with an indexingFromBlock disclaimer once Ponder is guaranteed to be running alongside the orchestrator.

Security Considerations

  • Re-entrancy. Marketplace.sol and ModelNFT.sol inherit ReentrancyGuardTransient from OpenZeppelin v5 and the payout path uses pull-payment via pendingWithdrawals[...] + withdraw().
  • Fee rate limiting. Marketplace.setFees enforces protocolFeeBps + nodeFeeBps <= 3000 and caps step changes at 100 bps up — prevents a compromised owner from draining listings in one tx.
  • Pull-payment. Every payout (_distribute, expired-session refund, Withdrawable) accrues to pendingWithdrawals and is withdrawn by the beneficiary. No automatic transfer to arbitrary addresses on the hot path.
  • Session ownership. /api/infer and /node/execute re-read Marketplace.sessions(sessionId) on-chain and reject user mismatch (403), tokenId mismatch (400), settled (409), node mismatch (403 at node layer).
  • SIWE. lib/wallet.tsx caches tokens for 240 s (<300 s backend max). backend/orchestrator/deps.py pins the expected domain to SIWE_EXPECTED_DOMAIN — no silent placeholder. Messages are base64'd in the Authorization: SIWE <msg>.<sig> header.
  • CORS. main.py uses explicit allow_origins=[settings.frontend_url] with allow_credentials=True. Never * + credentials (which the browser rejects anyway).
  • Upload limits. routes_models.upload_model caps at 100 MB (Pinata free-tier).
  • Rate limit. INFER_RATE_PER_MIN=30 on /api/infer, enforced per IP by deps.rate_limit_infer.
  • Mining pool. Nonce submissions are server-verified (_verify_share), deduped per (challenge, nonce, difficulty), and GC'd after 2 h. SubmitBody validates difficulty [12..28] and nonce [0..2^32).
  • Lit ACL. Every encrypt bakes the predicted tokenId into the evmContractConditions ACL; the sidecar rejects tokenId === "0" at /encrypt (services/lit-agent/src/server.ts).
  • Kill switches. LIT_KILL_SWITCH (both sides) and NODE_KILL_SWITCH let operators take components out of circulation cleanly.
  • Node kill-switch propagation. /node/execute returns 503 when flipped, so the orchestrator surfaces a 502 and reclaimExpiredSession can refund.
  • Tx signing. Nodes sign settleInference with their own NODE_PRIVATE_KEY under an asyncio _tx_lock to serialize nonces (backend/node/routes_node.py). One signer, one nonce stream.
  • Global error handler. main.py installs a catch-all handler that returns {"detail": "internal server error", "type": "..."} — no raw tracebacks leak to clients.

Contributing

  1. Fork the repo and create a feature branch (feat/<slug>).
  2. Run npm install at the root and pip install -r backend/requirements.txt inside a Python 3.11 venv.
  3. Keep changes scoped. Code style:
    • TypeScript. App Router + server components, strict mode, 2-space indent. Use viem for chain calls — do not introduce ethers.js to the frontend.
    • Solidity. 0.8.26, OpenZeppelin v5 imports only. Every external that mutates state must be protected by nonReentrant or documented as safe. Update the interface in contracts/interfaces/ alongside the implementation.
    • Python. FastAPI + pydantic v2, type hints everywhere, anyio.to_thread.run_sync to wrap web3.py blocking calls. Put env in backend/settings.py; do not scatter os.getenv across routes.
  4. Tests:
    • Contracts: npm run --workspace @synapse/hardhat test (or test:gas).
    • Backend: pytest backend/tests.
    • Frontend: type-check via tsc --noEmit (no e2e harness yet).
  5. For contract changes, re-export ABIs (npm run --workspace @synapse/hardhat export-abis:sepolia) and re-run npm run sync-env. Commit both the contract change and the synced artefacts.
  6. Do not commit .env files. The gitignored paths are backend/.env*, packages/hardhat/.env, packages/ponder/.env, services/lit-agent/.env, and .env.local at the repo root.
  7. Open a PR against main with a short changelog. Reference the contract address in packages/hardhat/deployments/sepolia/ when the change touches on-chain state.

Contributors


License

MIT. See LICENSE.


Acknowledgements

  • OpenZeppelin Contracts v5 — ERC-721, ERC-5192, Ownable, ReentrancyGuardTransient.
  • Lit Protocol v7datil-dev network, encryptString / decryptToString, evmContractConditions custom-contract-call ACL.
  • Ponder — multi-contract event indexer with typed GraphQL.
  • Pinata v3 — IPFS pinning service.
  • HuggingFace transformers — pipeline runtime on every compute node.
  • viem — typed wallet + public clients for the frontend.
  • web3.py 7.5 — backend contract calls.
  • ethereum-sepolia-rpc.publicnode.com — wide-range eth_getLogs that Alchemy's free tier does not serve.

Glossary

  • NFT — Non-Fungible Token, ERC-721 here. Each ModelNFT tokenId is a unique model identity.
  • SBT — Soulbound Token (ERC-5192); a non-transferable NFT used for achievement badges.
  • SIWE — Sign-In With Ethereum (EIP-4361); an ERC-4361 message the user signs in MetaMask so the backend can authenticate them without a password.
  • ACL — Access Control List. In Lit Protocol v7 it is the evmContractConditions object that tells the Lit network which on-chain conditions a caller must satisfy to decrypt.
  • ERC-5192 — "Minimal Soulbound NFTs"; adds locked(tokenId) and a Locked(tokenId) event to mark a token as non-transferable.
  • IPFS CID — Content Identifier; a self-describing multihash that addresses a blob on IPFS.
  • Session — the Marketplace.Session struct opened by purchaseInference and closed by settleInference or reclaimExpiredSession.
  • Pipeline tag — HuggingFace task identifier (e.g. text-generation, summarization). Drives node selection via ComputeRegistry.selectNode.
  • EWMA — Exponentially Weighted Moving Average; used by ComputeRegistry.recordCall to track node latency.
  • Pull payment — pattern where the payee calls withdraw() to claim funds, rather than the contract send()-ing to them, avoiding re-entrancy and the 2300-gas stipend trap.
  • Compute node — a machine running ROLE=node that stakes on-chain, serves HuggingFace pipelines, and signs settleInference from its own key.
  • Kill switch — a boolean env var (LIT_KILL_SWITCH, NODE_KILL_SWITCH) that takes a component out of rotation without stopping the process.

About

Decentralized on-chain AI model marketplace — mint model weights as NFTs, stake to serve inference, settle every call on Ethereum Sepolia. Full-stack monorepo: Solidity + FastAPI + Next.js + Ponder + Lit Protocol.

Topics

Resources

License

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors