# Extending OIF for Cross-Chain Privacy
*Execution models, refund safety, and what privacy pools reveal about cross-chain intent design.*
---
## TL;DR
While building a cross-chain privacy system on top of the [Open Intents Framework](https://openintents.xyz) (OIF), we encountered two gaps where the default assumptions — optimistic settlement and generic refunds — did not hold.
While this post uses OIF terminology, the issues discussed apply equally to ERC-7683-style intent systems.
This post describes:
* **Two gaps** in current intent frameworks exposed by privacy pools: pre-fill verification and intent-aware refunds
* **An execution pattern** that addresses them: asymmetric verification with explicit refund semantics
* **Why standardization matters**: Any protocol that needs to verify intent existence before allowing a fill will need similar patterns
---
## The Problem: Privacy Pools Need Cross-Chain Execution
Privacy protocols enforce compliance rules on deposits — who can enter the pool, under what conditions, and with what attestations. These rules vary by protocol: some use Association Set Providers, some use proof-of-innocence, some use permissioned admission.
Regardless of the compliance model, all of these protocols share a common problem: deploying separate pools per chain fragments participation and weakens the protocol's guarantees.
The solution is a **single canonical pool** that accepts deposits and withdrawals from any chain. This is a cross-chain execution problem, and OIF — with its modular separation of escrow, delivery, and settlement — was a natural fit.
But privacy pools operate under a different threat model than swaps. Building on OIF required extending it in two specific ways.
---
## Gap 1: Deposits Need Pre-Fill Verification
OIF's default settlement model is optimistic: fill first, verify later. This works for swaps because a solver who fills fraudulently only hurts themselves.
For privacy pool deposits, the cost of a fraudulent fill is **compliance bypass**.
### The Attack
Privacy pools enforce compliance rules on deposits. Under optimistic settlement, an attacker who controls both sides of the intent can bypass these rules entirely:
1. Attacker (as user) signs a deposit intent from a "clean" address
2. Attacker (as solver) fills that intent on the pool chain using funds not originating from the claimed depositor
3. The pool attributes the deposit to the "clean" address — compliance checks pass for funds that should have been rejected
The attacker has no reason to complete settlement on the origin chain. There is no solver to slash — the solver *is* the user. The intent was never to complete the trade; it was to impersonate a clean actor and bypass the protocol's admission rules.
Some privacy protocols (e.g., Privacy Pools / 0xbow) allow retroactive rejection of a deposit's compliance label via ASPs, limiting the damage. But for protocols with non-revocable admission (e.g., Veil, Railgun), once the deposit is accepted, the state change is permanent. In either case, preventing the fraudulent fill is strictly better than relying on after-the-fact correction.
### What We Built: The `intentOracle`
We extended the OIF output settler for deposits to require **proof that the intent exists on the origin chain** before accepting a fill.
The following snippets are illustrative and not intended as normative interfaces.
The deposit output settler checks an `intentOracle` and reverts if the intent has not been attested:
```solidity
if (!IInputOracle(intentOracle).isProven(originChainId, oracle, source, intentHash)) {
revert IntentNotProven();
}
```
The `intentOracle` is a cross-chain message relay that attests: "this intent was created by this depositor, with these funds, on the origin chain." The specific transport mechanism (message bridge, storage proof, etc.) is an implementation detail — what matters is that the destination chain can verify intent existence before modifying state.
This enforces that the depositor address actually locked funds on the origin chain. No attestation, no fill.
### Withdrawals Stay Optimistic
This concern is specific to deposits. For withdrawals, the user has already proven their credentials via a ZK proof validated on-chain. The solver's only job is to deliver funds to the recipient on the destination chain.
If the solver doesn't fill, the user doesn't receive funds — but the pool's state is protected by the refund mechanism (described below). If the solver fills correctly, they claim the escrowed amount. There is no incentive for a solver to fill fraudulently, because they front their own capital and only recover it after settlement.
The execution model is therefore **asymmetric by design**: verified deposits, optimistic withdrawals. The verification logic is determined by the threat model of the operation, not applied uniformly.
---
## Gap 2: Refunds Need Intent-Aware Semantics
OIF's default refund path is a simple token return: if the intent expires, send the escrowed funds back to the user. This is sufficient for swaps.
For privacy pools, a refund is not a transfer — it is a **semantic rollback** that depends on the type of intent.
### Why Simple Refunds Don't Work
Consider a cross-chain withdrawal that fails because no solver fills it. The user has already spent their nullifier on the pool chain. Their original commitment is consumed.
A simple refund — sending tokens back to an address — creates two problems:
* **If sent to the user's address**: The user's identity is linked to the nullified commitment, destroying privacy.
* **If sent to the pool contract**: The funds arrive as a plain transfer with no associated commitment. They become permanently inaccessible — locked in the contract with no owner.
Neither option is acceptable. The refund must restore value *inside the pool* as a new commitment that only the original user can spend.
### What We Built: Explicit Refund Calldata
We added a `refundCalldata` field to the intent struct:
```solidity
struct ShinobiIntent {
// ... standard OIF fields ...
bytes refundCalldata; // Protocol-specific rollback logic
}
```
For **deposits**, the refund calldata is empty — the input settler performs a simple ETH return to the depositor. Standard behavior.
For **withdrawals**, the refund calldata encodes a call to the pool's `handleRefund()` function, which inserts a new commitment into the Merkle tree:
```solidity
function handleRefund(uint256 _refundCommitmentHash, uint256 _amount) external payable onlyEntrypoint {
_insert(_refundCommitmentHash);
}
```
When the intent expires without a fill, the input settler executes the appropriate refund path:
```solidity
if (intent.refundCalldata.length == 0) {
// Simple ETH transfer to user
intent.user.call{value: totalAmount}("");
} else {
// Protocol-specific rollback
(address target, bytes memory calldata_) = abi.decode(intent.refundCalldata, (address, bytes));
target.call{value: totalAmount}(calldata_);
}
```
No solver cooperation required. The failure path is a first-class state transition, defined at intent creation time.
---
## How This Fits OIF's Architecture
These extensions work with OIF's modular design, though they require changes across the stack:
* **Intent proof verification** lives in a custom output settler. OIF already supports protocol-specific settlers — we added oracle checks to ours.
* **Refund calldata** lives in the intent struct and is executed by the input settler. We extended the settler's refund logic to dispatch protocol-specific calldata instead of defaulting to a simple transfer.
* **Solvers were extended** to handle the additional intent fields (`intentOracle`, `refundCalldata`) and the two-phase flow required for verified deposits (relay intent proof, then fill).
OIF's settler abstraction is the right place to encode verification requirements, and the intent struct is the right place to encode refund semantics. The architecture accommodated these extensions naturally.
---
## A Case for Standardization
We built these extensions for privacy pools, but the underlying problem is not unique to privacy.
Any cross-chain protocol that needs to verify intent existence on the origin chain before allowing a solver to fill will need a similar pattern. Current OIF and ERC-7683 do not support this.
Today, each such protocol would need to independently extend OIF — adding their own intent verification, designing their own refund logic, building custom settler variants, and modifying solvers to support each protocol's ad-hoc extensions.
If ERC-7683 evolved to include a shared vocabulary for **intent verification** (e.g., an `intentOracle` field) and **typed refund semantics** (e.g., a `refundCalldata` field), these patterns could be reused across protocols. Solvers would have a standard interface for non-optimistic intents. New protocols would not need to reinvent the same infrastructure.
This is not a criticism of OIF — its modular design is precisely what allowed us to build on it. It is a suggestion that the patterns we needed may be generally useful enough to standardize.
---
## Closing Thoughts
OIF gave us the building blocks to make cross-chain privacy work. The extensions we built — asymmetric verification, intent-aware refunds — were driven by a specific use case, but the problems they solve are general.
As intent-based execution becomes shared infrastructure, standardizing these patterns would save the next team from solving the same problems independently.
These patterns are implemented in [Shinobi Cash](https://shinobi.cash), currently on testnet.
---
*This post discusses design considerations encountered while building cross-chain privacy systems. It is not a protocol specification. All referenced implementations are testnet-only and unaudited.*