# VeilSwap: Privacy-Preserving Distributed Liquidity on Uniswap v4
###### tags: `DeFi` `Uniswap v4` `MPC` `Privacy` `HackMoney 2026`
:::info
π [**HackMoney 2026**](https://ethglobal.com/showcase/veilswap-2y9sd) β Uniswap v4 Privacy DeFi, 2nd Place
:::
## The Why
Here's an uncomfortable truth about decentralized exchanges: they're centralised where it matters most.
When you provide liquidity to Uniswap, Curve, or Balancer, your funds land in a single smart contract β a pool. That pool holds every LP's tokens in one place, governed by one set of logic, exposed through one attack surface. It's a honeypot by design.
In 2025 alone, DEX exploits drained over **$3.1 billion** from the ecosystem:
| Protocol | Loss | What Happened | Post-Mortem |
|:---------|:-----|:--------------|:------------|
| **Cetus** (Sui) | $223M | An overflow vulnerability in liquidity calculations let an attacker drain the pool in *15 minutes*. Spoofed tokens manipulated AMM pricing, and downstream integrations treated the garbage as valid. | [Cyfrin](https://www.cyfrin.io/blog/inside-the-223m-cetus-exploit-root-cause-and-impact-analysis), [SlowMist](https://medium.com/coinmonks/cetus-hack-post-mortem-of-a-223m-heist-acd851f2e5b9) |
| **Balancer v2** | $129M | A rounding-error bug in composable stable pools β code that had been live for *five years* β was exploited across multiple chains in a single coordinated attack. | [Check Point Research](https://research.checkpoint.com/2025/how-an-attacker-drained-128m-from-balancer-through-rounding-error-exploitation/), [SlowMist](https://slowmist.medium.com/when-small-flaws-collapse-a-giant-inside-balancers-100m-hack-85b9e92a9ae3), [Certora](https://www.certora.com/blog/breaking-down-the-balancer-hack) |
| **GMX v1** (Arbitrum) | $42M | A reentrancy-style vulnerability in the GLP liquidity pool let an attacker loop withdrawals, draining LP funds while everything looked normal on the surface. | [Sherlock](https://sherlock.xyz/post/gmx-exchange-hack-explained), [CertiK](https://www.certik.com/resources/blog/gmx-incident-analysis), [Halborn](https://www.halborn.com/blog/post/explained-the-gmx-hack-july-2025) |
| **Cork Protocol** | $12M | Developers modified Uniswap v4's default `beforeSwap` hook permissions. Inadequate access checks allowed malicious data injection. | [Cork Official](https://www.cork.tech/blog/post-mortem), [Dedaub](https://dedaub.com/blog/the-11m-cork-protocol-hack-a-critical-lesson-in-uniswap-v4-hook-security/), [Rekt](https://rekt.news/cork-protocol-rekt) |
A pattern emerges: **one pool, one vulnerability, total loss**.
The 2nd vector here is that even when nobody is actively exploiting the pool, the architecture leaks information. Every LP position is onchain, and can see how much liquidity is being provisioned, the prices ranges, additions and removals.
This is a buffet for MEV searchers. Flashbots estimated that MEV extraction on Ethereum AMMs cost users over **$1.2 billion in 2024** β more than all DeFi hacks *combined* that year. Sandwich attacks alone hit an average of 50,000 victims per day.
Hence, we have two compounding problems:
:::warning
1. **Concentration risk** ~ All funds in one contract = one exploit drains everything
2. **Information leakage** ~ All positions visible onchain = systematic value extraction by MEV and front-runners
:::
This is the problem we set out to research and offer a solution for, with VeilSwap.
### The Building Blocks
Before we walk through the full protocol, let's lay out the primitives. VeilSwap combines three well-understood building blocks in a way that β as far as we know β hasn't been done before.
### 1. Uniswap v4 Hooks
[Uniswap v4](https://docs.uniswap.org/contracts/v4/concepts/hooks) introduced **hooks**: custom logic that executes at specific points in the swap lifecycle. The one that matters for us is `beforeSwap`.
```
User calls swap() β Pool calls beforeSwap() on Hook β Hook can modify or SKIP the swap
```
The critical capability used is a hook can return `SKIP_SWAP`, intercepting the swap entirely and routing execution elsewhere. The pool contract treats it as handled - the user gets their swap, just not through the standard AMM path.
This is our entry point. VeilSwap's hook intercepts every swap, creates an **intent**, and hands it off to an offchain coordination layer.
### 2. Multi-Party Computation (MPC)
MPC lets multiple parties jointly compute a function over their private inputs without revealing those inputs to each other.
The classic example: three employees want to know their average salary without telling each other what they earn. MPC makes this possible - everyone learns the average, nobody learns any individual salary.
For VeilSwap, the *function* is:
> *Given that Server A has `x` USDC, Server B has `y` USDC, and Server C has `z` USDC - can they fill a 1000 USDC order, and if so, how should it be split?*
The result is revealed, the individual values of `x`, `y`, and `z` never are.
We use three specific MPC techniques:
```
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MPC Toolbox β
ββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ€
β Shamir's Secret β Split a secret into shares such that β
β Sharing β any t-of-n shares can reconstruct it, β
β β but t-1 shares reveal nothing. β
ββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββ€
β Beaver Triples β Pre-computed random triples (a,b,c) β
β β where c = aΒ·b. Enable multiplication β
β β on secret-shared values without β
β β revealing operands. β
ββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββ€
β Arithmetic β Compose addition, multiplication, β
β Circuits β comparison, and division gates over β
β β secret-shared values to compute β
β β arbitrary functions. β
ββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββ
```
### 3. Atomic Onchain Settlement
After MPC determines allocations, we need settlement to be **atomic** β either all servers deliver their portion or none do. No partial fills that leave the user hanging.
Each server signs their allocation commitment. The settlement contract verifies all signatures, confirms allocations sum to the order size, and executes all transfers in a single transaction.
---
## The How
Now let's trace a real swap through the entire protocol. A user wants to swap **1000 USDC β ETH**.
### Phase 1: Intent Creation (OnChain)
```mermaid
sequenceDiagram
participant U as π€ User
participant V4 as π· Uniswap v4
participant H as πͺ VeilSwap Hook
U->>V4: swap(1000 USDC β ETH)
V4->>H: beforeSwap(poolKey, params)
Note over H: Create intent_idStore order detailsEmit IntentCreated event
H-->>V4: return SKIP_SWAP
Note over V4: Swap intercepted βno AMM execution
```
The `SKIP_SWAP` return is the key trick. From Uniswap's perspective, the swap is "handled."
The `IntentCreated` event broadcasts the order details (token pair, amount, deadline) to the MPC network. It does **not** reveal anything about LP positions - those are about to be computed privately.
### Phase 2β3: Secret Sharing & Distribution
Three MPC servers (3 for the purpose of this example) detect the intent. Each has private liquidity they're willing to commit:
| Server | Available USDC | Visible To |
|:-------|:---------------|:-----------|
| Server A | 300 | Only A |
| Server B | 500 | Only B |
| Server C | 400 | Only C |
Each server splits their capacity into **Shamir secret shares** and distributes them:
```mermaid
sequenceDiagram
participant A as π’ Server A(300 USDC)
participant B as π΅ Server B(500 USDC)
participant C as π Server C(400 USDC)
Note over A: Generate shares of 300: (s_A1, s_A2, s_A3)
Note over B: Generate shares of 500: (s_B1, s_B2, s_B3)
Note over C: Generate shares of 400: (s_C1, s_C2, s_C3)
A->>B: encrypted(s_A2)
A->>C: encrypted(s_A3)
B->>A: encrypted(s_B1)
B->>C: encrypted(s_B3)
C->>A: encrypted(s_C1)
C->>B: encrypted(s_C2)
Note over A: Holds: s_A1, s_B1, s_C1
Note over B: Holds: s_A2, s_B2, s_C2
Note over C: Holds: s_A3, s_B3, s_C3
```
After distribution, each server holds one share from every party. No server can reconstruct any other server's capacity from a single share β that's the Shamir guarantee.
### Phase 4β6: The Three MPC Rounds
This is where the cryptographic magic happens. Three computation rounds, each building on the last:
```mermaid
flowchart TB
subgraph R1["π’ Round 1 β Capacity Summation"]
direction LR
A1["Server A computeslocal_sum_A = s_A1 + s_B1 + s_C1"]
B1["Server B computeslocal_sum_B = s_A2 + s_B2 + s_C2"]
C1["Server C computeslocal_sum_C = s_A3 + s_B3 + s_C3"]
RES1["local_sum_A + local_sum_B + local_sum_C = 1200β οΈ No single server knows this value"]
end
subgraph R2["βοΈ Round 2 β Secure Comparison"]
direction LR
BT["Generate beaver tripleshares for comparison circuit"]
MASK["Exchange masked valuesacross multiple rounds"]
CMP["Each server computes shareof comparison result"]
RESULT["Reconstruct: 1200 β₯ 1000? β β TRUE"]
end
subgraph R3["π Round 3 β Allocation Calculation"]
direction LR
DIV["Division circuit: computeeach server's proportion"]
MUL["Multiplication circuit:proportion Γ order_size"]
ALLOC["Each server holds sharesof ALL allocations"]
end
R1 --> R2 --> R3
style R1 fill:#1a1a2e,stroke:#e94560,color:#eee
style R2 fill:#1a1a2e,stroke:#f5a623,color:#eee
style R3 fill:#1a1a2e,stroke:#00b894,color:#eee
```
**Round 1** is cheap β addition on secret-shared values is free (you just add the shares locally). Nobody communicates.
**Round 2** is where it gets interesting. We need to check if total capacity β₯ order size, but *without reconstructing the total*. This is where **beaver triples** earn their keep. The comparison circuit uses pre-computed random triples `(a, b, c)` where `c = aΒ·b` to evaluate the comparison gate by exchanging only masked (randomized) intermediate values. Each server learns their share of the boolean result. When the shares are reconstructed, the answer is simply `TRUE` or `FALSE` β the actual total (1200) is never revealed to anyone.
> If the comparison returns `FALSE`, the protocol broadcasts intent failure. The user's swap is cancelled gracefully. No funds were ever at risk because nothing was committed yet.
**Round 3** computes proportional allocations: each server's share of the order, proportional to their capacity. This requires both division and multiplication over secret-shared values β again using beaver triples for the multiplication gates. At the end, each server holds shares of *every* allocation.
### Phase 7: Selective Revelation
This is the privacy punchline.
Each server reconstructs **only their own** allocation by combining the relevant shares:
```
Server A learns: 250 USDC (their allocation)
Server B learns: 417 USDC (their allocation)
Server C learns: 333 USDC (their allocation)
βββββββββ
Total: 1000 USDC β
```
:::success
**The privacy invariant:** Server A knows it must provide 250 USDC. It does *not* know that Server B is providing 417 or Server C is providing 333. It doesn't know the total network capacity was 1200. It only knows the network had *enough* β because the comparison returned TRUE.
:::
### Phase 8β11: Settlement (Back Onchain)
```mermaid
sequenceDiagram
participant A as π’ Server A
participant B as π΅ Server B
participant C as π Server C
participant SC as π SettlementContract
participant H as πͺ VeilSwapHook
Note over A,C: Each server locks allocationand generates ZK proof
A->>A: Sign(intent_id, 250 USDC)
B->>B: Sign(intent_id, 417 USDC)
C->>C: Sign(intent_id, 333 USDC)
Note over A,C: Gossip signaturesvia P2P network
A->>SC: Submit all 3 signatures
Note over SC: Verify signatures βVerify 250 + 417 + 333 = 1000 βExecute atomic settlement
SC->>SC: Pull 250 USDC from APull 417 USDC from BPull 333 USDC from C
SC->>SC: Swap 1000 USDC β ETH
SC->>SC: Distribute ETH proportionally
SC->>H: IntentFilled(intent_id)
Note over A,C: Each server updateslocal balance privately
```
Settlement is atomic β the contract either executes all three pulls and the swap, or reverts entirely. Nonce-based duplicate protection ensures that even if multiple servers submit in parallel, only the first transaction succeeds.
---
### What Each Party Knows
This is worth stating precisely:
```
βββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββ
β β Knowledge After Settlement β
βββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββ€
β Server A β β Own capacity (300) β
β β β Own allocation (250) β
β β β Total capacity β₯ order size β
β β β Other servers' capacities β
β β β Other servers' allocations β
β β β Total network capacity (1200) β
β β β Other servers' remaining balances β
βββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββ€
β User β β Swap executed (1000 USDC β ETH) β
β β β How liquidity was sourced β
β β β Number of servers involved β
β β β Any server's capacity or allocation β
βββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββ€
β Onchain observer β β A swap of 1000 USDC β ETH occurred β
β β β Settlement contract was called β
β β β Individual LP contributions β
β β β LP capacity or positions β
βββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββ
```
Compare this to a standard AMM where **all** of this information is public. Every LP position, every rebalance, every tick - fully visible to MEV searchers, competitors, and attackers.
---
### When Things Go Wrong
A protocol isn't serious until it has failure modes. Here's how VeilSwap handles them:
```mermaid
flowchart TD
INTENT["Intent Created:1000 USDC β ETH"] --> MPC["MPC Computation"]
MPC -->|"Capacity β₯ Order"| SUCCESS["β Proceed to Settlement"]
MPC -->|"Capacity < Order"| FAIL["β Comparison returns FALSE"]
FAIL --> BROADCAST["Broadcast intent failure"]
BROADCAST --> CANCEL["Hook cancels swap"]
SUCCESS --> SETTLE["Settlement Phase"]
SETTLE -->|"Server B goes offline"| FAILURE_MODE["β οΈ Settlement Failure"]
FAILURE_MODE --> OPT1["Option 1:Reduce order sizeRerun MPC with A + C"]
FAILURE_MODE --> OPT2["Option 2:Recruit Server Das replacement"]
FAILURE_MODE --> OPT3["Option 3:Abort intentUser notified"]
style INTENT fill:#16213e,stroke:#0f3460,color:#e94560
style SUCCESS fill:#1a472a,stroke:#2d6a4f,color:#fff
style FAIL fill:#461220,stroke:#8b2635,color:#fff
style FAILURE_MODE fill:#3d2c2c,stroke:#c44536,color:#fff
```
The important thing: **no funds are at risk during failure**. The MPC phase is purely computational β nothing is committed until the settlement transaction executes onchain. If any server vanishes between allocation and settlement, the worst case is a cancelled swap and a retry.
---
## The Conclusion
Putting it all together, here's the full system as a layered view:
```mermaid
flowchart TB
subgraph ON_CHAIN["βοΈ Onchain Layer"]
direction LR
USER["π€ User"] -->|"swap()"| V4["π· Uniswap v4 Pool"]
V4 -->|"beforeSwap()"| HOOK["πͺ VeilSwap Hook\n(Intent Registry)"]
HOOK -->|"SKIP_SWAP"| V4
SETTLE["π Settlement Contract"] -->|"IntentFilled"| HOOK
end
subgraph OFF_CHAIN["π Offchain MPC Layer"]
direction LR
SA["π’ Server A"] <--->|"encrypted shares"| SB["π΅ Server B"]
SB <--->|"encrypted shares"| SC["π Server C"]
SC <--->|"encrypted shares"| SA
end
HOOK -->|"IntentCreated event"| OFF_CHAIN
OFF_CHAIN -->|"Signed allocations"| SETTLE
style ON_CHAIN fill:#0d1b2a,stroke:#1b263b,color:#e0e1dd
style OFF_CHAIN fill:#1b1b2f,stroke:#2e2e4d,color:#e0e1dd
```
Two layers, clean separation of concerns:
- **Onchain**: intent creation, signature verification, atomic settlement. Minimal trust assumptions β it's all verifiable.
- **Offchain**: privacy-preserving computation. The MPC servers never need to be trusted individually β the protocol's security holds as long as a threshold of servers are honest.
---
*This article is part of our [HackMoney 2026 project showcase](https://ethglobal.com/showcase/veilswap-2y9sd). The codebase, deployment details, and demo are available [here](https://github.com/VanshSahay/VeilSwap). The team comprised of [me](https://github.com/ultraviolet10), and [Vansh Sahay](https://github.com/VanshSahay).*