| > Ohm4Token |
Reserve currency · fixed supply, no owner, no mint |
ETHEREUM |
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Ohm4Treasury} from "./Ohm4Treasury.sol";
import {Ohm4PersonaRegistry} from "./Ohm4PersonaRegistry.sol";
import {Ohm4CredentialLedger} from "./Ohm4CredentialLedger.sol";
import {Ohm4Staking} from "./Ohm4Staking.sol";
import {Ohm4BondDepository} from "./Ohm4BondDepository.sol";
import {Ohm4RangeGuard} from "./Ohm4RangeGuard.sol";
import {Ohm4ReputationCurve} from "./Ohm4ReputationCurve.sol";
import {Ohm4Hook} from "./Ohm4Hook.sol";
/*
+------+ +------+ +---------------+
| SWAP |---->| HOOK |--+->| RANGE GUARD |
+------+ +------+ | +---------------+
|
| +---------------+
+->| CREDENTIALS |
+---------------+
// ---
// file: ohm4
// contract: Ohm4Token
// role: reserve currency
// mint: disabled
// owner: none
// network: ethereum
// imports: [treasury, persona-registry, credential-ledger, staking, bond-depository,
// range-guard, reputation-curve, hook]
// ---
//
// summary: >
// OHM4 crosses two things this project family has built separately before: a
// treasury-backed reserve currency in the shape Olympus DAO made familiar, and an identity
// layer where personas and credentials are self-declared rather than gatekept. the crossing
// point is earned credentials - a persona doesn't get to claim "staked for a month" the way
// identity.md let anyone claim anything about anyone. here, that credential only exists once
// the staking contract actually records a month of real activity behind it. the persona is
// still unverified in the sense that nobody checks who's behind the address; what's no longer
// unverified is whether the activity a credential describes actually happened on-chain.
//
// policy: >
// NO EMISSIONS. NO REBASES. NO INFLATION. the supply fixed at deployment is the only supply
// that will ever exist. staking pays yield out of what the treasury holds, denominated
// separately from the token itself; bonding only ever redistributes supply that already
// existed at genesis. nothing here mints.
//
// note: >
// this file is the currency itself - a fixed-supply, ownerless ERC-20. it does not hold
// reserves, mint personas, issue credentials, or run the range guard; that machinery lives in
// the eight contracts imported above, mapped here but never called from this file.
*/
contract Ohm4Token {
string public constant name = "ohm4";
string public constant symbol = "OHM4";
uint8 public constant decimals = 18;
uint256 public immutable genesisSupply;
uint256 private circulating;
uint256 public immutable deployedAtHeight;
uint256 public immutable deployedAt;
mapping(address => uint256) private balances;
mapping(address => mapping(address => uint256)) private allowances;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
constructor() {
uint256 supply = 4_000_000 * 10 ** decimals;
genesisSupply = supply;
circulating = supply;
balances[msg.sender] = supply;
deployedAtHeight = block.number;
deployedAt = block.timestamp;
emit Transfer(address(0), msg.sender, supply);
}
// ---------------------------------------------------------------
// standard erc20
// ---------------------------------------------------------------
function totalSupply() external view returns (uint256) {
return circulating;
}
function balanceOf(address account) public view returns (uint256) {
return balances[account];
}
function allowance(address tokenOwner, address spender) external view returns (uint256) {
return allowances[tokenOwner][spender];
}
function transfer(address to, uint256 value) external returns (bool) {
_transfer(msg.sender, to, value);
return true;
}
function approve(address spender, uint256 value) external returns (bool) {
_approve(msg.sender, spender, value);
return true;
}
function transferFrom(address from, address to, uint256 value) external returns (bool) {
_spendAllowance(from, msg.sender, value);
_transfer(from, to, value);
return true;
}
function increaseAllowance(address spender, uint256 addedValue) external returns (bool) {
_approve(msg.sender, spender, allowances[msg.sender][spender] + addedValue);
return true;
}
function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool) {
uint256 current = allowances[msg.sender][spender];
require(current >= subtractedValue, "below zero");
_approve(msg.sender, spender, current - subtractedValue);
return true;
}
function burn(uint256 value) external {
_burn(msg.sender, value);
}
function burnFrom(address account, uint256 value) external {
_spendAllowance(account, msg.sender, value);
_burn(account, value);
}
function _transfer(address from, address to, uint256 value) private {
require(to != address(0), "transfer to zero");
uint256 fromBal = balances[from];
require(fromBal >= value, "balance too low");
unchecked {
balances[from] = fromBal - value;
}
balances[to] += value;
emit Transfer(from, to, value);
}
function _approve(address tokenOwner, address spender, uint256 value) private {
allowances[tokenOwner][spender] = value;
emit Approval(tokenOwner, spender, value);
}
function _spendAllowance(address tokenOwner, address spender, uint256 value) private {
uint256 allowed = allowances[tokenOwner][spender];
if (allowed != type(uint256).max) {
require(allowed >= value, "allowance too low");
unchecked {
allowances[tokenOwner][spender] = allowed - value;
}
}
}
function _burn(address account, uint256 value) private {
uint256 bal = balances[account];
require(bal >= value, "balance too low");
unchecked {
balances[account] = bal - value;
circulating -= value;
}
emit Transfer(account, address(0), value);
}
// ---------------------------------------------------------------
// view functions
// ---------------------------------------------------------------
function reserveSupply() external view returns (uint256) {
return circulating;
}
function retiredSupply() external view returns (uint256) {
return genesisSupply - circulating;
}
function retiredBps() external view returns (uint256) {
if (genesisSupply == 0) return 0;
return ((genesisSupply - circulating) * 10000) / genesisSupply;
}
function mintCapacity() external pure returns (uint256) {
return 0;
}
function holdsClaim(address account) external view returns (bool) {
return balances[account] > 0;
}
function claimShareBps(address account) external view returns (uint256) {
if (circulating == 0) return 0;
return (balances[account] * 10000) / circulating;
}
function delegatedAmount(address tokenOwner, address spender) external view returns (uint256) {
return allowances[tokenOwner][spender];
}
function hasOpenDelegation(address tokenOwner, address spender) external view returns (bool) {
return allowances[tokenOwner][spender] == type(uint256).max;
}
function secondsSinceGenesis() external view returns (uint256) {
return block.timestamp - deployedAt;
}
function heightSinceGenesis() external view returns (uint256) {
return block.number - deployedAtHeight;
}
function genesisRecord()
external
view
returns (address contractAddress, uint256 atHeight, uint256 atTime, uint256 chainId)
{
return (address(this), deployedAtHeight, deployedAt, block.chainid);
}
function hasNoTreasurer() external pure returns (bool) {
return true; // no owner or operator variable exists in this contract, this just says so on-chain
}
function reserveSummary() external view returns (string memory, string memory, uint8, uint256) {
return (name, symbol, decimals, circulating);
}
// whole units held, discarding anything below one token - a
// coarser read than balanceOf for a quick glance.
function wholeUnitsHeld(address account) external view returns (uint256) {
return balances[account] / (10 ** decimals);
}
// a slice small enough to round to zero at current scale.
function backingFloor() external view returns (uint256) {
return circulating / 10000;
}
// purely a self-identifying flag, confirms this is in fact the
// OHM4 currency contract without checking name/symbol strings.
function isOhm4() external pure returns (bool) {
return true;
}
}
|
| > Ohm4Treasury |
Multi-asset reserve ledger |
ETHEREUM |
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/*
// ---
// file: ohm4
// contract: Ohm4Treasury
// role: multi-asset reserve ledger
// gate: none
// ---
//
// summary: >
// a treasury backing a single reserve currency rarely holds just one kind of asset. this
// tracks several distinct reserve types at once, each with its own recorded value, and
// totals them into a single backing figure rather than assuming everything is denominated
// the same way. a deposit updates one asset's line without disturbing the others; the total
// is always the sum of whatever's currently on the books across every asset type that's ever
// been touched.
//
// access: >
// depositing into an asset line and adjusting the bond pool allocation are both
// permissionless. this tracks value, it doesn't custody anything - pair it with real asset
// transfers wherever reserves actually need to move.
*/
contract Ohm4Treasury {
mapping(bytes32 => uint256) public reserveValueOf;
bytes32[] private assetTypes;
mapping(bytes32 => bool) private isKnownAsset;
uint256 public bondPoolAllocation;
uint256 public bondPoolDistributed;
event ReserveDeposited(bytes32 indexed assetType, uint256 amount, uint256 newValue);
event BondPoolFunded(uint256 amount, uint256 newAllocation);
event BondPoolDrawn(uint256 amount, uint256 remaining);
function depositReserve(bytes32 assetType, uint256 amount) external {
if (!isKnownAsset[assetType]) {
isKnownAsset[assetType] = true;
assetTypes.push(assetType);
}
reserveValueOf[assetType] += amount;
emit ReserveDeposited(assetType, amount, reserveValueOf[assetType]);
}
function fundBondPool(uint256 amount) external {
bondPoolAllocation += amount;
emit BondPoolFunded(amount, bondPoolAllocation);
}
// draws from the bond pool allocation, capped at whatever's still
// undistributed. never reverts on an oversized request.
function drawFromBondPool(uint256 amount) external returns (uint256 actuallyDrawn) {
uint256 remaining = bondPoolAllocation > bondPoolDistributed ? bondPoolAllocation - bondPoolDistributed : 0;
actuallyDrawn = amount > remaining ? remaining : amount;
bondPoolDistributed += actuallyDrawn;
emit BondPoolDrawn(actuallyDrawn, bondPoolAllocation - bondPoolDistributed);
}
function totalReserveValue() external view returns (uint256 total) {
for (uint256 i = 0; i < assetTypes.length; i++) {
total += reserveValueOf[assetTypes[i]];
}
}
function assetTypeCount() external view returns (uint256) {
return assetTypes.length;
}
function assetTypeAt(uint256 index) external view returns (bytes32) {
if (index >= assetTypes.length) return bytes32(0);
return assetTypes[index];
}
function valueOf(bytes32 assetType) external view returns (uint256) {
return reserveValueOf[assetType];
}
function shareOfTotalBps(bytes32 assetType) external view returns (uint256) {
uint256 total;
for (uint256 i = 0; i < assetTypes.length; i++) {
total += reserveValueOf[assetTypes[i]];
}
if (total == 0) return 0;
return (reserveValueOf[assetType] * 10000) / total;
}
function backingPerTokenScaled(uint256 tokenSupply) external view returns (uint256) {
if (tokenSupply == 0) return 0;
uint256 total;
for (uint256 i = 0; i < assetTypes.length; i++) {
total += reserveValueOf[assetTypes[i]];
}
return (total * 1e18) / tokenSupply;
}
function bondPoolRemaining() external view returns (uint256) {
return bondPoolAllocation > bondPoolDistributed ? bondPoolAllocation - bondPoolDistributed : 0;
}
function bondPoolUtilizationBps() external view returns (uint256) {
if (bondPoolAllocation == 0) return 0;
uint256 capped = bondPoolDistributed > bondPoolAllocation ? bondPoolAllocation : bondPoolDistributed;
return (capped * 10000) / bondPoolAllocation;
}
}
|
| > Ohm4PersonaRegistry |
One primary persona per address |
ETHEREUM |
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/*
// ---
// file: ohm4
// contract: Ohm4PersonaRegistry
// role: identity layer, primary-persona binding
// gate: none
// ---
//
// summary: >
// identity.md let one address forge any number of unrelated personas at once. this narrows
// that back down on purpose: an address can still mint several personas, but only one is ever
// "primary" - the one every other contract in this system reads when it needs to know who's
// behind an address. switching primaries is instant and free; the personas left behind aren't
// deleted, they're just not the one credentials and reputation currently attach to.
//
// access: >
// minting a persona and switching which one is primary are both permissionless.
*/
contract Ohm4PersonaRegistry {
struct Persona {
address owner;
string displayName;
uint256 mintedAt;
}
Persona[] private personas;
mapping(address => uint256[]) private personasByOwner;
mapping(address => uint256) public primaryPersonaOf;
mapping(address => bool) public hasPrimary;
event PersonaMinted(uint256 indexed personaId, address indexed owner, string displayName);
event PrimarySet(address indexed owner, uint256 indexed personaId);
function mintPersona(string calldata displayName) external returns (uint256 personaId) {
personas.push(Persona({owner: msg.sender, displayName: displayName, mintedAt: block.timestamp}));
personaId = personas.length - 1;
personasByOwner[msg.sender].push(personaId);
if (!hasPrimary[msg.sender]) {
primaryPersonaOf[msg.sender] = personaId;
hasPrimary[msg.sender] = true;
emit PrimarySet(msg.sender, personaId);
}
emit PersonaMinted(personaId, msg.sender, displayName);
}
// switches the caller's primary persona to one they already own.
// switching to a persona owned by someone else, or one that
// doesn't exist, is a no-op.
function setPrimary(uint256 personaId) external {
if (personaId >= personas.length) return;
if (personas[personaId].owner != msg.sender) return;
primaryPersonaOf[msg.sender] = personaId;
hasPrimary[msg.sender] = true;
emit PrimarySet(msg.sender, personaId);
}
function personaCount() external view returns (uint256) {
return personas.length;
}
function getPersona(uint256 personaId) external view returns (Persona memory) {
if (personaId >= personas.length) {
return Persona({owner: address(0), displayName: "", mintedAt: 0});
}
return personas[personaId];
}
function personasOf(address owner) external view returns (uint256[] memory) {
return personasByOwner[owner];
}
function personaCountOf(address owner) external view returns (uint256) {
return personasByOwner[owner].length;
}
function primaryOf(address owner) external view returns (uint256 personaId, bool exists) {
return (primaryPersonaOf[owner], hasPrimary[owner]);
}
function ownerOf(uint256 personaId) external view returns (address) {
if (personaId >= personas.length) return address(0);
return personas[personaId].owner;
}
function ageOf(uint256 personaId) external view returns (uint256) {
if (personaId >= personas.length) return 0;
return block.timestamp - personas[personaId].mintedAt;
}
function isPrimary(address owner, uint256 personaId) external view returns (bool) {
return hasPrimary[owner] && primaryPersonaOf[owner] == personaId;
}
}
|
| > Ohm4CredentialLedger |
Earned, not claimed |
ETHEREUM |
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/*
// ---
// file: ohm4
// contract: Ohm4CredentialLedger
// role: identity layer, earned-activity credentials
// gate: caller-attested activity (see note)
// ---
//
// summary: >
// identity.md's credential mill let anyone mint any claim about any persona. this is the
// opposite instinct: a credential here is only supposed to land once a category of real
// activity has actually happened - a stake held past a duration, a bond completed, a range
// intervention called first. the ledger itself has no way to independently verify that a
// caller's claim is true; it trusts whatever contract is wired to call issueCredential for a
// given category. what's different from identity.md isn't a cryptographic guarantee, it's the
// intent: this is meant to be called by the system's own contracts recording their own
// outcomes, not by an arbitrary address minting a claim about a stranger.
//
// access: >
// issuing a credential is technically open to any caller, same as everything else in this
// family - what makes a credential meaningful here is which contract is actually calling it
// in a real deployment, not a permission check this contract enforces itself.
*/
contract Ohm4CredentialLedger {
struct Credential {
uint256 personaId;
bytes32 category;
uint256 issuedAt;
address issuedBy;
}
Credential[] private credentials;
mapping(uint256 => uint256[]) private credentialsForPersona;
mapping(uint256 => mapping(bytes32 => uint256)) public categoryCountFor;
event CredentialIssued(uint256 indexed credentialId, uint256 indexed personaId, bytes32 category);
function issueCredential(uint256 personaId, bytes32 category) external returns (uint256 credentialId) {
credentials.push(Credential({personaId: personaId, category: category, issuedAt: block.timestamp, issuedBy: msg.sender}));
credentialId = credentials.length - 1;
credentialsForPersona[personaId].push(credentialId);
unchecked {
categoryCountFor[personaId][category] += 1;
}
emit CredentialIssued(credentialId, personaId, category);
}
function credentialCount() external view returns (uint256) {
return credentials.length;
}
function getCredential(uint256 credentialId) external view returns (Credential memory) {
if (credentialId >= credentials.length) {
return Credential({personaId: 0, category: bytes32(0), issuedAt: 0, issuedBy: address(0)});
}
return credentials[credentialId];
}
function credentialsFor(uint256 personaId) external view returns (uint256[] memory) {
return credentialsForPersona[personaId];
}
function totalCredentialsFor(uint256 personaId) external view returns (uint256) {
return credentialsForPersona[personaId].length;
}
function countInCategory(uint256 personaId, bytes32 category) external view returns (uint256) {
return categoryCountFor[personaId][category];
}
function hasAnyInCategory(uint256 personaId, bytes32 category) external view returns (bool) {
return categoryCountFor[personaId][category] > 0;
}
function distinctCategoryCount(uint256 personaId, bytes32[] calldata categoriesToCheck) external view returns (uint256 count) {
for (uint256 i = 0; i < categoriesToCheck.length; i++) {
if (categoryCountFor[personaId][categoriesToCheck[i]] > 0) count++;
}
}
function issuedByFor(uint256 credentialId) external view returns (address) {
if (credentialId >= credentials.length) return address(0);
return credentials[credentialId].issuedBy;
}
function timeSinceIssued(uint256 credentialId) external view returns (uint256) {
if (credentialId >= credentials.length) return 0;
return block.timestamp - credentials[credentialId].issuedAt;
}
}
|
| > Ohm4Staking |
Yield vouchers, no rebase |
ETHEREUM |
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/*
// ---
// file: ohm4
// contract: Ohm4Staking
// role: staking, identity-linked yield rate
// gate: none
// ---
//
// summary: >
// a stake earns yield vouchers - a claim on the treasury's real reserves, never a change to
// the staker's token balance - the same non-rebasing approach this project family has used
// before. what's new is where the rate comes from: instead of a flat number set once, the
// voucher rate scales with however many credentials the staker's linked persona is carrying
// when the stake is opened. more earned activity behind a persona means a better rate for
// whatever gets staked under it - the identity layer isn't just decoration sitting next to
// the reserve currency, it changes the economics.
//
// access: >
// staking, unstaking, and cashing vouchers are all permissionless.
*/
contract Ohm4Staking {
uint256 public constant BASE_RATE_PER_SECOND = 1;
uint256 public constant RATE_PER_CREDENTIAL = 1;
struct Stake {
uint256 amount;
uint256 stakedAt;
uint256 ratePerSecond;
uint256 lastAccrualAt;
uint256 uncashedVouchers;
uint256 totalCashedVouchers;
}
mapping(address => Stake) private stakes;
uint256 public totalStaked;
uint256 public redemptionBudget;
uint256 public redemptionDistributed;
event Staked(address indexed staker, uint256 amount, uint256 ratePerSecond, uint256 newTotal);
event Unstaked(address indexed staker, uint256 amount, uint256 remaining);
event VouchersAccrued(address indexed staker, uint256 amount, uint256 newUncashed);
event VouchersCashed(address indexed staker, uint256 requested, uint256 actuallyCashed);
event RedemptionBudgetFunded(uint256 amount, uint256 newBudget);
function _accrue(address staker) internal {
Stake storage s = stakes[staker];
if (s.amount == 0 || s.lastAccrualAt == 0) {
s.lastAccrualAt = block.timestamp;
return;
}
uint256 elapsed = block.timestamp - s.lastAccrualAt;
if (elapsed == 0) return;
uint256 accrued = elapsed * s.ratePerSecond;
s.uncashedVouchers += accrued;
s.lastAccrualAt = block.timestamp;
emit VouchersAccrued(staker, accrued, s.uncashedVouchers);
}
// stakes an amount, with the rate for this stake set from the
// caller-supplied credential count - a real deployment reads that
// count off the credential ledger for the staker's primary
// persona before calling this.
function stake(uint256 amount, uint256 personaCredentialCount) external {
_accrue(msg.sender);
Stake storage s = stakes[msg.sender];
s.amount += amount;
s.ratePerSecond = BASE_RATE_PER_SECOND + (personaCredentialCount * RATE_PER_CREDENTIAL);
if (s.stakedAt == 0) {
s.stakedAt = block.timestamp;
}
totalStaked += amount;
emit Staked(msg.sender, amount, s.ratePerSecond, s.amount);
}
// unstakes an amount, capped at what's actually staked. accrues
// pending vouchers first so nothing is lost in the process.
function unstake(uint256 amount) external returns (uint256 actuallyUnstaked) {
_accrue(msg.sender);
Stake storage s = stakes[msg.sender];
actuallyUnstaked = amount > s.amount ? s.amount : amount;
s.amount -= actuallyUnstaked;
totalStaked -= actuallyUnstaked;
emit Unstaked(msg.sender, actuallyUnstaked, s.amount);
}
function fundRedemptionBudget(uint256 amount) external {
redemptionBudget += amount;
emit RedemptionBudgetFunded(amount, redemptionBudget);
}
// cashes uncashed vouchers, capped at both what the staker actually
// holds and what the redemption budget can still cover.
function cashVouchers(uint256 amount) external returns (uint256 actuallyCashed) {
_accrue(msg.sender);
Stake storage s = stakes[msg.sender];
uint256 budgetRemaining = redemptionBudget > redemptionDistributed ? redemptionBudget - redemptionDistributed : 0;
uint256 cap = amount > s.uncashedVouchers ? s.uncashedVouchers : amount;
actuallyCashed = cap > budgetRemaining ? budgetRemaining : cap;
s.uncashedVouchers -= actuallyCashed;
unchecked {
s.totalCashedVouchers += actuallyCashed;
redemptionDistributed += actuallyCashed;
}
emit VouchersCashed(msg.sender, amount, actuallyCashed);
}
function stakedAmountOf(address staker) external view returns (uint256) {
return stakes[staker].amount;
}
function pendingVouchers(address staker) external view returns (uint256) {
Stake memory s = stakes[staker];
if (s.amount == 0 || s.lastAccrualAt == 0) return s.uncashedVouchers;
uint256 elapsed = block.timestamp - s.lastAccrualAt;
return s.uncashedVouchers + (elapsed * s.ratePerSecond);
}
function totalCashedBy(address staker) external view returns (uint256) {
return stakes[staker].totalCashedVouchers;
}
function redemptionBudgetRemaining() external view returns (uint256) {
return redemptionBudget > redemptionDistributed ? redemptionBudget - redemptionDistributed : 0;
}
function timeStaked(address staker) external view returns (uint256) {
Stake memory s = stakes[staker];
if (s.stakedAt == 0) return 0;
return block.timestamp - s.stakedAt;
}
function rateOf(address staker) external view returns (uint256) {
return stakes[staker].ratePerSecond;
}
}
|
| > Ohm4BondDepository |
Redistributes genesis supply only |
ETHEREUM |
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/*
// ---
// file: ohm4
// contract: Ohm4BondDepository
// role: bonding, credential trigger on completion
// gate: none
// ---
//
// summary: >
// a bond here does what bonds in this project family always do - exchange reserve value for
// token supply that already existed at genesis, vesting linearly, nothing minted. what's
// attached this time is a flag: once a bond finishes vesting, it's marked eligible for a
// credential, and the credential ledger can be told to issue one against the bonder's
// persona. the flag only fires once per bond, and only once the bond has actually fully
// vested - a bond claimed early carries no credential eligibility yet.
//
// access: >
// opening a bond, claiming vested tokens, and checking credential eligibility are all
// permissionless.
*/
contract Ohm4BondDepository {
uint256 public constant VESTING_PERIOD = 5 days;
bytes32 public constant BOND_COMPLETED_CATEGORY = keccak256("BOND_COMPLETED");
struct Bond {
address bonder;
uint256 personaId;
uint256 payout;
uint256 claimed;
uint256 startedAt;
bool credentialFlagged;
}
Bond[] private bonds;
event BondOpened(uint256 indexed bondId, address indexed bonder, uint256 personaId, uint256 payout);
event Claimed(uint256 indexed bondId, uint256 amount, uint256 totalClaimed);
event CredentialEligible(uint256 indexed bondId, uint256 indexed personaId);
function openBond(address bonder, uint256 personaId, uint256 payout) external returns (uint256 bondId) {
bonds.push(
Bond({bonder: bonder, personaId: personaId, payout: payout, claimed: 0, startedAt: block.timestamp, credentialFlagged: false})
);
bondId = bonds.length - 1;
emit BondOpened(bondId, bonder, personaId, payout);
}
// claims whatever portion of a bond has vested so far, capped at
// the bond's total payout. once the bond is fully vested for the
// first time, this also flags it as credential-eligible.
function claim(uint256 bondId) external returns (uint256 claimable) {
if (bondId >= bonds.length) return 0;
Bond storage b = bonds[bondId];
uint256 elapsed = block.timestamp - b.startedAt;
bool fullyVested = elapsed >= VESTING_PERIOD;
uint256 vested = fullyVested ? b.payout : (b.payout * elapsed) / VESTING_PERIOD;
claimable = vested > b.claimed ? vested - b.claimed : 0;
b.claimed += claimable;
emit Claimed(bondId, claimable, b.claimed);
if (fullyVested && !b.credentialFlagged) {
b.credentialFlagged = true;
emit CredentialEligible(bondId, b.personaId);
}
}
function bondCount() external view returns (uint256) {
return bonds.length;
}
function getBond(uint256 bondId) external view returns (Bond memory) {
if (bondId >= bonds.length) {
return Bond({bonder: address(0), personaId: 0, payout: 0, claimed: 0, startedAt: 0, credentialFlagged: false});
}
return bonds[bondId];
}
function vestedAmount(uint256 bondId) external view returns (uint256) {
if (bondId >= bonds.length) return 0;
Bond memory b = bonds[bondId];
uint256 elapsed = block.timestamp - b.startedAt;
return elapsed >= VESTING_PERIOD ? b.payout : (b.payout * elapsed) / VESTING_PERIOD;
}
function claimableNow(uint256 bondId) external view returns (uint256) {
if (bondId >= bonds.length) return 0;
Bond memory b = bonds[bondId];
uint256 elapsed = block.timestamp - b.startedAt;
uint256 vested = elapsed >= VESTING_PERIOD ? b.payout : (b.payout * elapsed) / VESTING_PERIOD;
return vested > b.claimed ? vested - b.claimed : 0;
}
function isFullyVested(uint256 bondId) external view returns (bool) {
if (bondId >= bonds.length) return false;
return block.timestamp - bonds[bondId].startedAt >= VESTING_PERIOD;
}
function isCredentialFlagged(uint256 bondId) external view returns (bool) {
if (bondId >= bonds.length) return false;
return bonds[bondId].credentialFlagged;
}
function personaBehind(uint256 bondId) external view returns (uint256) {
if (bondId >= bonds.length) return 0;
return bonds[bondId].personaId;
}
}
|
| > Ohm4RangeGuard |
Floor / ceiling price defense |
ETHEREUM |
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/*
// ---
// file: ohm4
// contract: Ohm4RangeGuard
// role: price band defense, first-responder tracking
// gate: none
// ---
//
// summary: >
// a floor the treasury defends by buying, a ceiling it defends by selling - the same
// range-bound approach this project family has used before. what's added here is a record of
// who actually reported the price the moment it first crossed outside the band on a given
// side. that address's linked persona gets marked as the first responder for that
// intervention window, purely for the record - it doesn't grant anything by itself, but a
// persona with a long history of catching interventions first is a different kind of
// credential candidate than one that's never shown up.
//
// access: >
// setting the band and reporting price are both permissionless.
*/
contract Ohm4RangeGuard {
uint256 public floorPrice;
uint256 public ceilingPrice;
uint256 public lastPrice;
uint256 public lastReportedAt;
bool public interventionActive;
bool public activeIsBuySide;
uint256 public activePersonaId;
address public activeReporter;
uint256 public buySideFlags;
uint256 public sellSideFlags;
mapping(uint256 => uint256) public firstResponseCountFor;
event BandSet(uint256 floorPrice, uint256 ceilingPrice);
event PriceReported(uint256 price, bool belowFloor, bool aboveCeiling);
event InterventionOpened(bool buySide, uint256 indexed personaId, address reporter);
event InterventionCleared(bool buySide);
function setBand(uint256 newFloorPrice, uint256 newCeilingPrice) external {
floorPrice = newFloorPrice;
ceilingPrice = newCeilingPrice;
emit BandSet(newFloorPrice, newCeilingPrice);
}
// reports a fresh price alongside the reporting persona. if this
// is the first out-of-band reading since the last time price was
// in-band, the reporting persona is credited as the first
// responder for this intervention window.
function reportPrice(uint256 price, uint256 personaId) external returns (bool belowFloor, bool aboveCeiling) {
belowFloor = price <= floorPrice && floorPrice != 0;
aboveCeiling = price >= ceilingPrice && ceilingPrice != 0;
lastPrice = price;
lastReportedAt = block.timestamp;
emit PriceReported(price, belowFloor, aboveCeiling);
if ((belowFloor || aboveCeiling) && !interventionActive) {
interventionActive = true;
activeIsBuySide = belowFloor;
activePersonaId = personaId;
activeReporter = msg.sender;
unchecked {
firstResponseCountFor[personaId] += 1;
if (belowFloor) {
buySideFlags += 1;
} else {
sellSideFlags += 1;
}
}
emit InterventionOpened(belowFloor, personaId, msg.sender);
} else if (!belowFloor && !aboveCeiling && interventionActive) {
interventionActive = false;
emit InterventionCleared(activeIsBuySide);
}
}
function isWithinBand(uint256 price) external view returns (bool) {
if (floorPrice == 0 && ceilingPrice == 0) return true;
return price > floorPrice && price < ceilingPrice;
}
function bandWidth() external view returns (uint256) {
if (ceilingPrice <= floorPrice) return 0;
return ceilingPrice - floorPrice;
}
function currentIntervention() external view returns (bool active, bool buySide, uint256 personaId, address reporter) {
return (interventionActive, activeIsBuySide, activePersonaId, activeReporter);
}
function firstResponsesBy(uint256 personaId) external view returns (uint256) {
return firstResponseCountFor[personaId];
}
function timeSinceLastReport() external view returns (uint256) {
if (lastReportedAt == 0) return 0;
return block.timestamp - lastReportedAt;
}
function distanceToFloor(uint256 price) external view returns (uint256) {
if (price <= floorPrice) return 0;
return price - floorPrice;
}
function distanceToCeiling(uint256 price) external view returns (uint256) {
if (price >= ceilingPrice) return 0;
return ceilingPrice - price;
}
}
|
| > Ohm4ReputationCurve |
Score halves every 14 days |
ETHEREUM |
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/*
// ---
// file: ohm4
// contract: Ohm4ReputationCurve
// role: identity layer, decaying reputation score
// gate: none
// ---
//
// summary: >
// a credential earned once and never touched again shouldn't carry the same weight forever.
// this turns a persona's raw credential count into a reputation score that halves on a fixed
// period unless it's topped up - each top-up folds in whatever fresh activity has happened
// since the last one, but a persona that goes quiet watches its score decay toward zero
// rather than sitting frozen at whatever peak it once reached. this is reputation about
// recent standing, not a permanent trophy case.
//
// access: >
// topping up a persona's reputation is permissionless.
*/
contract Ohm4ReputationCurve {
uint256 public constant HALF_LIFE = 14 days;
struct Reputation {
uint256 score;
uint256 lastToppedUpAt;
}
mapping(uint256 => Reputation) private reputations;
event ReputationToppedUp(uint256 indexed personaId, uint256 addedScore, uint256 newScore);
function _decayed(Reputation memory r) internal view returns (uint256) {
if (r.lastToppedUpAt == 0) return 0;
uint256 elapsed = block.timestamp - r.lastToppedUpAt;
uint256 halvings = elapsed / HALF_LIFE;
if (halvings >= 32) return 0;
return r.score >> halvings;
}
// tops up a persona's reputation with fresh score, folding in
// whatever decay had already accrued before adding the new amount.
function topUpReputation(uint256 personaId, uint256 addedScore) external returns (uint256 newScore) {
Reputation storage r = reputations[personaId];
uint256 decayed = _decayed(r);
newScore = decayed + addedScore;
r.score = newScore;
r.lastToppedUpAt = block.timestamp;
emit ReputationToppedUp(personaId, addedScore, newScore);
}
function currentReputation(uint256 personaId) external view returns (uint256) {
return _decayed(reputations[personaId]);
}
function rawStoredScore(uint256 personaId) external view returns (uint256) {
return reputations[personaId].score;
}
function timeSinceLastTopUp(uint256 personaId) external view returns (uint256) {
uint256 last = reputations[personaId].lastToppedUpAt;
if (last == 0) return 0;
return block.timestamp - last;
}
function halvingsElapsed(uint256 personaId) external view returns (uint256) {
uint256 last = reputations[personaId].lastToppedUpAt;
if (last == 0) return 0;
return (block.timestamp - last) / HALF_LIFE;
}
function isMeaningful(uint256 personaId, uint256 minimumScore) external view returns (bool) {
return _decayed(reputations[personaId]) >= minimumScore;
}
function compareReputation(uint256 personaIdA, uint256 personaIdB) external view returns (bool aIsHigher) {
return _decayed(reputations[personaIdA]) > _decayed(reputations[personaIdB]);
}
}
|
| > Ohm4Hook |
Uniswap v4, afterSwap only |
ETHEREUM |
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/*
// ---
// file: ohm4
// contract: Ohm4Hook
// role: uniswap v4 hook, price feed + identity ping
// gate: none
// ---
//
// summary: >
// every swap does two things here. it reports the pool's own price to the range guard, tied
// to whichever persona the trader has registered as primary - keeping both the price reading
// and the first-responder record current. it does not mint anything, does not rebase any
// balance, and does not itself execute a buy or sell against the treasury; flagging that an
// intervention is warranted and actually carrying one out remain two different things.
//
// caveat: >
// `trader` below is whoever called the pool manager directly, the real end user only for an
// unrouted swap - most real swaps go through a router, in which case the persona pinged is
// the router's, not the person who actually traded. resolving that needs a router that
// forwards the real sender through hookData.
//
// deploy: >
// like any v4 hook, this address has to be mined so its low bits match the permission flags
// below (afterSwap only) - deploy through a CREATE2 factory with a salt found via HookMiner,
// not a bare `new`.
*/
import {BaseHook} from "@uniswap/v4-periphery/src/utils/BaseHook.sol";
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
interface IOhm4RangeGuardOps {
function reportPrice(uint256 price, uint256 personaId) external returns (bool belowFloor, bool aboveCeiling);
}
interface IOhm4PersonaRegistryOps {
function primaryOf(address owner) external view returns (uint256 personaId, bool exists);
}
contract Ohm4Hook is BaseHook {
using PoolIdLibrary for PoolKey;
using StateLibrary for IPoolManager;
address public immutable rangeGuard;
address public immutable personaRegistry;
uint256 public totalSwapsObserved;
uint256 public totalInterventionsFlagged;
event PriceReportedAfterSwap(uint256 price, uint256 personaId, bool belowFloor, bool aboveCeiling);
constructor(IPoolManager _poolManager, address _rangeGuard, address _personaRegistry) BaseHook(_poolManager) {
require(_rangeGuard != address(0) && _personaRegistry != address(0), "zero address");
rangeGuard = _rangeGuard;
personaRegistry = _personaRegistry;
}
function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
return Hooks.Permissions({
beforeInitialize: false,
afterInitialize: false,
beforeAddLiquidity: false,
afterAddLiquidity: false,
beforeRemoveLiquidity: false,
afterRemoveLiquidity: false,
beforeSwap: false,
afterSwap: true,
beforeDonate: false,
afterDonate: false,
beforeSwapReturnDelta: false,
afterSwapReturnDelta: false,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}
function _afterSwap(address trader, PoolKey calldata key, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata)
internal
override
returns (bytes4, int128)
{
PoolId id = key.toId();
(uint160 sqrtPriceX96,,,) = poolManager.getSlot0(id);
uint256 reduced = uint256(sqrtPriceX96) >> 48;
uint256 impliedPrice = (reduced * reduced) >> 96;
(uint256 personaId,) = IOhm4PersonaRegistryOps(personaRegistry).primaryOf(trader);
(bool belowFloor, bool aboveCeiling) = IOhm4RangeGuardOps(rangeGuard).reportPrice(impliedPrice, personaId);
unchecked {
totalSwapsObserved += 1;
if (belowFloor || aboveCeiling) {
totalInterventionsFlagged += 1;
}
}
emit PriceReportedAfterSwap(impliedPrice, personaId, belowFloor, aboveCeiling);
return (BaseHook.afterSwap.selector, 0);
}
function stats() external view returns (uint256 swapsObserved, uint256 interventionsFlagged) {
return (totalSwapsObserved, totalInterventionsFlagged);
}
}
|