NEAR Intents: Confidential Intents
Solve for private liquidity on the confidential relay
Confidential Intents extend the NEAR Intents protocol with privacy-preserving swaps. Market makers can provide liquidity on a private chain where balances and swap details are shielded from public view.
How it works
Confidential Intents use a two-chain architecture:
- NEAR (Public) — The public blockchain.
intents.nearis the smart-contract ledger that credits deposited balances. - FAR (Private) — A private NEAR fork where the
intents.farcontract executes swaps with shielded balances
FAR is a permissioned instance of the NEAR protocol operated by a small validator set with no public networking. Balances and swap details on FAR are not visible to the public — the RPC is private and reachable only through authenticated channels.
Shield (public → private): Tokens are transferred to the Treasury account on NEAR. The Private PoA Bridge processes this deposit and mints equivalent IMT tokens on FAR.
Unshield (private → public): IMT tokens are burned on FAR. The PoA Bridge processes the burn request and releases the backing tokens from the Treasury on NEAR.
[!NOTE]
The bridge operates on a request-driven model — it processes deposits and burns via RPC requests rather than actively detecting on-chain events.
Market makers quote against their private IMT liquidity on FAR. The swap settles entirely on FAR via a token_diff intent, invisible to public indexers.
Asset identifiers
Confidential assets are represented as IMT (Intents Multi-Token) tokens on FAR. Each IMT is backed 1:1 by the corresponding tokens locked in the Treasury smart-contract account on the public chain.
The token ID format prefixes the public asset ID with a system account:
imt:<ACCOUNT_ID>:nep141:<token>
For example, wrapped NEAR on the confidential chain:
imt:51e8f94d77b5e90dc9852ca6113771e11e8382ce69473a75e313e41665de7cbe:nep141:wrap.near
This account ID (PRIVATE_TREASURY_ACCOUNT_ID in solver configs) is a production constant shared by all participants.
Private Relay
Confidential Intents use a separate Private Relay (PRIVATE_RELAY_WS_URL) instead of the public Message Bus. The private relay:
- Broadcasts quote requests for confidential swaps (asset IDs are IMT-wrapped)
- Collects signed quotes from connected solvers
- Uses
QUOTE_STATUS_EXTENDEDevents (vsQUOTE_STATUSon the public relay) - Verifies signatures against the public
intents.nearcontract, but reads balances from FAR
The private relay does not settle intents directly — settlement is handled by the Private PoA Bridge, which mints/burns IMT tokens across the public⇄private boundary.
[!NOTE]
The private relay uses the same Partner JWT authentication as the public relay. Contact the Defuse team for access to the private relay URL and credentials.
Versioned nonces
Confidential intents require a versioned nonce format that encodes the contract salt and deadline. This differs from public intents where solvers typically use deterministic nonces based on reserves.
The nonce is constructed using VersionedNonceBuilder from the @defuse-protocol/intents-sdk:
import { VersionedNonceBuilder } from '@defuse-protocol/intents-sdk';
const salt = Buffer.from('e110f317', 'hex'); // 4-byte contract salt
const deadline = new Date(Date.now() + quoteDeadlineMs);
const nonce = VersionedNonceBuilder.encodeNonce(salt, deadline);
[!WARNING]
The contract salt (
PRIVATE_INTENTS_CONTRACT_SALT) is a production constant. Using the wrong salt will cause intent verification to fail.
Solver response format
When quoting confidential swaps, solvers return signed intents in the private_signed_data field:
| Intent | Required | Purpose |
|---|---|---|
shield |
No | Transfer of solver's public liquidity to the Treasury (triggers IMT mint on FAR) |
swap |
Yes | Must contain exactly one token_diff. May optionally include an imt_burn if the solver wants output released immediately rather than staying shielded |
recover |
If shield present |
Fallback imt_burn if the swap fails (shares the swap's nonce — only one can land) |
[!WARNING]
A quote with a
shieldintent but norecoverintent will be rejected. The recover intent prevents solver liquidity from being stranded if settlement fails.
Solvers should only act on the quote_settle_successful event before releasing funds.
Unshield notifications
When a solver's funds are unshielded (private → public), the relay sends an UNSHIELDED status notification via the dedicated shield_status stream. Subscribe to this stream separately from quote_status to receive these notifications:
// Subscribe to shield status updates
await this.sendRequest('subscribe', ['shield_status', null, true]);
The shield_status event payload contains:
quote_hash: The quote identifierstatus:"unshielded"when funds have been released to public
{
"jsonrpc": "2.0",
"method": "subscribe",
"params": {
"quote_hash": "00000000000000000000000000000000",
"status": "unshielded"
}
}
[!NOTE]
shield_statussupports the same Guaranteed Delivery acknowledgement/redelivery mechanics asquote_status, opt in the same way by passing the guaranteed-delivery flag onsubscribe.
If your solver needs to check its own confidential balance rather than just watch for shield events, that's a separate flow: it queries GET /v0/account/balances on the 1Click API, which requires a User-Session token. See Authenticating end users for how to get one, and Confidential Example → Authentication for it wired into a solver.
Shielding and unshielding liquidity
Before quoting confidential swaps, market makers must shield (deposit) liquidity from the public NEAR chain into their private balance on FAR.
Shield (deposit into confidential)
Transfer tokens from your public Intents balance to your confidential balance:
depositType:INTENTSrecipientType:CONFIDENTIAL_INTENTSrefundType:INTENTS
{
"dry": false,
"swapType": "EXACT_INPUT",
"originAsset": "nep141:wrap.near",
"destinationAsset": "nep141:wrap.near",
"amount": "1000000000000000000000000",
"depositType": "INTENTS",
"recipient": "your-solver.near",
"recipientType": "CONFIDENTIAL_INTENTS",
"refundTo": "your-solver.near",
"refundType": "INTENTS"
}
Unshield (withdraw to public)
Transfer tokens from your confidential balance back to public Intents:
depositType:CONFIDENTIAL_INTENTSrecipientType:INTENTSrefundType:CONFIDENTIAL_INTENTS
{
"dry": false,
"swapType": "EXACT_INPUT",
"originAsset": "nep141:wrap.near",
"destinationAsset": "nep141:wrap.near",
"amount": "1000000000000000000000000",
"depositType": "CONFIDENTIAL_INTENTS",
"recipient": "your-solver.near",
"recipientType": "INTENTS",
"refundTo": "your-solver.near",
"refundType": "CONFIDENTIAL_INTENTS"
}
[!TIP]
Use the same asset for both
originAssetanddestinationAssetwhen shielding or unshielding. These are liquidity movements, not swaps, soamountis just the amount you're moving, not something you're pricing.
The example solver repository includes runnable scripts for depositing and withdrawing confidential liquidity via the 1Click API.
Next steps
Detailed walkthrough of solving for confidential intents
Compare with the public intents implementation