Prerequisites
- Node.js v20.18+ with npm
- A NEAR mainnet account
- Liquidity in the Verifier contract (
intents.near) - Basic familiarity with WebSockets and the NEAR account model
How it works
When a user wants to swap tokens, they will send a quote request to the Message Bus — a WebSocket-based relay that broadcasts the request to all connected solvers. The solver evaluates whether the request matches the configured token pair and whether sufficient liquidity is available. If both conditions are met, it calculates a price, signs an intent with the proposed amounts, and sends a quote response back to the Message Bus. Multiple solvers compete for the same request by offering prices. The Message Bus collects the responses and returns top quotes to the user application. If a quote is selected, it is transmitted to the Verifier contract where the swap settles on-chain. For a deeper look at this architecture, see the Market Makers introduction and the Message Bus overview. The following steps show this flow in action.Run the example solver
The AMM Solver example is a Node.js application that implements everything described above. It uses a simple constant-product AMM formula to price quotes — the same model used by Uniswap-style DEXs.Configure your environment
Create your environment file from the provided example:Open The
env/.env.local and fill in these values:MARGIN_PERCENT controls your spread — the difference between what you receive and what you give. A higher value means more profit per trade but fewer quotes accepted.Other settings like RELAY_WS_URL and INTENTS_CONTRACT have sensible defaults and do not need changing for most setups.Get a Message Bus API key:The default Message Bus WebSocket endpoint (wss://solver-relay-v2.chaindefuser.com/ws) requires an API key. To get one:- Sign up at partners.near-intents.org
- Request an API key through the partner portal
src/services/websocket-connection.service.ts and add your API key as a Bearer token in the WebSocket connection headers at line 35:Deposit liquidity
Your solver can only fill swaps if it has token balances inside the Verifier contract (Replace
intents.near). You need to do two things: register your solver’s public key on the contract, and deposit tokens.Register your public key:YOUR_PUBLIC_KEY with the public key that corresponds to the private key in your .env.local file, and SOLVER_ACCOUNT_ID with your actual NEAR account ID.Deposit tokens using one of these methods:- The Passive Deposit/Withdrawal Service — deposit from any supported chain via API
- near-intents.org — a web interface for swapping and depositing tokens
usdt.tether-token.near and wrap.near deposited into the contract.Start the solver
Since the environment file is named On startup, the solver connects to the Message Bus WebSocket, subscribes to quote events, and begins polling the Verifier contract for current token balances every 15 seconds. Log output confirms the connection and initial reserves.
.env.local, set NODE_ENV=local so the app picks it up:Verify it is working
Check the health endpoint to confirm the solver is running:In the logs, look for:
- Connection confirmed — successful WebSocket connection to the Message Bus
- Quote requests — incoming swap requests being evaluated
- Quote responses — signed quotes being sent back for pairs your solver supports
Understanding the code
Now that the solver is running, the sections below describe what happens under the hood. The project is organized into focused services, each handling one part of the workflow.Connecting to the Message Bus
The WebSocket connection service (src/services/websocket-connection.service.ts) manages the link to the Message Bus. On connect, it subscribes to two event types:
Evaluating and responding to quotes
The quoter service (src/services/quoter.service.ts) is where the core decision-making happens. For each incoming request, it:
- Validates the deadline — rejects requests with unreasonable timeframes
- Checks reserves — looks up current balances for both tokens
- Calculates the price — uses a constant-product AMM formula with the configured margin
- Signs the response — creates a
token_diffintent and signs it with the configured NEAR key
x * y = k model:
Keeping state fresh
The solver does not only check reserves once. A cron service (src/services/cron.service.ts) refreshes token balances from the Verifier contract every 15 seconds. After a successful trade, the solver updates its position and adjusts future quotes accordingly.
Making it your own
The example uses a constant-product AMM formula, but any pricing logic can be used. The quoter service is the place to start — replace thegetAmountOut and getAmountIn functions with a custom strategy, whether that is pulling prices from external APIs, using order books, or applying custom spread models.
A few additional areas to customize:
- Support more token pairs — add additional token IDs in the configuration
- Add position limits — cap how much of a token the solver can allocate
- Implement risk controls — set minimum trade sizes, maximum exposure, or rate limits