Karah enables ENS name owners (lessors) to lease names to lessees. Lessees can modify name and subname records during their lease period without owning the name. While Konna is a DAO template allowing individual groups of lessees to propose and vote on name changes for a particular co-leased name, acting as a single lessee in Karah.
Version: 0.0.11 (31/10/2025)
License: BSL 1.1 - Peng Protocol 2025
Solidity: ^0.8.2
Manages ENS name leasing, with Karah owning names during active lease terms. Lessees can modify subnodes and records, while lessors retain ultimate control via reclamation.
- Lease struct:
lessor,currentUnitCost,currentToken,agreementCount,active. - LeaseAgreement struct: Per-lease history:
lessee,unitCost,token,totalDuration,startTimestamp,daysWithdrawn,withdrawableAmount,ended.
- ensRegistry, owner,
currentTime,isWarped. - leases:
node => Lease. - agreements:
node => leaseId => LeaseAgreement(historical + active). - leaseCount, leaseNodes, lessorNodes, lesseeNodes.
- nodeTo*Index: O(1) array ops.
- lessorLeases, lesseeLeases:
address => node => bool.
- createLease(node, unitCost, token): Transfers ENS ownership to
Karah, sets terms. - updateLeaseTerms(node, unitCost, token): Updates future terms. Note: Allowed during active lease — affects renewals only.
- subscribe(node, durationDays): Creates new
LeaseAgreement, pulls viatransferFrom, setswithdrawableAmount = cost, uses_now()forstartTimestamp. - renew(node, renewalDays): Extends current agreement, uses current
unitCost/currentToken. Warning: If terms changed mid-lease,withdrawableAmountmay mix tokens (e.g., USDC + DAI). Lessors should avoid token changes during active leases. - endLease(node): Refunds unused days from
withdrawableAmount, marksended. - reclaimName(node): Ends lease + returns ENS ownership to lessor.
- withdraw(node, leaseId): Lessor withdraws earned days from any agreement (active/ended). Uses
daysElapsed = ended ? totalDuration : (current - start)/1dvia_now(). Reverts if called atstartTimestamp(0 days elapsed — correct). - modifyContent(...)*: Requires
daysElapsed < totalDurationvia_now()(strict; lease expires at exact end). - warp(timestamp) / unWarp(): Owner-only time control for testing.
currentTimestores effective time;isWarpedtoggles mode. - getAgreementDetails(node, leaseId): View any historical agreement.
- Views:
viewLeases,getAllLessorLeases,getAllLesseeLeases,getLeaseDetails(active only, uses_now()),getAgreementCount,getActiveLeasesCount.
- subscribe →
_now()→ setsstartTimestamp. - _calculateRefund →
_now()→ computesdaysElapsed. - withdraw →
_now()→ computesdaysElapsed. - modifyContent →
_now()→ checks expiry. - getLeaseDetails →
_now()→ computesdaysLeft.
- Per-Agreement Accounting:
withdrawableAmountis isolated perLeaseAgreement. No cross-contamination. - Renewal Token Mixing: If
updateLeaseTermschanges token, renewal payments use new token.withdrawableAmountaccumulates mixed tokens. Documented risk — lessors must not change token mid-lease. - Withdrawal Edge:
withdrawatcurrentTime == startTimestamp→ 0 days → reverts "Nothing to withdraw" (correct). - Lease Expiry:
modifyContentallows changes for< totalDurationdays (not≤). Standard. - Time Warping:
warp()setscurrentTimeandisWarped=true. All time-sensitive logic uses_now().unWarp()resets toblock.timestamp.
- Security: Pre/post balance checks, isolated funds, safe math.
- Gas: O(1) array ops via index mappings. Three arrays maintained for top-down views.
- Testability: Full time control via
warp()/unWarp()for deterministic testing. - Future (v0.0.12): Consider locking
updateLeaseTermswhileactive == trueto prevent token mixing.
DAO template for lessees to collectively acquire and manage ENS name leases via proposals. Acts as a Karah lessee executing content changes, lease acquisition and termination.
- Proposal struct:
proposer,node,label,subnodeOwner,resolver,ttl,votesFor,expiry(1–26 weeks),executed. - LeaseProposal struct:
totalNeeded,collected,token,durationDays. - proposals:
id => Proposal. - leaseProps:
id => LeaseProposal(for acquisition/termination). - contributions:
id => voter => amount(ERC20 contributed). - voted:
id => voter => voted. - members: DAO members array.
- proposalCount, karah, owner,
currentTime,isWarped.
addMember,removeMember: Owner-managed membership.proposeChange,vote,executeChange: ENS record/subnode updates viaKarah.modifyContent. Uses_now()for expiry.viewProposals(maxIterations): Top-down proposal IDs.
- proposeLease(node, durationDays, expiry): Member proposes lease. Fetches
currentUnitCost,currentTokenfromKarah.getLeaseDetails. SetstotalNeeded. Uses_now()for expiry. - voteLease(id, inFavor, amount): Member votes + contributes ERC20. Caps pull to
totalNeeded - collected. Pre/post balance check. Vote counts once. Uses_now()for expiry. - executeAcquisition(id): Requires quorum + full funding. approves
karahto pulltotalNeeded. Revert-safe. Transfers exacttotalNeededtoKarahby callingsubscribe. Uses_now()for expiry. - cancelStaleAcquisition(id): Post-expiry (via
_now()), refunds all contributions proportionally.
- proposeTermination(node, expiry): Requires
lessee == address(this)viagetLeaseDetails. Uses_now()for expiry. - voteTermination(id, inFavor): Standard vote, no funds. Uses
_now()for expiry. - executeTermination(id): Quorum → calls
Karah.endLease. Measures delta balance (pre/post) to isolate refund. Distributes proportionally via_refundContributions. Uses_now()for expiry.
setKarah,transferOwnership, warp(timestamp), unWarp(): Time control for testing.
- proposeLease →
IKarah.getLeaseDetails(view) →_now()for expiry. - voteLease →
IERC20.transferFrom+ balance checks → updatescollected,contributions→_now()for expiry. - executeAcquisition →
IERC20.approve→IKarah.subscribe→_now()for expiry. - cancelStaleAcquisition →
_now()→_refundContributions(collected). - proposeTermination →
IKarah.getLeaseDetails→_now()for expiry. - executeTermination →
IKarah.endLease→ balance delta →_refundContributions(refund)→_now()for expiry. - _voteBasic →
_now()for expiry. - warp/unWarp → control
currentTime,isWarped.
- isMember: O(n) scan (small DAO).
- _voteBasic: Shared vote logic with
_now(). - _refundContributions(id, amount): Proportional refund:
share = contrib * amount / collected. Clears mapping. - _now(): Returns
currentTimeifisWarped, elseblock.timestamp.
- Fund Isolation:
executeTerminationuses pre/post balance delta — prevents draining other proposals (even same token). - Contribution Capping:
voteLeasepulls only what's needed. - Proportional Refunds: On stale or termination, contributors get fair share of actual refund.
- Quorum:
> members.length / 2(strict majority). - Gas Control:
maxIterationsin views; no unbounded loops. - Security: Pre/post checks on all ERC20 moves. No reentrancy (single external call).
- Flexibility: Supports any ERC20 per lease. DAO can hold multiple leases.
- Testability: Full time control via
warp()/unWarp()for deterministic proposal expiry, acquisition, and termination testing. ownercan be another DAO (DAOception!).