Skip to content

Repository files navigation

StableGate

KYC-Gated Institutional Stablecoin Swaps on Uniswap v4

Base (credentials) → Reactive Network (automation) → Unichain (execution)


What is StableGate?

StableGate is permissioned stablecoin swap infrastructure built on Uniswap v4 hooks. Institutions earn verified access to a 1:1 USDC/USDT0 Constant Sum Market Maker by minting credential NFTs on Base. The entire onboarding, tier assignment, and revocation lifecycle is automated cross-chain by Reactive Network — zero manual steps between credential issuance and swap execution.

The Problem

Institutional stablecoin trading faces a tension: DeFi offers efficient execution but can't enforce compliance; CeFi offers compliance but sacrifices transparency. There's no way to have permissioned access and on-chain execution without manual admin overhead.

How StableGate Solves It

  1. Credential NFTs on BaseMembershipNFT (trading) and LPMembershipNFT (liquidity) are non-transferable ERC721 tokens representing verified institutional relationships
  2. Reactive Network automation — A Reactive Smart Contract monitors NFT events on Base and delivers callbacks to Unichain automatically — allowlisting, tier forwarding, expiry sync, and revocation
  3. Uniswap v4 hook enforcementPermissionedCSMMHook enforces all rules on-chain: allowlist gates, tiered fees, membership expiry, daily volume caps, and LP whitelist

Architecture

Base Sepolia Reactive Lasna Unichain Sepolia
──────────────── ────────────────── ──────────────────────
MembershipNFT ─── mint ────────► AllowlistReactive ── callback ──► PermissionedCSMMHook
(Trading NFT) Transfer Contract addToAllowlist beforeSwap()
(Tiered, Expiry) TierUpdated (4 subscriptions) setTier checks allowlist
ExpirySet setExpiry applies tier fee
enforces daily cap
LPMembershipNFT ─ mint ────────► (same RSC) ──── callback ──────► beforeAddLiquidity()
(LP NFT) Transfer addToLPWhitelist checks lpWhitelist
burn ─────────► (same RSC) ──── callback ──────► removeFromAllowlist
atomic state cleanup

Unichain Integration

StableGate uses Uniswap v4 hooks on Unichain as the core execution layer. Here's exactly where and how:

PermissionedCSMMHook.sol — The Hook Contract

Hook FunctionWhat It DoesCode Reference
beforeSwap()Checks allowlist, verifies expiry, enforces daily volume cap, computes tier-based fee, executes 1:1 CSMM pricing via NoOp patternsrc/PermissionedCSMMHook.sol:167-241
beforeAddLiquidity()Checks lpWhitelist[lp] — only LP-credentialed institutions can provide liquiditysrc/PermissionedCSMMHook.sol:141-150

CSMM (Constant Sum Market Maker) Implementation

The hook implements 1:1 stablecoin pricing using the NoOp pattern — it fully handles the swap, bypassing the AMM curve:

1. poolManager.take(input) ← pull input tokens from PoolManager to hook
2. compute fee (tier-based) ← Gold: 0 bps, Silver: 1 bps, Bronze: 3 bps
3. split fee 50/50 ← LP share via donate(), operator share accrued
4. poolManager.donate(lpShare) ← distribute LP fees proportionally to LPs
5. sync → transfer → settle ← push output tokens (minus fee) to PoolManager
6. return BeforeSwapDelta ← tell PoolManager the hook handled everything

Fee Split via donate()

Every non-Gold swap fee is split:

  • 50% to LPs — returned to the pool via poolManager.donate(), distributed proportionally to in-range liquidity providers
  • 50% to operator — accrued in accruedFees[currency], withdrawable anytime via withdrawFees()

Tiered Fee & Volume System

TierFee (bps)Daily Volume CapDerived From
Gold0UnlimitedDAILY_LIMIT_GOLD = 0
Silver15,000,000 USDCDAILY_LIMIT_SILVER = 5_000_000e6
Bronze31,000,000 USDCDAILY_LIMIT_BRONZE = 1_000_000e6

Two Independent Credentials

NFTGovernsHook CheckCan Hold Both?
MembershipNFTSwap accessbeforeSwapallowlist[swapper]Yes
LPMembershipNFTLP accessbeforeAddLiquiditylpWhitelist[lp]Yes

Revoking one doesn't affect the other. Each credential has its own lifecycle.

Pool Configuration

Pool: MockUSDC / MockUSDT0
Price: 1:1 (sqrtPriceX96 for equal decimals)
Fee: 100 (0.01%)
Tick Spacing: 1
Hook: PermissionedCSMMHook (CREATE2 deployed with address-encoded permission flags)

Reactive Network Integration

StableGate uses Reactive Network to automate cross-chain state synchronization. Here's exactly where and how:

AllowlistReactiveContract.sol — The Reactive Smart Contract

The RSC extends AbstractReactive and subscribes to 4 event streams on Base Sepolia:

#EventSource ContractCallback EmittedHook Function Called
1Transfer(from, to, tokenId)MembershipNFTaddToAllowlistReactive or removeFromAllowlistReactiveAllowlist add/remove + atomic state cleanup
2TierUpdated(institution, tier)MembershipNFTsetInstitutionTierSet fee tier on hook
3ExpirySet(institution, expiry)MembershipNFTsetInstitutionExpirySet expiry timestamp on hook
4Transfer(from, to, tokenId)LPMembershipNFTaddToLPWhitelist or removeFromLPWhitelistLP whitelist add/remove

Key design: log._contract routing — Subscriptions 1 and 4 use the same Transfer topic but different origin contracts. The RSC distinguishes them by checking log._contract:

if (log._contract == membershipNFT) {
// Trading credential → allowlist callbacks
} elseif (log._contract == lpMembershipNFT) {
// LP credential → LP whitelist callbacks
}

Callback Authorization

The hook extends AbstractCallback from the Reactive library:

  • rvmIdOnly(rvm_id) modifier on all callback functions
  • rvm_id is set to the deployer address in the constructor
  • Reactive Network replaces the first argument of every callback with the RSC deployer's address
  • All callback function signatures include address rvm_id as the first parameter
function addToAllowlistReactive(address_rvm_id, addressinstitution)
externalrvmIdOnly(_rvm_id) { ... }

Atomic Revocation

When a MembershipNFT is burned on Base:

Base: burn(tokenId)
→ Transfer(institution, 0x0, tokenId)
→ RSC.react() detects burn
→ emit Callback: removeFromAllowlistReactive(rvm_id, institution)
→ Hook._removeFromAllowlist():
allowlist[addr] = false
institutionTier[addr] = Bronze ← safe default
institutionExpiry[addr] = 0 ← cleared
dailyVolume[addr] = 0 ← cleared
lastResetBlock[addr] = 0 ← cleared
emit InstitutionStateCleared(addr)

Re-onboarding always starts from a clean slate.

Callback Proxy Funding

The Unichain callback proxy charges the hook contract for gas on every callback delivery. The deploy script pre-funds the hook's reserves on the proxy:

callbackProxy.call{value: 0.005ether}(
abi.encodeWithSignature("depositTo(address)", address(hook))
);

Contracts

ContractChainLinesDescription
PermissionedCSMMHook.solUnichain~350v4 hook — CSMM, allowlist, tiers, fees, expiry, daily caps, LP whitelist, fee split, atomic revocation
MembershipNFT.solBase~135Trading credential — tiered ERC721, non-transferable, with expiry
LPMembershipNFT.solBase~65LP credential — binary ERC721, non-transferable
AllowlistReactiveContract.solReactive Lasna~225RSC — 4 subscriptions, event routing, callback emission
IStableGate.sol~38Shared Tier enum, errors, events
MockUSDC.solUnichain~206-decimal testnet USDC with public mint
MockUSDT0.solUnichain~206-decimal testnet USDT0 with public mint

Test Suite

151 tests across 7 suites:

SuiteTestsCoverage
PermissionedCSMMHookTest71Allowlist, tiers, fees, expiry, daily limits, fee split/withdraw, LP whitelist, beforeAddLiquidity, credential independence, atomic revocation
MembershipNFTTest22Mint, revoke, tiers, expiry, transfer lock, ExpirySet event
AllowlistReactiveContractTest22Mint/burn/transfer routing, tier forwarding, expiry forwarding, LP routing, payload encoding
ForkDemoTest13Multi-chain fork — real mainnet USDC/USDT0, full lifecycle including LP access control
LPMembershipNFTTest11Mint, revoke, transfer lock, admin
MockTokensTest10Decimals, mint, transfer, name/symbol
DeployScripts2Deployment script compilation
forge test -vvv # Run all 151 tests

Setup & Demo Guide

Prerequisites

ToolVersionInstall
Node.js20+https://nodejs.org
FoundryLatestcurl -L https://foundry.paradigm.xyz | bash && foundryup
Yarn1.x+npm install -g yarn
jqAnybrew install jq (macOS)

Wallets

You need 5 wallets (generate with cast wallet new):

WalletPurposeFunding Needed
OperatorDeploys contracts, admin opsETH on Base Sepolia + Unichain Sepolia + lREACT on Reactive Lasna
Institution BronzeTrades with 3 bps feeETH on Unichain Sepolia (tokens minted by deploy script)
Institution SilverTrades with 1 bps feeETH on Unichain Sepolia
Institution GoldTrades with 0 feeETH on Unichain Sepolia
Institution LPSeeds pool liquidityETH on Unichain Sepolia

Faucets:

AssetSource
Base Sepolia ETHCoinbase Faucet
Unichain Sepolia ETHUnichain Faucet
lREACTSend Sepolia ETH to 0x9b9BB25f1A81078C544C829c5EB7822d747Cf434

USDC and USDT0 are not needed from faucets — the deploy script mints mock tokens automatically.

Step 1: Clone & Build

git clone https://github.com/SamAg19/StableGate.git
cd StableGate
git submodule update --init --recursive
forge build

Step 2: Configure Environment

cp .env.example .env

Fill in your wallet addresses and private keys:

DEPLOYER_ADDRESS=0x...
DEPLOYER_PRIVATE_KEY=0x...
INSTITUTION_BRONZE=0x...
INSTITUTION_SILVER=0x...
INSTITUTION_GOLD=0x...
INSTITUTION_LP=0x...
BASE_SEPOLIA_RPC=https://base-sepolia-rpc.publicnode.com
UNICHAIN_SEPOLIA_RPC=https://unichain-sepolia-rpc.publicnode.com
REACTIVE_LASNA_RPC=https://lasna-rpc.rnk.dev/

Step 3: Deploy All Contracts

bash script/deploy-all.sh

This deploys across all 3 chains sequentially:

  1. Base Sepolia — MembershipNFT + LPMembershipNFT
  2. Unichain Sepolia — MockUSDC, MockUSDT0, PermissionedCSMMHook, pool init, hook reserve seeding, callback proxy funding
  3. Reactive Lasna — AllowlistReactiveContract

State is saved to deployments.json. If Reactive deploy fails, retry with:

bash script/deploy-all.sh --reactive

Step 4: Mint Tokens to LP Institution

The deploy script mints tokens to the 3 tier institutions automatically. Mint separately for the LP institution:

cast send $USDC_ADDRESS"mint(address,uint256)"$INSTITUTION_LP 500000000000 \
--rpc-url $UNICHAIN_SEPOLIA_RPC --private-key $DEPLOYER_PRIVATE_KEY
cast send $USDT0_ADDRESS"mint(address,uint256)"$INSTITUTION_LP 500000000000 \
--rpc-url $UNICHAIN_SEPOLIA_RPC --private-key $DEPLOYER_PRIVATE_KEY

Step 5: Set Up Demo

cd demo
yarn install
cp .env.example .env # Fill in all addresses + private keys from deploy output

Extract ABIs from compiled contracts:

cd .. && forge build && bash demo/scripts/extract-abis.sh &&cd demo

Step 6: Run the Tests

Before running the live demo, verify everything works locally:

# Run all 151 tests
forge test -vvv

Fork tests simulate the entire cross-chain lifecycle using real mainnet USDC/USDT0 on forked Base and Unichain:

forge test --match-contract ForkDemoTest -vvv

The fork demo proves (without any testnet deployment):

  • Non-allowlisted swap rejected
  • MembershipNFT minted on Base fork → RSC react() emits Callback
  • Callback delivered to hook (simulated via vm.prank) → institution allowlisted
  • Gold tier: 10,000 USDC → 10,000 USDT0 (1:1, zero fee)
  • Bronze tier: 10,000 USDC → 9,997 USDT0 (3 bps fee deducted)
  • Reverse swap: 5,000 USDT0 → 5,000 USDC
  • Expired membership reverts with MembershipExpired
  • Daily volume cap enforced (second swap blocked at cap)
  • LP whitelist: whitelisted LP adds liquidity, non-whitelisted blocked
  • Revoked institution blocked, existing LP positions untouched
  • Trading and LP credentials are independent

To run just the unit tests (no network fork required):

forge test --match-contract "PermissionedCSMMHookTest|MembershipNFTTest|LPMembershipNFTTest|AllowlistReactiveContractTest|MockTokensTest" -vvv

Step 7: Run the Demo

Full 4-phase demo (LP → Gold → Silver → Bronze):

bash scripts/run-demo.sh

Individual runs:

yarn demo # Silver tier (default)
yarn demo --tier=gold # Gold tier
yarn demo --tier=bronze # Bronze tier
yarn demo --only=lp # LP steps only (1-3)
yarn demo --only=trading --tier=gold # Trading only, Gold
yarn demo --fresh # Ignore snapshot, start from step 1

Demo Walkthrough

The demo runs 6 steps in two phases:

Phase 1: LP Initialization (Steps 1-3)

Uses the dedicated LP institution (500k USDC + 500k USDT0).

StepChainWhat You See
1. Mint LP NFTBase SepoliaAdmin mints LPMembershipNFT → Transfer event emitted
2. Wait for callbackUnichain SepoliaPolls isLPWhitelisted() with spinner + Reactscan link (~15-45s)
3. Add liquidityUnichain Sepolia50k USDC + USDT0 deposited via PositionManager with Permit2 flow

Phase 2: Trading (Steps 4-6)

Uses the tier-specific institution (Gold/Silver/Bronze).

StepChainWhat You See
4. Mint Trading NFTBase SepoliaAdmin mints MembershipNFT with tier → 3 events emitted
5. Wait for callbacksUnichain SepoliaPolls isAllowlisted() + institutionTier() with Reactscan link
6. Execute swapUnichain Sepolia10k USDC → USDT0 swap — shows fee deduction + 50/50 split

What the Demo Proves

ClaimHow It's Demonstrated
Zero-touch onboardingMint NFT on Base → institution can swap on Unichain within 15-45s, no admin calls needed
Tiered fees workGold: 10,000 received. Silver: 9,999 received (1 bps). Bronze: 9,997 received (3 bps)
LP access is separateLP institution adds liquidity; trading institutions can only swap
Fee economicsEvery fee split 50/50: LP share via donate(), operator share via accruedFees
Cross-chain automationReactscan link shows RSC activity live during the wait steps

Presenter Tips

  1. Open Reactscan during Steps 2 and 5 to show callbacks arriving in real-time
  2. Run run-demo.sh for the full showcase — all 3 fee tiers back-to-back
  3. Compare the swap outputs across Gold/Silver/Bronze to highlight fee differences
  4. Point out hookDataabi.encode(institutionAddress) is how the hook identifies who's swapping
  5. If a step was already completed (institution already minted), the demo skips it automatically and proceeds

Repository Structure

StableGate/
├── src/
│ ├── PermissionedCSMMHook.sol # Uniswap v4 hook (main contract)
│ ├── MembershipNFT.sol # Trading credential NFT (Base)
│ ├── LPMembershipNFT.sol # LP credential NFT (Base)
│ ├── AllowlistReactiveContract.sol # Reactive Smart Contract
│ ├── interfaces/IStableGate.sol # Shared types and events
│ └── mocks/MockUSDC.sol, MockUSDT0.sol # Testnet tokens
├── test/ # 151 tests (7 suites)
├── script/
│ ├── deploy-all.sh # One-command 3-chain deployment
│ ├── DeployBase.s.sol # Base Sepolia
│ ├── DeployUnichain.s.sol # Unichain Sepolia
│ └── DeployReactive.s.sol # Reactive Lasna
└── demo/
├── scripts/run-demo.sh # Full 4-phase demo runner
├── src/
│ ├── index.ts # Entry point (--tier, --only, --fresh)
│ ├── config.ts # Chains, constants, institutions
│ ├── clients.ts # 3 public + 5 wallet clients
│ ├── utils.ts # Uniswap v4 SDK integration
│ ├── steps/step1-6.ts # Demo step implementations
│ ├── snapshot.ts # Resume-on-failure system
│ └── poller.ts # Cross-chain polling with spinner
└── abis/ # Contract ABIs

Sponsor Tracks

Unichain

  • Uniswap v4 hook implementing CSMM pricing with permissioned access, tiered fees, and LP whitelist
  • Fee split via donate() — LP share returned to pool proportionally, operator share accrued for withdrawal
  • NoOp swap pattern — hook fully controls execution, bypassing the AMM curve for 1:1 stablecoin pricing
  • beforeAddLiquidity LP gate — separate credential for liquidity provision
  • CREATE2 deployment with HookMiner for address-encoded permission flags
  • 151 tests including 13 mainnet fork tests with real USDC/USDT0

Reactive Network

  • 4-subscription RSC monitoring two NFT contracts across chains
  • log._contract routing — same Transfer topic, different origin contract, different callback paths
  • AbstractCallback + rvmIdOnly for secure callback authorization
  • Atomic revocation — NFT burn on Base triggers full state cleanup on Unichain in one callback
  • Live testnet demo with Reactscan monitoring links during cross-chain wait steps

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages