Core contractsCreatorShareOFT
CreatorShareOFT
The tradable LayerZero share token that holders buy and sell, which charges the buy fee and emits lottery entries.
CreatorShareOFT is the ShareOFT () that holders trade on the Base book after the auction settles. It is a LayerZero V2 omnichain fungible token: the wrapper mints it on Base, and LayerZero moves it across the mesh.
At a glance
| Role | Tradable share; takes the buy fee and emits lottery entries on qualifying buys |
| Deployed | Per creator: hub on Base, plus a remote ShareOFT on each mesh chain |
| Standard(s) | ERC-20, LayerZero V2 OFT |
| Key relationships | CreatorOVaultWrapper, CreatorOVault, Gauge controller, LotteryManager, Registry4626 |
How it works
Vault claim vs ShareOFT
The vault claim () is accounting: a claim on what the vault holds. Holders don't trade it on DEXs. ShareOFT () is what they buy and sell. Deposit does not mint ShareOFT; wrapping does.
A deposit through the wrapper still reads as about 1 creator coin to 1 , but the wrap itself is never 1:1. The wrapper locks 1000 per 1 because vault accounting and the DEX use different units. See Token units.
redeems for the creator coin on Base. After a wrap, the wrapper holds the and the holder holds . The wrapper's deposit() does both steps in one call.
Mint backing
Every hub mint checks backing with _assertMintBacking: the wrapper's balance must be at least local totalSupply × VAULT_SHARE_NORMALIZATION (1000), or the mint reverts UnbackedShareMint. The check is skipped when the wrapper or vault is unset. LayerZero's _credit on bridge receipt is not this mint.
Qualifying buys
A qualifying DEX buy of ShareOFT pays a transfer fee of 6.9% by default (buyFeeBps = 690). A buy is a transfer from a SwapOnly address to a non-SwapOnly address.
- The fee goes to the per-creator gauge.
- If the hub gauge's
receiveFeesreverts, the fee waits in ShareOFTpendingFeesuntil anyone callsflushPendingFeesToGauge. - The same transfer may emit a lottery entry for the recipient.
The fee split and lottery odds are on How it works.
DEX aggregators (1inch, Paraswap, LlamaSwap, CoW Swap, Uniswap Universal Router) are SwapOnly. The recipient gets the lottery entry, not the aggregator. Same-type hops and NoFees / Unknown addresses pay no trade fee.
On Base (the hub), entries go to LotteryManager in the same transaction. On remote chains, buys queue via lotteryQueue.queueFromOft, and the buyer later calls lotteryQueue.submitPendingLotteryEntry and pays the LayerZero fee.
LayerZero send moves ShareOFT across the mesh with no buy fee and no lottery entry. Remote chains hold ShareOFT only, with no local vault redeem. See Solana share bridge.
Key functions
Transfers and bridging
| Function | What it does | Who can call |
|---|---|---|
send(sendParam, fee, refundAddress) | Bridge ShareOFT over LayerZero (inherited from OFT) | Holder |
quoteSend(sendParam, payInLzToken) | Quote the LayerZero fee for send | Anyone |
flushPendingFeesToGauge() | Retry sending pendingFees to the gauge | Anyone, hub only |
Configuration
| Function | What it does | Who can call |
|---|---|---|
setAddressType(addr, opType) | Mark an address Unknown, SwapOnly, or NoFees | Owner |
setBuyFee(feeBps) | Change the buy fee (default 690, max 1000 / MAX_FEE_BPS) | Owner |
setFeesEnabled(enabled) / setLotteryEnabled(enabled) | Toggle fees or lottery entries | Owner |
setGaugeController(controller) | Set the fee recipient gauge | Owner |
setLotteryResolver(resolver, allowed) | Allowlist an ILotteryBeneficiary resolver | Owner |
setWrapper(wrapper) | Set the minting wrapper | Owner |
setPeer(eid, peer) / setHubConfig(isHub, hubEid, hubGaugeReceiver) / setHubLotteryPeer(hubEid, hubLotteryPeer) | LayerZero mesh wiring | Owner, or remoteProtocolWireAuthority off Base |
Lottery queue (remote chains)
lotteryQueue is a ShareOFTRemoteLotteryQueue created in the constructor. Pending-entry calls live on the queue, not on ShareOFT.
| Function | What it does | Who can call |
|---|---|---|
queueFromOft(buyer, amount, coverageBalance, venueUsd) | Record a pending entry | The ShareOFT |
quotePendingLotteryEntry(entryId) | Quote the LayerZero fee to submit an entry | Anyone |
submitPendingLotteryEntry(entryId) | Send the entry to the hub before it expires | The buyer only (NotPendingLotteryEntryOwner) |
quoteLottery(payload, options) / sendLottery(...) | ShareOFT-side LayerZero send for entries | sendLottery is queue only (OnlyLotteryQueue) |
Gotchas
- The wrap is 1000 per 1 . Never treat and as 1:1.
- Wraps, deposits, and bridge receipts do not pay the buy fee and do not enter the lottery.
- Sell-side fees need an enabled tax hook aligned to the same
tradeFeeCollector. Don't assume sell fees are on without checking that hook onchain. setBuyFeeis mutable. ReadbuyFeeBpsonchain instead of hardcoding 6.9%.- Remote pending entries expire after 7 days (
PENDING_LOTTERY_ENTRY_TTL), and each buyer can hold at most 16 (MAX_PENDING_LOTTERY_ENTRIES_PER_BUYER); extra buys queue no entry. lotteryQueueis immutable and has no setter. The queue storesshareOftas immutable too.setLotteryResolveronly affects allowlistedILotteryBeneficiarycontracts. Unknown contracts and EOAs keep the recipient as the beneficiary.setHubLotteryPeerrequiressetHubConfigfirst, with a non-zerohubGaugeReceiver.flushPendingFeesToGaugerevertsNotHuboff Base andNothingToFlushwhen empty. Remote pending fees bridge throughspokeModule.flushFees, not this call.setWrapper(address(0))revertsWrapperRequiredWhileSupplyExistswhile supply remains, since that would disable mint-backing checks.
Emergency
To disable the lottery on an affected remote ShareOFT, run from the repo root:
forge script script/EmergencyDisableRemoteLottery.s.sol:EmergencyDisableRemoteLottery \
--rpc-url $RPC_URL --broadcast -vvvv
Required env: PRIVATE_KEY (ShareOFT owner) and SHARE_OFT (the remote ShareOFT address).