Skip to content

Instantly share code, notes, and snippets.

@zmanian
Created February 16, 2026 04:54
Show Gist options
  • Select an option

  • Save zmanian/8efb655e11d5f61feb7b32adb354e16f to your computer and use it in GitHub Desktop.

Select an option

Save zmanian/8efb655e11d5f61feb7b32adb354e16f to your computer and use it in GitHub Desktop.
Decentralized NEAR Intents Settlement on Mosaik

Decentralized NEAR Intents Settlement on Mosaik

Problem

NEAR Intents (formerly Defuse Protocol) coordinates intent-based settlement across 18+ chains. The current architecture relies on a centralized Solver Relay (solver-relay-v2.chaindefuser.com) as the sole coordination point between users and solvers. This is a single point of failure, a censorship vector, and a trust assumption on the relay operator.

Real NEAR Intents Protocol

Token Diff Model

The core primitive is token_diff -- a map of desired balance changes:

{
  "intent": "token_diff",
  "diff": {
    "nep141:usdc.near": "-1000",
    "nep141:wrap.near": "500"
  }
}

Negative values = willing to give. Positive values = want to receive. The Verifier contract matches complementary diffs and settles atomically. Settlement must be zero-sum across all participants.

Supported Intent Types

Type Description
token_diff Primary trading intent (swaps, bridges)
transfer Direct token transfer to another account
ft_withdraw Withdraw NEP-141 fungible tokens from the Verifier
native_withdraw Withdraw native NEAR tokens

RFQ-Based Solver Competition

Solvers compete via Request-for-Quote (not a traditional auction):

  1. All connected solvers receive quote requests simultaneously
  2. Each has a 3-second window to respond with pricing
  3. Best quote is selected and presented to user
  4. Solver signs a counter-intent (their own complementary token_diff)
  5. User intent + solver counter-intent are bundled and submitted to the Verifier
  6. Verifier executes both atomically -- all succeed or all fail

Asset Identifiers

Tokens use defuse-style prefixed identifiers:

  • nep141:usdc.near -- NEP-141 fungible tokens
  • nep171:issuer.near:token_id -- NEP-171 NFTs
  • nep245:contract.near:token_id -- NEP-245 multi-tokens

Current Architecture (Centralized)

User → [Centralized Solver Relay (WebSocket)] → Solvers
                    ↓
           Best quote selected
                    ↓
User signs intent + Solver signs counter-intent
                    ↓
        [intents.near Verifier Contract]
                    ↓
           Atomic settlement

The Solver Relay is a JSON-RPC/WebSocket service that:

  • Receives quote requests from frontends
  • Broadcasts to all connected solvers
  • Collects responses within the 3s window
  • Returns best quote to the frontend
  • Handles intent publication and status tracking

Our Design: Decentralized Settlement on Mosaik

Replace the centralized relay with Mosaik's P2P streams and replicated Raft state machine:

Users (tag: "user")
  │
  ▼ Stream<Intent>
Solvers (tag: "solver")
  │ subscribe_if(|p| p.tags().contains("user"))
  │
  ▼ Stream<SolverQuote>
Auctioneers (tag: "auctioneer") ── 3-node Raft Group
  │ VerifierStateMachine
  │   SubmitIntent → SubmitQuote → ClearRound
  │
  ▼ Stream<Settlement>
Relayers (tag: "relayer")
  │ subscribe_if(|p| p.tags().contains("auctioneer"))
  │
  ▼ On-chain submission

Role Mapping

Role Mosaik Tags Function
Users ["user"] Produce Stream<Intent>
Solvers ["solver"] Consume intents, produce Stream<SolverQuote>
Auctioneers ["auctioneer"] 3-node Raft group running VerifierStateMachine
Relayers ["relayer"] Consume settlements, submit on-chain

VerifierStateMachine (Raft RSM)

struct VerifierStateMachine {
    pending_intents: BTreeMap<u64, Intent>,
    quotes: BTreeMap<u64, Vec<SolverQuote>>,  // intent_id -> quotes
    current_round: u64,
    round_results: Vec<Settlement>,
}

Commands: SubmitIntent(Intent), SubmitQuote(SolverQuote), ClearRound

ClearRound matching logic:

  1. For each intent with quotes, verify token_diff compatibility (opposite signs)
  2. Pick the best quote by amount_out (best price for user)
  3. Verify aggregate flow is zero-sum
  4. Emit Settlement

Selective Solver Subscriptions

Mosaik's subscribe_if predicates enable solvers to selectively subscribe to specific intent producers:

// Solver specializing in NEAR/USDC swaps
let user_tag = Tag::from("user");
let mut consumer = network.streams()
    .consumer::<Intent>()
    .subscribe_if(move |peer| peer.tags().contains(&user_tag))
    .build();

In production, solvers could filter by asset type, chain, volume tier, or user reputation -- something the centralized relay's broadcast-to-all model doesn't support.

Implementation: zmanian/near-intents

Improvements Over the Centralized Relay

Aspect Centralized Solver Relay Mosaik-Based Design
Coordination Single WebSocket server Decentralized P2P streams
Fault tolerance Server down = system down 3-node Raft group survives 1 failure
Censorship Relay operator can filter intents/solvers No central gatekeeper
Trust Trust the relay operator Trust the Raft quorum
Solver discovery Connect to relay WebSocket Mosaik peer discovery with tags
Intent routing Broadcast to all solvers Selective via subscribe_if predicates
Quote aggregation Relay collects and selects VerifierStateMachine matches atomically
Scaling Vertical (bigger server) Horizontal (more auctioneer nodes)
Transparency Opaque server-side selection Replicated state machine, auditable

Mosaik API Patterns Learned

  1. Producer ordering matters -- Create stream producers BEFORE calling discover_all(). If peers sync catalogs before producers exist, the catalog entries won't include stream IDs and consumers won't find producers.

  2. Tag propagation is not instant -- discovery.feed(signed_entry) updates the local catalog only. For other nodes to see tag changes immediately, broadcast the signed entry to each node directly:

    for other_net in &other_networks {
        other_net.discovery().feed(signed_entry.clone());
    }
  3. Group is not Clone -- You can't share a Raft group handle across tasks. In the NEAR Intents demo, solvers produce solutions on a stream rather than calling group.execute() directly. The auctioneer node consumes the solution stream and feeds them into the Raft group.

  4. Use Consistency::Weak on followers -- Consistency::Strong queries forward to the leader but can panic on followers if the forwarding path isn't implemented. Wait for group.when().committed().reaches(target_index) then query with Consistency::Weak.

  5. Create consumers before sending data -- If a settlement is produced before the relayer's consumer is connected, it will be missed. Create consumers and wait for consumer.when().subscribed() before the producing side sends data.

What This Doesn't Change

  • The NEAR Intents Verifier contract (intents.near) remains the on-chain trust anchor
  • Token deposits, withdrawals, and cross-chain bridges are unchanged
  • The signing standards (NEP-413, ERC-191, Raw Ed25519) are unchanged
  • Solvers still compete on price and execution quality

Open Questions

  • Quote window timing: The centralized relay enforces a strict 3s quote window server-side. In the decentralized design, this is approximated via tokio::time::sleep before ClearRound. A more robust approach could use Raft-committed timestamps.
  • Solver reputation/staking: Without a central relay tracking solver behavior, reputation and anti-spam mechanisms need to move on-chain or into the Raft state machine.
  • Cross-chain settlement: The current demo handles the matching/quoting layer. Actual cross-chain settlement still requires Chain Signatures MPC or the PoA Bridge -- those are orthogonal to the relay decentralization.
  • MEV protection: A replicated state machine makes intent ordering transparent. This could be a feature (auditability) or a risk (MEV extraction). Threshold encryption of intents until matching time could address this.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment