Quick Start
Lucy is an immutable OTC escrow protocol. Makers lock tokenA, define the target tokenB amount and acceptance threshold, and contributors supply tokenB liquidity. Once an order meets the minimum fill, the maker releases tokenA to contributors and receives the matched tokenB minus protocol fees.
Contract Address
Base Mainnet deterministic deployment:
Loading...
Supported Chains
Additional networks will appear here once deployed.
Core Functions
createOrder
function createOrder(
address tokenA,
uint256 amountA,
address wantedTokenB,
uint256 wantedAmountB,
uint16 minAcceptBps,
uint40 matchWindowSeconds,
bool allowCrowdFill
) external payable returns (uint256 orderId)
Makers escrow tokenA and define desired terms for wantedTokenB.
tokenA/wantedTokenB: ERC20 address oraddress(0)to settle in native ETH.amountAandwantedAmountBmust each be ≥MIN_AMOUNT(10,000 base units).minAcceptBpsis the minimum fill percentage (betweenMIN_ACCEPT_BPS_FLOORand10000).matchWindowSecondscaps how long contributors can fill the order (1 –MAX_MATCH_WINDOW).allowCrowdFilltoggles multiple contributors; single-fill mode enforces a one-shot full match.- For native
tokenA, sendmsg.value = amountA. ERC20 paths require prior approval. - Transfers account for fee-on-transfer tokens by measuring actual balance deltas.
setOrderWhitelist
function setOrderWhitelist(
uint256 orderId,
address whitelist,
bool whitelistOnly
) external
Makers may restrict contributors before any tokenB is deposited. Orders must still be Open and untouched.
updateOrderTerms
function updateOrderTerms(
uint256 orderId,
uint256 newWantedAmountB,
uint16 newMinAcceptBps
) external
Adjust target tokenB amount or minimum acceptance while the order remains open with zero contributions. New values honor the same floors and caps as creation.
contributeToOrder
function contributeToOrder(
uint256 orderId,
uint256 offeredAmountB
) external payable
Contributors deposit tokenB subject to match window, whitelist rules, and contributor caps.
- Requires
offeredAmountB ≥ MIN_AMOUNTand executes beforematchExpiry. - Single-fill orders must satisfy the minimum acceptance in one call; crowd-fill orders accumulate until the threshold is met.
- New contributors are limited to
MAX_CONTRIBUTORS; makers cannot self-match. - Fee-on-transfer tokens are supported—actual received amounts drive accounting.
release
function release(uint256 orderId) external
Maker-settlement callable once the order is matched and within the release window.
- Distributes net
tokenAproportionally to contributors (largest holder receives the rounding remainder). - Credits maker with
tokenBminus protocol fees. - Accrues protocol fees for both sides to the treasury’s claimable balances.
Cancellation Paths
cancelOrder(uint256 orderId): Maker cancels an open/partial order, refunding all parties.cancelMatch(uint256 orderId): Maker or any contributor unwinds a matched order after the release window expires.cancelExpiredOpen(uint256 orderId): Anyone may unwind an open/partial order once the match window has lapsed.
withdraw
function withdraw(address token) external
Pull claimable balances (native or ERC20). Reverts when nothing is available, preventing gas griefing.
View Helpers
getOrderStatus(orderId)→ state plus match/release expiry flags.getContributors(orderId)/getContributorCount(orderId)→ contributor roster.getContribution(orderId, user)→ tokenB amount supplied by a participant.claimableOf(token, user)→ withdrawable balance.getMinAcceptAmount(orderId)→ calculated minimum acceptance threshold.
Constants & Limits
MIN_AMOUNT: 10,000 base units for both tokenA and tokenB.MIN_ACCEPT_BPS_FLOOR: 5,000 (50% minimum acceptance).MAX_MATCH_WINDOW: 30 days;MAX_RELEASE_WINDOW: 30 days after matching.MAX_CONTRIBUTORS: 20 unique contributors per order.feeBps: Immutable protocol fee (≤ 1000 bps) applied symmetrically to tokenA and tokenB at release.
Integration Guide
Using Ethers.js v6
import { ethers } from 'ethers'
const provider = new ethers.BrowserProvider(window.ethereum)
const signer = await provider.getSigner()
const contract = new ethers.Contract(
LUCY_ADDRESS,
LUCY_ABI,
signer
)
const tokenA = '0x...'
const tokenB = '0x...'
const amountA = ethers.parseUnits('1000', 18)
const wantedAmountB = ethers.parseUnits('500', 6)
const minAcceptBps = 9000 // 90% minimum
const matchWindowSeconds = 3 * 24 * 60 * 60 // 3 days
// Approve tokenA first when using ERC20
const erc20 = new ethers.Contract(tokenA, ERC20_ABI, signer)
await erc20.approve(LUCY_ADDRESS, amountA)
const tx = await contract.createOrder(
tokenA,
amountA,
tokenB,
wantedAmountB,
minAcceptBps,
matchWindowSeconds,
true // allow crowd-fill
)
await tx.wait()
Reading Order State
const order = await contract.orders(orderId)
console.log({
maker: order.maker,
tokenA: order.tokenA,
wantedTokenB: order.wantedTokenB,
amountA: order.amountA,
wantedAmountB: order.wantedAmountB,
minAcceptBps: order.minAcceptBps,
matchExpiry: order.matchExpiry,
releaseExpiry: order.releaseExpiry,
allowCrowdFill: order.allowCrowdFill,
whitelist: order.whitelist,
whitelistOnly: order.whitelistOnly,
totalB: order.totalB,
state: order.state // 0=Open, 1=Partial, 2=Matched, 3=Released, 4=Cancelled
})
Verifying the Eternal Field
The eternal version is hosted on IPFS and pinned to multiple providers.
Access Points
lucyascension.com/eternalipfs.io/ipfs/QmS78vnnjZjrsLvHaVz4qjSYiZ9yjpounF1STTBWjGbAhM(direct)
Verification Steps
- Check the IPFS hash displayed in the eternal version footer
- Compare it to the hash advertised here:
QmS78vnnjZjrsLvHaVz4qjSYiZ9yjpounF1STTBWjGbAhM - Access the content via multiple gateways to verify consistency
- Optionally run your own IPFS node and fetch:
ipfs cat QmXXX
If Everything Fails
In 2030, if all Lucy infrastructure is gone:
- Find the IPFS hash from historical references
- Access via any public IPFS gateway
- Use Etherscan to interact with contract directly
- Run an IPFS node:
ipfs daemonthenipfs get QmXXX
Security Considerations
Immutability
Lucy has no owner, no admin keys, and no upgrade path. The contract cannot be paused, stopped, or modified. Interact at your own risk.
Fee-on-Transfer Tokens
Lucy supports fee-on-transfer tokens by measuring actual received amounts after each transfer.
Reentrancy Protection
All state-changing functions use a custom nonReentrant modifier to prevent reentrancy attacks.
Pull-Payment Pattern
Tokens are not sent automatically. Users must call withdraw() to claim, preventing gas griefing
and external call failures from blocking settlements.