# \[Idea\] Anoma Protocol Adapter × NEAR Intents

**URL:** <https://forum.anoma.net/t/idea-anoma-protocol-adapter-x-near-intents/2657>\
**Category:** Applications\
**Created:** [April 16, 2026, 7:22am UTC](https://forum.anoma.net/t/idea-anoma-protocol-adapter-x-near-intents/2657 "2026-04-16T07:22:30Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![apriori](https://dub1.discourse-cdn.com/flex013/user_avatar/forum.anoma.net/apriori/32/1655_2.png) [@apriori](https://forum.anoma.net/u/apriori)\
**Post date:** [April 16, 2026, 7:22am UTC](https://forum.anoma.net/t/idea-anoma-protocol-adapter-x-near-intents/2657/1 "2026-04-16T07:22:30Z")

</div>

# Anoma Protocol Adapter × NEAR Intents

> Note: This is an idea that we could prototype, this document does not make any claims about product market fit or user stories justifying demand. Claude Code & Gemini Flash assisted with this document through several iterations.

_tl;dr_

- Product: a cross-chain shielded-value router where NEAR Intents handles the asset conversion and cross-chain routing, and the Anoma Protocol Adapter receives the final leg as a shielded commitment on the destination chain. (_shielded settlement for near intents_).
- Two headline flows: transparent ZEC to shielded ETH (one user intent), and shielded ETH (pool A) to shielded ETH (pool B) across chains.
- Note: privacy is a composite property here. The shielded pool hides identity at the endpoints, the NEAR Intents leg is transparent between them. This is an honest tradeoff, not a regression; the alternative (no cross-chain shielded routing) is worse.

## The problem

A user holds ZEC (transparent, or shielded-in-Zashi with local unshielding available) and wants shielded ETH in an Anoma pool on Ethereum or Base. Or they have shielded ETH in a pool on mainnet and want the equivalent note in a pool on Base. Both require 5 to 6 manual steps today (unshield, bridge, swap, redeposit), each leaking info and requiring transient transparent balances on linked addresses.

“ZEC” below means transparent-pool ZEC. NEAR Intents’ PoA bridge speaks only to Zcash’s transparent pool; Sapling/Orchard is not in-protocol. Zashi’s shield/unshield is a local Zcash-internal step.

> Reader beware, privacy at rest only, not in flight. Zooko Wilcox-O’Hearn’s standing caveat (e.g. [Bankless, Jan 2026](https://www.bankless.com/podcast/zcash-founder-on-privacy-ai-and-how-zec-is-encrypted-bitcoin-zooko)): _“You can’t get privacy from value in flight, you can only get privacy from value at rest.”_ Do not expect end-to-end cross-chain privacy from this design. The NEAR Intents leg is transparent; amounts, timing, and source-to-destination linkage are all observable. What this integration does is preserve the shielded status of the source-side balance inside Zashi’s Zcash shielded pool and of the destination-side balance inside the Anoma shielded pool. The transfer connecting them is public.

Three naive approaches fail:

1. Build cross-chain routing inside Anoma - Controllers not cooked.
2. Conventional bridge plus DEX - 0 privacy, suboptimal routing.
3. Build your own solver network - Competing with incumbents for liquidity.

NEAR Intents already solved the hard parts. In October 2025 ECC integrated NEAR Intents into Zashi, giving one-tap swaps between BTC/SOL/USDC/ETH and transparent ZEC (with optional on-device shielding after arrival), plus CrossPay for the reverse direction. Live and routing hundreds of millions into the Zcash ecosystem per NEAR’s public numbers.

## The solution

Reading `OmniBridge.sol` L346–349 and the reference `BridgeToken.mint` confirms NEAR Intents withdrawals land as plain `IERC20.safeTransfer(recipient, amount)` with no external call. The `bytes message` parameter is discarded by the reference mint. `FtWithdraw.msg` is NEAR-side only. Arbitrary destination calldata is not supported.

A second finding constrains how commitments enter the tree. Reading `ProtocolAdapter.sol`, `CommitmentTree.sol`, and `Types.sol` confirms there is no external `appendCommitment`; the only state-mutating entry is `execute(Transaction)`, and every commitment must come from an Action with verified RISC Zero proofs. Third-party contracts participate only as `IForwarder` whose `forwardCall` output is compared against a pre-committed `expectedOutput`. The Receiver plays that `IForwarder` role.

Given both findings, the architecture binds the deposit to a deterministic per-user address and settles via `execute(Transaction)` against an `IForwarder` Receiver:

1. Client computes `commitment C`, derives CREATE2 `DepositProxy` address `D` from a user-chosen `salt`.
2. Client or relayer submits `registerIntent(salt, commitment, amount, expiry)` on the destination chain, binding `(D → commitment)` before funds move.
3. Client submits the NEAR Intents intent with plain destination address `D`.
4. NEAR Intents settles; plain ERC-20 lands at `D`.
5. Anyone submits a pre-built `execute(Transaction)` that invokes `Receiver.forwardCall`, which verifies `pending[D].commitment == commitment` and sweeps the DepositProxy, and the same Transaction’s compliance unit creates `C`. Front-running is impossible because `(salt → commitment)` was recorded before funds were routable; the `expectedOutput` check binds forwardCall success to the specific commitment.

Shielded-to-shielded runs this primitive forward and back. NEAR Intents provides swap, routing, solver competition, ZEC bridging, and MPC signing. Anoma provides shielded settlement via `execute(Transaction)`. The new contract is a Shielded Deposit Receiver (~200 lines).

## Architecture

```auto
                        ┌─ (1) register(salt, C, amt, expiry) ─┐
                        │ ▼
┌────────────┐ │ ┌─────────────────────────────┐
│USER CLIENT ├──────────┘ │ SHIELDED DEPOSIT RECEIVER │
│(off-chain) │─(2) publish_intent──┐ │ + DepositProxy factory │
│ C, salt, │ │ │ pending[D] = (C, amt, exp) │
│ D=CREATE2 │ │ └──────┬────────────┬─────────┘
└────┬───────┘ │ │ (5) pulls │ (5) appends C
     │ │ │ from D │ to PA tree
     │ ▼ ▼ ▼
     │ ┌────────────────┐ ┌──────┐ ┌─────────────┐
     │ │ NEAR INTENTS │ │ Dep. │ │ ANOMA PA │
     │ │ intents.near │ │Proxy │ │ (commitment │
     │ │ + Omni/PoA │ │ D │ │ tree) │
     │ │ bridges │ └──▲───┘ └─────────────┘
     │ └────────┬───────┘ │
     │ │ (4) plain │
     │ │ safeTransfer│
     │ └─────────────┘
     │
     └─(3) relayer submits intent, bridge routes funds to D ─ ─ ─ ─
                                                                  ▲
                                                                  │
     (anyone can call settle(salt) once D is funded) ─── (6) ─────┘

```

### The four components

1. User client. Generates the commitment locally using the user’s nullifier key: `commitment = Poseidon_many([logic_ref, label_ref, value_ref, nk_commitment, nonce, psi, quantity, is_ephemeral, rcm])`, `nullifier = Poseidon(nk, nonce, psi, commitment)`. Proofs are RISC Zero zkVM (the deployed PA verifies only RISC0). Client packages commitment, encrypted note ciphertext, and intent parameters, and submits to the Solver Relay.

2. NEAR Intents. Live; no changes needed. Verifier at `intents.near` holds NEP-141 balances and settles via `execute_intents`. Withdrawals route through Omni Bridge (ETH, Base, Arbitrum, BNB, Solana, BTC) or PoA bridge (wider list including ZEC, LTC, BCH, DOGE, XRP, Tron, Sui, Aptos, Cardano, Starknet).

3. Shielded Deposit Receiver and DepositProxy. Implements `IForwarder`, invoked by the PA during `execute(Transaction)`:

4. Anoma Protocol Adapter. Reused unmodified. `ProtocolAdapter.sol` maintains a binary Merkle commitment tree (Poseidon leaves) and a nullifier set, and verifies RISC Zero proofs for logic, compliance, delta, and optional aggregation. Deposits require an ARM Transaction whose compliance unit consumes an ephemeral-kind resource and creates the new PositionNote, plus an `externalPayload` targeting the Receiver’s `forwardCall`. The `ephemeral` flag is enforced inside the RISC Zero compliance circuit, not on-chain.

### What the deployed contracts actually allow

Verified by reading `ProtocolAdapter.sol` on Base (`0x094FCC…dd84F8`) and BSC (`0xFC44b6…2B68`): the contract loops over `Action.logicVerifierInputs[].appData.externalPayload[]` and dispatches each blob as a separate `forwardCall`. Multiple forwarder calls per `execute()` tx are supported, and multiple `execute()` txs per block are observed on Base. The Solidity imposes no per-block cap. A “singleton calldata carrier” convention may exist at the RM circuit layer in the naive case, but it is not enforced on-chain.

The deployed `ERC20Forwarder` is pinned to a single `_LOGIC_REF` and supports only Wrap/Unwrap via Permit2. Verified at `ERC20Forwarder.sol:161-172`: Unwrap decodes `UnwrapData { address receiver }` and does `IERC20(token).safeTransfer({to: data.receiver, value: amount})` with no on-chain constraint on `receiver`. Flow 2 Step 2’s `Unwrap` can target any address, including a PoA bridge deposit address; the off-chain resource-logic circuit gates which receivers are legitimate.

## Flow 1: transparent ZEC to shielded ETH

### Client-side setup

User picks `nk`, nonce, randomness, target quantity. Client computes `commitment` per the Poseidon schema above, picks a 256-bit CSPRNG `salt`, derives `D = CREATE2(proxyImpl, salt, receiver)`, and encrypts the note plaintext under the user’s viewing key.

### Step 1: register on destination

Client submits one transaction on the destination chain: `Receiver.registerIntent(salt, commitment, amount, expiry)`. This reserves `D` and binds it to `commitment`. Requires destination-chain gas (one-time UX cost; see open problems).

### Step 2: submit intent

Client calls the Solver Relay (`POST https://solver-relay-v2.chaindefuser.com/rpc`, method `quote`) with:

- `defuse_asset_identifier_in`: ZEC
- `defuse_asset_identifier_out`: WETH on the destination chain
- `exact_amount_in`: X ZEC
- Destination address: `D` (plain address, no calldata)

Signs and submits via `publish_intent`. PoA bridge enforces a 1.0 ZEC minimum plus 0.2 ZEC fee; client enforces a matching minimum.

### Step 3: solver settles

A winning solver executes two legs. Leg A sends the user’s t-addr ZEC to the PoA-bridge Zcash t-addr for their NEAR account, and the Verifier credits the user’s balance. Leg B: `execute_intents` transfers NEP-141 WETH from user to solver internally; solver triggers a Verifier withdrawal that bridges real WETH to `D`, a plain `IERC20.safeTransfer(D, amount)`.

### Step 4: settle on destination

Anyone submits a pre-built `execute(Transaction)` whose compliance unit creates `C` from an ephemeral input, and whose `externalPayload` invokes `Receiver.forwardCall(logicRef, abi.encode(salt, commitment))`. PA verifies proofs, calls `forwardCall`, Receiver checks `pending[D].commitment == commitment`, lazy-deploys the DepositProxy, sweeps WETH from `D` to the PA (minus the tip to `tx.origin`), and returns. If the returned bytes match `expectedOutput`, the commitment is appended in the same `execute()`. The shielded note exists and is spendable. The user submits themselves, or publishes for a relayer to earn the tip.

### Failure modes

Intent expires pre-settle, and ZEC stays in user custody. Bridge leg stalls, and WETH eventually arrives at `D`; `settle` works anytime before `expiry`. `expiry` passes with funds still at `D`, and `refund(salt, to)` returns WETH to a user-specified address; caller must prove knowledge of `salt`.

## Flow 2: shielded ETH (pool A) to shielded ETH (pool B)

User has a shielded ETH note in pool A on chain A (mainnet), wants an equivalent note in pool B on chain B (Base). Same PA architecture on both chains, distinct commitment trees.

### Step 0: pre-register on destination

Client computes `C_B`, `salt_B`, `D_B` for pool B, and calls `Receiver_B.registerIntent(salt_B, C_B, amount, expiry)` on chain B. The binding is now recorded on-chain on the destination side.

### Step 1: pre-sign the intent

Client pre-signs a NEAR Intents intent: “deposit X asset to WETH on chain B, destination = `D_B`”. Held client-side.

### Step 2: consume the source note

An ARM action that consumes the shielded ETH note on pool A (nullifier plus RISC Zero compliance and logic proofs) and produces a calldata-carrier resource targeting chain A’s `ERC20Forwarder` with an `Unwrap` sending WETH to the PoA bridge deposit address. The deployed ProtocolAdapter permits multiple forwarder calls per `execute()`, so this is one tx.

### Step 3: intent settles

Funds land on NEAR. The pre-signed intent becomes executable; a solver bridges WETH to `D_B` as a plain transfer.

### Step 4: settle on chain B

Anyone submits the pre-built chain-B `execute(Transaction)` with `forwardCall(logicRef, abi.encode(salt_B, C_B))` to `Receiver_B`. `C_B` lands in pool B’s tree; tip goes to the submitter.

### Asynchrony budget

A few minutes end-to-end, not atomic. Custody hops (PoA, Verifier, solver, chain-B DepositProxy) are each bounded.

## Proving: client-side or outsourced?

### What needs proving

Every deposit requires an ARM Transaction with RISC Zero proofs; `execute(Transaction)` is the only path into the commitment tree. Flow 1 and Flow 2 Step 4 are minimal Transactions: ephemeral input consumed, PositionNote created, logic + compliance + delta proofs. Flow 2 Step 2 is heavier because it consumes a real shielded note (Merkle inclusion against a stored root, nullifier derivation, plus a spending-predicate logic proof). Verified by reading `anoma/evm-protocol-adapter`: the PA uses RISC Zero exclusively. Seconds to minutes on consumer hardware, faster with GPU.

### The PA verifies only ZKPs

Any outsourcing must still produce a valid RISC Zero proof.

### Bring-your-own-proof

The PA verifies any valid RISC Zero proof regardless of source. The proof is the unit of trust; the prover is interchangeable. The SDK is prover-agnostic:

1. User-generated. Run `anoma/arm-risc0` locally (laptop, home GPU, own server). No service dependency, no trust beyond own hardware. (note we are not referencing MASP).
2. AnomaPay’s existing proving service. Production, on `anoma/arm-risc0`. SDK submits the witness, receives a valid proof. Convenient default for mobile.
3. Bonsai. RISC Zero’s hosted service. Same output format.
4. Any other RISC Zero prover. Friend’s machine, self-hosted enclave, community pool. All outputs interchangeable.

Witness handling is the only trust question: whoever receives `nk` can leak it or front-run spends. Users pick per their own threat model; the SDK ships sensible defaults but never constrains the choice.

### Batching

GPU parallelism across users plus on-chain amortisation via the PA’s set-union composition (multiple users’ actions share one `execute()` tx). Each user still needs their own proof; no SNARK-style recursion collapses many proofs into one on the RISC0 path.

### Minimum client responsibility

`nk` generation and custody, and commitment hashing (cheap). Everything else (RISC0 proving, note-ciphertext assembly, intent submission) is delegable.

### Architecture impact

Every deposit-side settle and every source-side consumption requires a proof. The SDK is prover-agnostic per the section above. Mobile clients default to AnomaPay’s service; desktop users can prove locally.

## Privacy properties

### Hidden

- The identity of the ultimate owner of the new shielded note inside either pool. The commitment `C` is an opaque hash; only the user knows the preimage.
- The link between the user’s source-side identity (Zashi z-addr holding the pre-unshield ZEC) and the destination-side recipient (shielded note in pool B). Zashi’s local shield/unshield carries standard Zcash shielded-pool privacy independent of this integration; we extend that privacy to a shielded ETH endpoint rather than a transparent one.
- Within each pool, all standard shielded-pool properties apply: anonymity set equals the set of notes in the pool; action timing correlation is the main residual leak (same as Zcash).

### Visible

- At the NEAR Intents layer: the intent amount, the source asset, the destination asset, the destination chain, the destination address (the Shielded Deposit Receiver), and the settlement transaction on `intents.near`. The Verifier’s on-chain ledger records the swap.
- On the destination chain: the ETH transfer from the bridge or solver to the Shielded Deposit Receiver, the amount, and the commitment hash. An observer watching the receiver contract sees every deposit commitment and the amount that backs it.
- The link between a specific `depositShielded` call on the destination chain and a specific NEAR Intents withdrawal is trivially observable by correlating the bridge’s output transaction.

### The nuance

Hides _who owns_ the final note but not that _some user_ converted transparent ZEC to shielded ETH of amount X at time T. The privacy win is endpoint identity, not transaction graph. Consistent with the reader-beware note at the top.

### Dark funds between bridge delivery and settle

Funds sit in the undeployed CREATE2 proxy `D` between bridge arrival and `settle(salt)`. Harmless (funds safe, `pending[D]` on-chain), but third-party dashboards won’t attribute the balance until lazy deployment. The client’s portfolio view must read `pending[D]` to surface these.

### Client mitigations

Commitment diversification (split across commitments, costs more fees); timing jitter on `settle(salt)` within the bridge attestation window; RPC hygiene (private-transport or split-RPC).

### Opt-in compliance via viewing keys

User holds their viewing key and can voluntarily export a signed history disclosure; the protocol never holds disclosure-capable keys.

## Open problems and honest limits

1. Calldata in withdrawals, resolved negative. Verified by reading `Near-One/omni-bridge` and `near/intents`. The pre-register plus `IForwarder` settle is the response.
2. PA accepts commitments only via `execute(Transaction)`, resolved. Verified by reading `ProtocolAdapter.sol`, `CommitmentTree.sol`, `Types.sol`: no external `appendCommitment`, `ephemeral` flag enforced off-chain only. Receiver implements `IForwarder`; every deposit is an ARM Transaction. Relayers earn the tip for constructing and submitting them.
3. ERC20Forwarder Unwrap recipient is user-settable, resolved. Verified at `ERC20Forwarder.sol:161-172`. Flow 2 Step 2 can target the PoA bridge deposit address directly.
4. Destination-chain gas before first deposit. Would need to ship EIP-712 meta-tx registration; relayers reimbursed from the `tip` at settle.
5. Nullifier-key entropy. SDK enforces 256-bit CSPRNG for `nk` and `salt`.
6. Mainnet-only testing. `intents.near` has no testnet; real-mainnet costs.

## Sources

- NEAR Intents docs: [docs.near-intents.org](https://docs.near-intents.org), Verifier contract [intro](https://docs.near-intents.org/integration/verifier-contract/introduction), [Solver Relay API](https://docs.near-intents.org/near-intents/market-makers/bus/solver-relay), [PoA bridge](https://docs.near-intents.org/near-intents/poa-bridge), [Token Bridges overview](https://docs.near-intents.org/integration/bridging/overview).
- NEAR Chain Signatures: [docs.near.org chain-abstraction](https://docs.near.org/chain-abstraction/chain-signatures), [mpc repo](https://github.com/near/mpc), [Omni Bridge docs](https://docs.near.org/chain-abstraction/omnibridge/how-it-works).
- NEAR Intents contracts: [github.com/near/intents](https://github.com/near/intents); Defuse sample solver: [github.com/defuse-protocol/near-intents-amm-solver](https://github.com/defuse-protocol/near-intents-amm-solver); TypeScript SDK: [@defuse-protocol/intents-sdk](https://www.npmjs.com/package/@defuse-protocol/intents-sdk).
- ZEC integration with NEAR Intents: [CoinDesk, 9 Oct 2025](https://www.coindesk.com/markets/2025/10/09/near-intents-activity-spikes-as-zcash-s-zashi-wallet-taps-it-for-private-swaps); [NEAR Protocol on X](https://x.com/NEARProtocol/status/1990058852189151465); [Zcash forum Q1 2026 grants call](https://forum.zcashcommunity.com/t/call-for-proposals-coinholder-directed-retroactive-grants-program-q1-2026/54208/8).
- Anoma source code: [`anoma/evm-protocol-adapter`](https://github.com/anoma/evm-protocol-adapter) (`ProtocolAdapter.sol`, `Types.sol`, `IForwarder.sol`), [`anoma/anomapay-erc20-forwarder`](https://github.com/anoma/anomapay-erc20-forwarder) (`ERC20Forwarder.sol`, `ForwarderBase.sol`), Verified deployed addresses on BaseScan and BscScan. Key facts used: `commitment = Poseidon_many([logic_ref, label_ref, value_ref, nk_commitment, nonce, psi, quantity, is_ephemeral, rcm])`; `nullifier = Poseidon(nk, nonce, psi, commitment)`; binary Merkle commitment tree; ProtocolAdapter loops `externalPayload[]` allowing multiple forwarder calls per tx; ERC20Forwarder pinned to a Wrap/Unwrap logic ref.
