Architecture
The project is organized into focused services, each handling one part of the workflow:
The WebSocket service receives a quote request, hands it to the quoter, and the quoter responds with a signed intent. Meanwhile, the cron service keeps balance data current so the quoter always knows what it can fill.
Message Bus connection
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:
Processing requests
When aquote event arrives, the WebSocket service checks whether the requested token pair matches the solver’s configured pair. If it does, the request is passed to the quoter service for evaluation.
Each quote request contains the following parameters:
src/services/quoter.service.ts) handles the core decision-making. 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
Building and signing intents
Once the quoter has computed a price, it constructs atoken_diff intent — a signed statement declaring which tokens the solver is willing to give and receive.
Token diff structure
Atoken_diff intent expresses net token changes from the solver’s perspective:
Assembly and signing
The solver constructs the intent message directly and signs it usingnear-api-js. The message includes the solver’s account ID, a deadline, and the token_diff intent:
Generating a nonce
Generating a nonce
Every intent requires a unique nonce. The example solver uses a deterministic approach — it hashes the current token reserves with SHA-256, so the nonce only changes when the solver’s balances change (i.e. after a trade settles):This avoids fetching the contract salt on every request. The nonce is recomputed whenever the cron service refreshes balances.
Learn more about the nonce structure in the Intent Types and Execution docs.
Quote response
After building and signing the intent, the solver sends aquote_response back through the WebSocket. The response includes the quote output (the calculated amount) and the full signed data:
sendRequestToRelay wraps the response in a JSON-RPC message:
quote_output field tells the Message Bus which side of the trade the solver is quoting. If the request specified exact_amount_in, the solver responds with amount_out (how much it will give). If the request specified exact_amount_out, it responds with amount_in (how much it wants to receive).
Monitoring
Settlements
The solver subscribes toquote_status events to learn when its quotes are selected and settled on-chain. The WebSocket service routes incoming messages based on the subscription type:
quote_status event arrives, the solver checks whether the settled quote hash matches one of its own cached quotes. If it does, it triggers a balance refresh so that future quotes reflect the updated reserves:
Balances
A cron service (src/services/cron.service.ts) refreshes token balances from the Verifier contract every 15 seconds. It calls the mt_batch_balance_of method on the intents contract to get the solver’s current reserves. After a successful trade, the settlement handler also triggers an immediate refresh. This ensures the quoter always has accurate reserve data when calculating prices.
Customization
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