Streaming USDC payroll and treasury infrastructure on Arc - salaries vest every second, taxes split themselves, income becomes a liquid asset, and idle escrow earns yield across chains.
Built for the Programmable Money Hackathon · DeFi Track
- Why Sluice
- Live Features
- Architecture
- How the Core Flows Work
- Quick Start
- Testing
- Live on Arc Testnet
- Repository Layout
- Technology Stack
- Roadmap & Planned Integrations
- Screenshots
- Security & Disclaimers
Payroll is the largest recurring money flow on earth, and it still runs like a 1970s batch job:
- Workers deliver value every second but are paid every 30 days - between pay runs they are effectively their employer's unsecured creditors.
- Tax withholding is manual back-office work, reconciled after the fact.
- Payroll escrow is one of the largest pools of dead capital in any company.
- Future income is real economic value that workers cannot access, sell, or insure.
Sluice rebuilds payroll as programmable money on Arc - Circle's L1 where USDC is the gas token and finality is sub-second. An employer escrows salary once; from that block onward the money flows.
Everything below is implemented, tested, and clickable in this repository today.
Employers open a stream with an amount, duration, tax rate, and tax vault
(createStream). Salary vests continuously at a fixed USDC-per-second rate with
6-decimal precision; employees withdraw any vested amount at any time.
Every withdrawal automatically routes the configured basis points to a designated tax vault before the employee is paid - compliance enforced by the contract, not the back office. Configurable per stream (e.g. 8% payroll tax, 0% contractor).
Each stream is an SFT whose token value equals the remaining streamable USDC.
A vendored, minimal ERC-3525 implementation (contracts/erc3525/) supports:
- Split - carve part of a salary off to another address; deposit, rate, and vesting schedule carry over pro-rata with exact math.
- Merge - combine same-schedule, same-employer streams.
- Transfer - whole streams change owners; the new owner collects all future flow.
Employees list streams for sale at a discount (listStreamForSale); any liquidity
provider buys them (buyStream) - payment goes straight to the seller and the SFT
transfers atomically. Future income becomes instant liquidity, on standard rails.
The protocol takes 0.5% of the ask (MARKET_FEE_BPS) at purchase - the only
fee anywhere in Sluice, charged at the seller's windfall moment, never on payroll.
borrowSalaryAdvance lets a stream owner draw up to 50% of unwithdrawn value
immediately - zero interest, zero liquidation risk. The advance repays itself as
salary continues to vest.
Employees pay a one-time 0.5% premium (insureStream) into a share-based pool
underwritten by USDC stakers (stakeInsurancePool). If the employer cancels a
stream early, the unvested shortfall is claimable from the pool
(claimDefaultCoverage). Stakers earn every premium.
Per-wallet automation rules run after every withdrawal - e.g. "swap 20% of each
paycheck to EURC." Executed through @circle-fin/swap-kit with the connected
wallet; on chains the kit does not yet route, the trigger records a labeled
simulated run through the identical code path.
Arc settles; every other chain is an on/off ramp. Via SluiceGate:
- Fund from any chain - a single CCTP burn with a
FUND_STREAMhook opens the stream on Arc; the employer keeps cancel rights. - Withdraw to any chain -
withdrawToChainpays tax on Arc and burns the net amount to the employee's chosen destination: Base, Ethereum, Arbitrum and OP Sepolia, Avalanche Fuji - or Solana Devnet viawithdrawToDomain, where the relayer delivers the mint to the recipient's USDC token account with a v0 transaction + address lookup table. All six are live and proven in production. - Buy a stream from any chain - a burn with a
BUY_STREAMhook settles the marketplace purchase atomically on mint; over-payments and vanished listings refund automatically.
The word "bridge" appears nowhere in the UI.
Idle escrow above a 40% liquidity buffer sweeps into SluiceTreasury
(sweepIdle, permissionless), which rebalances across yield venues - a local Arc
money market and a remote vault reached via hooked CCTP transfer. Withdrawals that
outrun the buffer auto-recall liquidity mid-transaction; remote positions come
home with their accrued yield through a hooked CCTP return. Full NAV accounting and
an on-chain activity feed. NAV above swept principal is protocol revenue,
claimable with claimYield - principal coverage is never touched.
Streams, withdrawals, advances and insurance carry zero protocol fee; premiums
accrue entirely to stakers. Revenue comes from two switchable surfaces, both live
and both readable on-chain from the landing page: float yield (idle escrow
auto-routed to yield venues; SluiceTreasury.yieldEarned() above principal is
claimable protocol revenue) and the 0.5% marketplace take
(totalMarketFees). The same model payroll processors have run for decades -
earn on the pre-funded window - except auditable by anyone.
Next.js 16 App Router app: marketing landing, live dashboard with per-second vesting animation, stream detail (withdraw / advance / insure / split / sell), marketplace, treasury console, and automation rules - all wired to the live Arc Testnet deployment. An employer / employee switcher in the sidebar scopes the navigation to the role: employers get the full product, employees get the paycheck side (dashboard, marketplace, automation, onboarding) with creation surfaces hidden - presentation only, so deep links keep working and the contracts remain the real permission layer.
flowchart LR
subgraph Base["Any CCTP chain — 5 EVM testnets + Solana Devnet (yield vault: Base Sepolia)"]
LP[LP / Employer wallet]
MB[TokenMessengerV2]
RV[RemoteYieldVault<br/>8.6% APY]
end
subgraph Arc["Arc L1 — USDC gas, sub-second finality"]
MA[TokenMessengerV2]
GATE[SluiceGate<br/>hook dispatcher]
SLUICE[Sluice.sol<br/>ERC-3525 streams · marketplace<br/>advances · insurance pool]
TRES[SluiceTreasury<br/>NAV · buffer · recall]
AMM[Morpho USDC Vault<br/>ERC-4626 · 3.5% APY]
end
REL((Sluice relayer /<br/>Circle Iris attestation))
LP -- "burn + hook<br/>(fund · buy)" --> MB
MB <-- messages --> REL
REL <-- messages --> MA
MA -- "mint + onCCTPHook" --> GATE
GATE -- "createStreamFor<br/>buyStreamFor<br/>withdrawFromStreamFor" --> SLUICE
SLUICE -- "sweepIdle ⇄ recall" --> TRES
TRES --> AMM
TRES -- "hooked CCTP<br/>deposit / return" --> MA
MA -.-> RV
Six contracts deployed live — every one source-verified on its explorer, all running against native Arc USDC and Circle's canonical CCTP v2:
| Contract | Purpose | Live on |
|---|---|---|
Sluice.sol |
Core payroll: ERC-3525 streams, vesting, tax splits, marketplace (0.5% take), advances, insurance pool, escrow-liability accounting | Arc ✅ |
crosschain/SluiceGate.sol |
CCTP hook receiver - cross-chain funding, buyouts, withdrawal exits | Arc ✅ |
crosschain/SluiceTreasury.sol |
Idle-escrow yield router: sweep / rebalance / recall, NAV, claimYield revenue |
Arc ✅ |
crosschain/ERC4626Adapter.sol |
Real ERC-4626 adapter - deposits idle escrow into the Morpho USDC vault on Arc | Arc ✅ |
crosschain/YieldAdapters.sol |
CCTPRemoteAdapter - routes escrow to the remote vault via hooked CCTP burns |
Arc ✅ |
crosschain/RemoteYieldVault.sol |
Destination-chain vault; accrues APY, exits home with yield | Base Sepolia ✅ |
Also in source but never deployed: the vendored ERC-3525 base
(erc3525/), the canonical CCTP v2 interfaces
(CCTPInterfaces.sol), and Foundry
test fixtures (mocks/, plus ReserveYieldAdapter) that
exist only so the 50-test suite can run without live chains - nothing mocked
ships in the deployment above.
No mock messenger. Cross-chain transfers go through Circle's canonical
TokenMessengerV2and are attested by Circle's Iris API; the localweb/scripts/cctp-relayer.mjsonly delivers attested messages and invokes their hooks — it does not stand in for CCTP. Circle's Bridge Kit is deliberately not used: it does not express thedepositForBurnWithHookpayloads Sluice's gate depends on.
Stream lifecycle - employer escrows amount USDC → SFT minted to employee with
value = amount → vests at amount / duration per second → withdrawals burn SFT
value, split tax, pay net → employer cancellation pays out vested, refunds
unvested (insured streams: the shortfall becomes a pool claim).
Cross-chain fund - employer burns USDC on chain B with an ABI-encoded
(FUND_STREAM, employer, recipient, duration, taxBps, taxVault) hook → relayer
delivers → messenger mints to SluiceGate and invokes onCCTPHook → gate opens
the stream with the minted funds, crediting the original employer.
Treasury cycle - anyone calls sweepIdle() (safe by construction: only
escrow above the 40% buffer moves) → rebalance() allocates 50/50 to the Arc
money market and, via hooked CCTP, the remote vault → any withdrawal exceeding
Sluice's local balance triggers treasury.recall() inside _push →
requestRemoteReturn() + relayer bring the remote position home with yield.
No setup needed: the app is live at www.sluiceapp.xyz — connect any EVM wallet and it offers to add Arc Testnet automatically.
To run it locally instead — requirements: Node 20+ and an injected wallet (MetaMask or similar).
git clone --recurse-submodules https://github.com/mrnetwork0001/Sluice.git
cd Sluice/web && npm install && npm run devOpen http://localhost:3000 and connect your wallet — the app offers to add
Arc Testnet (chain 5042002) automatically. Fund it from the faucet linked in
the app; on Arc, USDC is the gas token, so one faucet claim covers both.
There is no local chain to boot and no seed data to generate: the app talks directly to the live Arc Testnet deployment listed below.
Optional environment (only for the Circle-hosted features — see
web/.env.local.example):
| Variable | Enables |
|---|---|
NEXT_PUBLIC_CIRCLE_APP_ID |
MPC wallet onboarding at /onboard |
CIRCLE_API_KEY |
server-side Circle Wallets + Swap Kit proxy |
Cross-chain flows need the relayer. Funding or exiting via Base Sepolia requires the CCTP relayer to deliver attested messages and execute their hooks:
PRIVATE_KEY=0x... node web/scripts/cctp-relayer.mjsWithout it a burn is still attested by Circle, but nothing mints on the destination chain — the stream will not appear.
The relayer wallet pays gas on the destination chain of every delivery, so it needs a small native balance on each chain you fund from or withdraw to (Base Sepolia ETH, Sepolia ETH, Fuji AVAX, …). It prints a per-chain gas report at startup and HOLDs deliveries to unfunded chains - without poisoning them - until the wallet is topped up, at which point they deliver automatically.
For Solana Devnet payouts, additionally set SOLANA_PRIVATE_KEY (base58 secret
of a devnet-funded keypair) in the same .env - the startup preflight reports
Solana leg ready with the SOL balance and Circle route registrations, and the
first delivery creates a persistent address lookup table it reuses thereafter.
In production none of this runs locally: the live site's relayer is a pm2
app on a VPS (pm2 start web/scripts/cctp-relayer.mjs --name sluice-relayer),
reading the same address JSONs from its clone of this repo. Updating it after
a redeploy is git pull && pm2 restart sluice-relayer.
Where to see each feature
| Feature | Where to click |
|---|---|
| Live vesting, withdraw, advance, insure, split, sell | Dashboard → any stream card |
| Withdraw to another chain | Stream detail → "Pay out on …" (5 EVM testnets + Solana Devnet) |
| Fund a stream from another chain | Create Stream → Fund from: Base via CCTP |
| Buy a stream cross-chain | Marketplace → "Buy from Base via CCTP ⚡" |
| Treasury sweep / rebalance / recall + activity feed | Treasury tab |
| Swap Kit auto-triggers + history | Automation tab |
| Seedless employee wallet (Circle MPC) | /onboard |
forge test # 50 tests
forge test -vvv # verbose tracesCoverage spans per-second rate math (6-decimal precision), ERC-3525 value splits
and merges, marketplace validation, advance caps, insurance premiums/claims,
cancellation settlement, and the entire cross-chain stack - hooked funding,
cross-chain buyouts (including refund paths), withdrawal exits, treasury
sweep/rebalance/recall bounds, and remote yield returns. The test suite plays the
CCTP relayer itself, so cross-chain flows are exercised deterministically in one
EVM. CI (GitHub Actions) enforces forge fmt, the build, and the full suite on
every push.
The core Sluice contract is deployed, seeded, and streaming on the real Arc Testnet - per-second vesting, tax splits, marketplace, advances, and the insurance pool all run against native USDC:
| Sluice (core payroll) | 0xE4B8E984B63165846008d936e4B5D5c6D6d5BCE4 |
| SluiceGate (CCTP entry/exit) | 0xe3510af408bffbd2Cf7629CB5Fc25da745DA7671 |
| SluiceTreasury (auto-yield) | 0x52fC38aDB7BC3A5DC049BD21b8838436031be4fc |
| Morpho USDC Vault via Circle Earn (3.5%) | 0x547612eb7e88577a80Ad5636EC8dF93e80EC3864 |
| CCTP Remote Adapter | 0xe132B4Cd7F451d3CA7a026b7b129B705a13f843D |
| RemoteYieldVault (Base Sepolia, 8.6%) | 0x95c46545a6eE4D1D604e739E227C5Db8d417AC97 |
| Chain ID | 5042002 (0x4CEF52) |
| RPC | https://rpc.testnet.arc.network |
| Explorer | https://testnet.arcscan.app |
| USDC (native gas) | 0x3600000000000000000000000000000000000000 |
These are the addresses the app actually loads, from
web/src/lib/deployments.5042002.json
and deployments.84532.json.
All six are source-verified - click any address to read the Solidity and call it from the explorer's Read/Write tabs. Arcscan and Base Sepolia's explorer both run Blockscout, so verification needs no API key.
Auto-triggers are real swaps too. Withdrawal rules route a slice of each
paycheck through Circle Swap Kit on Arc — live quotes come from Circle's routing
service and the conversion is an on-chain transaction
(example: 0.20 USDC → 0.15503 EURC).
EURC on Arc is 0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a. Because Swap Kit's
API blocks cross-origin browser calls, quote/route requests are forwarded by a
server-side proxy (web/src/app/api/circle/[...path]/route.ts) — the swap itself
is still signed and broadcast by the user's wallet.
Cross-chain runs on real Circle CCTP v2 — nothing is mocked. Burns go through
the canonical TokenMessengerV2
(0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA, Arc domain 26 ↔ Base Sepolia
domain 6), Circle's Iris API attests them, and the Sluice relayer
(web/scripts/cctp-relayer.mjs) delivers each
mint via MessageTransmitterV2.receiveMessage and executes its hook. Yield is
paid in real USDC from pre-funded reserves. Verified end-to-end on the live
testnets: a 0.10 USDC withdrawal exited Arc and landed as 0.092 USDC on Base
Sepolia (net of 8% tax); a 2.00 USDC burn on Base Sepolia opened a salary
stream on Arc through the gate hook; and the treasury swept escrow, split it
across both chains, and recalled the remote position home with its yield. The
current deployment was exercised the same way on day one: withdrawal with an 8%
tax split, a self-repaying advance, an insured stream, a sweep + rebalance that
CCTP-routed half the idle escrow to Base Sepolia, and a live marketplace sale
that paid the 0.5% protocol take (totalMarketFees reads 2500 on-chain).
The full withdrawal matrix has since been proven from the live site: streams funded from Avalanche Fuji, and salaries withdrawn to Base Sepolia, Ethereum Sepolia, Arbitrum Sepolia, OP Sepolia, Avalanche Fuji and Solana Devnet - every delivery executed by the hosted relayer, ending in a real balance on the destination chain's explorer. The Solana leg crosses the EVM/SVM boundary with the same Circle-attested messages, delivered as v0 transactions against the CCTP Solana programs.
The full set, in order (each script writes the address JSONs the next one and the
frontend read). Deploy.s.sol also wires setFeeRecipient to the deployer:
export PRIVATE_KEY=0x... # funded with Arc testnet USDC + Base Sepolia ETH
forge script script/Deploy.s.sol --rpc-url arc_testnet --broadcast
SLUICE=0x... FROM_BLOCK=... \
forge script script/DeployCrossChain.s.sol --rpc-url arc_testnet --broadcast
ARC_TREASURY=0x... \
forge script script/DeployBaseVault.s.sol --rpc-url https://sepolia.base.org --broadcast
SLUICE=0x... TREASURY=0x... REMOTE_VAULT=0x... GATE=0x... LOCAL_ADAPTER=0x... \
FROM_BLOCK=... TREASURY_FROM_BLOCK=... \
forge script script/AddRemoteAdapter.s.sol --rpc-url arc_testnet --broadcastSeeding caveat:
script/SeedArc.s.solfails in Foundry's local simulation — Arc's USDC calls a blocklist precompile (0x1800…0001) the local EVM cannot execute, and--skip-simulationdoes not bypass script-phase execution. Seed with direct transactions instead (cast sendofapprove,createStream,stakeInsurancePool), which never touch a local EVM.
Re-verify after every deploy. Verification is not automatic, and an unverified contract is unreadable to anyone evaluating this. One command re-verifies the whole live set, reading addresses straight from the deployment JSONs so it stays correct after a redeploy:
./script/verify-all.shIt is idempotent - already-verified contracts report as passes - so it is safe to run any time, and should be run immediately after any redeploy.
Careful:
script/Deploy.s.solrewritesweb/src/lib/deployments.5042002.jsonwith only thesluicekey, dropping thegate/treasury/ adapter /fromBlockentries and disabling every cross-chain feature. Re-runscript/DeployCrossChain.s.solandscript/AddEarnAdapter.s.solafterwards, or restore the other keys by hand.
If contracts changed, regenerate the typed ABIs (keep the whole expression on one
line — the Next.js SWC parser rejects a line-broken ] as const):
python3 - <<'EOF'
import json, subprocess
for target, name, out in [
("contracts/Sluice.sol:Sluice", "sluiceAbi", "web/src/lib/sluiceAbi.ts"),
("contracts/crosschain/SluiceTreasury.sol:SluiceTreasury", "treasuryAbi", "web/src/lib/treasuryAbi.ts"),
]:
abi = json.loads(subprocess.check_output(["forge", "inspect", target, "abi", "--json"]))
open(out, "w").write(f"export const {name} = {json.dumps(abi, indent=2)} as const;\n")
EOFcontracts/ Sluice.sol · vendored ERC-3525
contracts/crosschain/ SluiceGate · SluiceTreasury · ERC4626Adapter
YieldAdapters · RemoteYieldVault · CCTPInterfaces
contracts/mocks/ MockUSDC · MockERC4626 (test fixtures only)
test/ Sluice.t.sol · CrossChain.t.sol (50 tests)
script/ Deploy · DeployCrossChain · AddEarnAdapter
AddRemoteAdapter · DeployBaseVault
web/ Next.js 16 app — wagmi v3 / viem / Tailwind v4
web/scripts/cctp-relayer.mjs CCTP attestation delivery + hook execution
web/src/lib/ chain configs · typed ABIs · deployment address JSONs
docs/ screenshots · pitch deck · PITCH_DECK.md
| Layer | Technology |
|---|---|
| Settlement | Arc L1 - USDC gas, sub-second finality |
| Contracts | Solidity 0.8.x, Foundry (via-IR), vendored ERC-3525 |
| Cross-chain | Circle CCTP v2 — canonical TokenMessengerV2 + hooks, Iris attestation, hosted delivery relayer; 5 EVM testnets + Solana Devnet (Anchor / v0 + lookup table) |
| Stablecoin tooling | Swap Kit (real USDC→EURC auto-conversion), Gateway / Unified Balance Kit (unified cross-chain balance), Circle Wallets (MPC onboarding), Morpho USDC vault via ERC-4626 (the vault Circle Earn surfaces on Arc) |
| Frontend | Next.js 16 (App Router), wagmi v3, viem, TanStack Query, Tailwind v4 |
| Quality | 50 Foundry tests, GitHub Actions CI (fmt + build + test), manual end-to-end verification on live Arc + Base Sepolia |
Phase 1 - Production rails — ✅ shipped
- Arc testnet deployment of the full contract suite — live, addresses above
- Real CCTP v2 — canonical
TokenMessengerV2, Circle Iris attestation, Arc domain 26 ↔ Base Sepolia domain 6. The mock messenger is gone. - Circle Gateway / Unified Balance Kit on the dashboard
- Circle Wallets — seedless MPC onboarding for employees at
/onboard - Circle Earn — idle escrow earns real yield in the Morpho USDC vault
- Public deployment at sluiceapp.xyz — frontend on Vercel, relayer on a VPS
Phase 2 - Payroll operations — partly shipped
- Batch stream creation — paste a roster or drop a CSV, exact amounts or percentage splits, fundable from another chain
- EURC salary legs — on-withdrawal FX via Swap Kit auto-triggers
- Scheduled top-ups / recurring pay periods
- Org-level treasury policies and role-based access
- Streaming invoices for contractors (reverse direction)
- More CCTP domains — Ethereum, Arbitrum, OP Sepolia, Avalanche Fuji all live for funding and payouts (adding one is configuration, not code)
- Solana Devnet payouts —
withdrawToDomainburns to the recipient's USDC token account; the relayer mints via the CCTP Solana programs with a v0 transaction + lookup table. Proven live, twice. - Solana funding — the same
FUND_STREAMhook with a Solana signer: the CCTP Solana program already supportsdeposit_for_burn_with_hook, so opening a stream from a Solana wallet is wallet UX, not new protocol work - Hosted relayer — runs as a pm2 app on a VPS; no local process needed
Phase 3 - Deeper DeFi
- Real yield venues behind the adapter interface (Aave-class money markets, tokenized T-bill vaults) with risk-weighted allocation targets
- Secondary-market order book for streams (bids, auctions, partial fills)
- Under-collateralized credit scoring from on-chain income history
- Insurance-pool tranching (junior/senior risk)
Phase 4 - Reach
- Fiat off-ramp partners on withdrawal (salary → bank account)
- Account-abstraction wallets & gas sponsorship for employees
- Mobile-first PWA
- Compliance modules: jurisdiction-aware withholding templates, exportable tax-vault reports
![]() |
![]() |
![]() |
![]() |
Full gallery in docs/screenshots/. Pitch deck:
docs/Sluice_Checkpoint_Deck.pptx ·
slide script in docs/PITCH_DECK.md.
This is hackathon software - unaudited, and not production-ready:
- The live deployment uses native Arc USDC and Circle's attested CCTP v2 —
nothing on the critical path is mocked.
MockUSDCandMockERC4626remain incontracts/mocks/purely as Foundry test fixtures and are not deployed. RemoteYieldVaultand theReserveYieldAdaptermodel yield from pre-funded reserves rather than a real venue; the Arc-sideERC4626Adapteris real.setGate/setTreasury/setFeeRecipientare one-time, first-caller-wins wiring with no ownership check. The live instance is already wired, but any fresh deployment must be wired in the same transaction batch or an attacker can claim the gate — which is privileged (withdrawFromStreamFor). Production needs an owner, timelocks, and pausability.- Insured cancellation refunds the unvested balance to the employer and records
the same amount as a claimable shortfall, and nothing prevents
employer == recipient. A production build needs that guard plus a funded, underwritten pool. - The relayer is permissionless by design (it only delivers Circle-attested messages) but it is a single hosted process — if it is down, cross-chain transfers are attested and queue until it returns (nothing is lost; deliveries to unfunded chains HOLD rather than fail). Production would run redundant relayers with a dedicated low-privilege gas key rather than the deployer key.
- The treasury's liquidity buffer bounds sweeps, but adapter risk, remote-return latency, and insurance-pool solvency all deserve formal modeling before real funds are involved.
MIT - see LICENSE. Built with Foundry, Circle developer tooling, and
the Arc network.




