Skip to main content
This guide walks through the changes needed to upgrade the example AMM Solver from public intents to confidential intents. If you haven’t already, read the public Example Solver guide first to understand the baseline implementation.

Overview

Migrating to confidential intents requires changes in four areas: The core quoting logic and AMM pricing remain unchanged — the same token_diff intent structure works for both public and confidential swaps.

Configuration

Environment variables

Add the confidential intents configuration to your environment file:
PRIVATE_RELAY_WS_URL, PRIVATE_INTENTS_CONTRACT, PRIVATE_INTENTS_CONTRACT_SALT, PRIVATE_TREASURY_ACCOUNT_ID, and ONE_CLICK_BASE_URL are production constants shared by all solver operators. Only SOLVER_INSTANCE_ID and PARTNER_JWT are solver-specific.

Solver mode

Create a mode toggle to switch between public and confidential:

Token identifiers

Converting public to private asset IDs

Confidential assets wrap the public token ID with the treasury account:
The solver uses these wrapped identifiers when:
  • Subscribing to quote requests
  • Checking token pair support
  • Building token_diff intents
For 1Click API balance queries, use the public asset identifiers (without the imt: prefix). The API strips the prefix from response keys. See the Balance queries section for details.

Token configuration

Update the tokens configuration to conditionally use private identifiers:

Nonce generation

Versioned nonces

Confidential intents require nonces that encode the contract salt and deadline. Use VersionedNonceBuilder from the intents SDK:
The salt must be exactly 4 bytes. The production salt e110f317 is a shared constant used by all solvers — do not change it. See the main confidential intents page for more details.

Choosing the nonce strategy

In the quoter service, select the nonce based on the solver mode:
Unlike public intents where the nonce only changes after a trade settles, confidential intents use a fresh nonce for each quote. This removes the throughput limitation of one trade per nonce.

Intent signing

Recipient contract

Confidential intents sign against a different contract ID:
The recipient field in the signed payload uses this active contract:

Intent structure

The token_diff intent structure is identical for both modes — only the asset identifiers and recipient change:

WebSocket connection

Private relay URL

Configure the WebSocket to connect to the private relay with an instance ID for guaranteed delivery:

Authentication

Both relays require a Partner JWT in the connection headers:
Then pass the options when creating the WebSocket:

Guaranteed delivery

Quote status acknowledgements

The private relay uses an acknowledgement mechanism to guarantee delivery of quote status updates. When subscribing to quote_status_extended, pass a third parameter to enable acknowledgements:

Processing with acknowledgements

Each quote_status event includes a sequence number. The solver must acknowledge receipt before processing:
Call this before processing the quote status:
The instance_id in the WebSocket URL enables the relay to track which events have been acknowledged. If the solver disconnects, unacknowledged events are redelivered on reconnect.

Balance queries

1Click API integration

Confidential balances are not visible on the public NEAR chain. Query them through the 1Click API instead.
The 1Click API returns balances with the imt:<minter>: prefix stripped. When querying balances, pass the public asset identifiers (e.g., nep141:wrap.near), not the IMT-wrapped versions. The response will use these same public identifiers as keys.

Authentication

The 1Click API requires an authenticated user token. Generate one by signing an intent:

Unified balance method

Switch between on-chain and 1Click queries based on mode. Note that for confidential mode, you need to convert IMT-wrapped token IDs back to public format for the API call:

Solver response format

When responding to confidential quote requests, solvers must return their signed intents in the private_signed_data bundle. This structure contains up to three signed intents:
SignedData is the same signed-payload shape used everywhere else in the protocol (see publish_intent):
For the full rules on when each of shield/swap/recover is required (not just their types), see Confidential Intents → Solver response format.
Each of shield, swap, and recover is signed the same way, by the solver’s own NEAR key, using the same near-api-js signing flow the Example Solver uses for public token_diff intents (build the message, sign it, bs58-encode the result into public_key/signature). The only thing that changes per intent is the message content, swap signs a token_diff, shield/recover sign whatever transfer/burn intent your Treasury integration expects. Here’s what a fully populated private_signed_data bundle looks like on the wire, shielding public liquidity before the swap:
  • shield signs a transfer intent on the public contract (recipient: "intents.near"), moving your liquidity into the Treasury account. memo carries the encrypted recipient the Private PoA Bridge needs to mint the matching IMT on FAR.
  • swap signs against the private contract (recipient: "intents.far") and bundles two intents together: the token_diff (the actual swap) plus an imt_burn that unshields the received funds immediately instead of leaving them shielded. Its nonce must exactly match recover’s.
  • recover also signs against intents.far, a single imt_burn for the amount that was shielded, using the same nonce as swap so only one of the two can ever execute.

Building the response

When quoting, construct the private_signed_data bundle with your signed swap intent:
If you need to shield public liquidity for the swap, include all three intents:
The recover intent must share the same nonce as the swap intent. This ensures only one of them can execute — either the swap succeeds, or the recover burns the shielded tokens back to public.

Depositing liquidity

Before quoting confidential swaps, deposit liquidity from public Intents into your confidential balance.

Using the deposit script

The example solver includes a script for shielding liquidity:
Run this for each token in your pair:
Amounts are in the token’s smallest unit. For 24-decimal tokens like wNEAR, 100000000000000000000000 equals 0.1 wNEAR.

Withdrawing liquidity

To move liquidity back to public Intents:

Running the solver

With liquidity deposited, start the solver in confidential mode:
The solver will:
  1. Connect to the private relay with your instance ID
  2. Subscribe to confidential quote requests
  3. Query balances from the 1Click API
  4. Respond with signed quotes using versioned nonces
  5. Acknowledge quote status events for guaranteed delivery

Quick reference


Next steps

Confidential Overview

Learn more about the confidential intents architecture

Guaranteed Delivery

Deep dive into the acknowledgement protocol