Skip to Content
⚠️ Alert: Sylan is under active development—only trust contract/wallet addresses announced on our official channels; we will never DM you, ask for funds, or run surprise airdrops/presales.
ContractsNode Registry

NodeRegistry

Manages the active node set, staking, unbonding, and slashing/rewards plumbing for Sylan nodes. It exposes a compact view for UIs and an admin surface for governance. The contract is UUPS‑upgradeable, ownable, pausable, and uses ReentrancyGuard and SafeERC20.

Time units are seconds (Unix epoch via block.timestamp). Token amounts use 18 decimals (SYL).


Responsibilities

  • Accept SYL stakes from nodes and track lifecycle status: None → Active ↔ Unbonding ↔ Inactive.
  • Enforce minStake for Active status; manage unbonding via unbondingPeriod.
  • Maintain payout address and free‑form metadata per node.
  • Provide a public, minimal node view for dashboards and eligibility checks (isActiveNode).
  • Allow authorized slashers (owner/consensus/allow‑list) to slash stake and distribute to treasury / nodePool / burn by BPS.
  • Track aggregate totals (totalStaked) and optional reputation.

Key parameters & addresses (views)

  • sylToken() → address — SYL ERC‑20 (proxy).
  • minStake() → uint96 — minimum stake to be Active.
  • unbondingPeriod() → uint64 — seconds a node must wait after requesting to unstake.
  • treasury() → address — project treasury.
  • slashDestination() → address — address receiving the treasury share of slashed funds.
  • nodePool() → address — address receiving the node‑pool share of slashed funds.
  • treasuryBps() → uint16, nodePoolBps() → uint16, burnBps() → uint16must sum to 10,000.
  • totalStaked() → uint256 — sum of all stakes.
  • reputation(node) → uint256 — monotonic counter; increased by Consensus for honest behavior.

Status model

None not registered Active meets minStake and not unbonding Unbonding pending unstake requested, waiting period running Inactive registered but below minStake (or unbonded)

Node view & eligibility

  • isActiveNode(node) → booltrue if status == Active.
  • nodeOf(node) → NodeInfo — compact struct for UIs:
struct NodeInfo { uint96 stake; // current stake uint64 unbondRequestedAt; // 0 if none NodeStatus status; // None/Active/Unbonding/Inactive address payout; // SYL withdrawal address string metadata; // free‑form (e.g., endpoint, region) }

Internally, the contract also tracks pendingUnbond (requested amount awaiting finalize). It is not part of NodeInfo to keep reads light.


Node actions (writes)

Register & stake

registerAndStake(uint96 amount, address payout, string metadata)
Requires amount ≥ minStake. Pulls SYL from the caller, sets payout (defaults to caller if 0x0), stores metadata, sets status Active, resets any unbonding fields, and bumps totalStaked.

Emits: NodeRegistered(node, payout, initialStake, metadata), NodeActivated(node)

Stake more

stakeMore(uint96 amount)
Increases stake and totalStaked. If the node was Inactive/Unbonding but now meets minStake with no pending unbond, status flips to Active.

Emits: StakeIncreased(node, amount, newStake) (and possibly NodeActivated(node)).

Update payout / metadata

  • setPayout(address payout) → updates payout (non‑zero).
  • setMetadata(string metadata) → updates metadata blob.

Emits: PayoutUpdated(node, payout), MetadataUpdated(node, metadata)

Unstake (two‑step with cancel)

  1. requestUnstake(uint96 amount)
    Adds to pendingUnbond. If pendingUnbond > 0, status becomes Unbonding and unbondRequestedAt is set if it was zero. Remaining effective stake is stake - pendingUnbond; if it falls below minStake, the node is deactivated.

Emits: UnstakeRequested(node, amount, unlockAt) where unlockAt = unbondRequestedAt + unbondingPeriod.

  1. cancelUnstake()
    Clears pendingUnbond and unbondRequestedAt. If stake ≥ minStake, status flips back to Active.

Emits: UnstakeCanceled(node) (and possibly NodeActivated(node)).

  1. finalizeUnstake()
    Callable after block.timestamp ≥ unbondRequestedAt + unbondingPeriod. Transfers the pending amount to payout (or the node if payout is zero), zeros unbond state, reduces stake and totalStaked, and updates status based on the remaining stake.

Emits: UnstakeFinalized(node, amount, remainingStake) (and possibly NodeActivated(node) or NodeDeactivated(node, stake)).


Slashing & reputation (protocol)

  • increaseReputation(address node, uint256 amount)only Consensus may call; increments reputation.
  • slash(address node, uint96 amount, bytes32 reason)only authorized slashers may call. Slashes up to the node’s current stake. Adjusts status (Inactive or Unbonding if below minStake).
    • Slashed amount is distributed:
      • treasuryShare = amount * treasuryBps / 10_000 → sent to slashDestination.
      • burnShare = amount * burnBps / 10_000burned via SYL.burn.
      • nodePoolShare = amount − treasuryShare − burnShare → sent to nodePool.

Emits: Slashed(node, amountSlashed, reason, by)


Admin (owner) surface

  • Parameters
    • setMinStake(uint96 newMin)Emits MinStakeUpdated(old, new)
    • setUnbondingPeriod(uint64 seconds)Emits UnbondingPeriodUpdated(old, seconds)
  • Wiring
    • setConsensus(address c) → also (re)authorizes c as slasher; Emits ConsensusUpdated(old, c)
    • setSlasher(address slasher, bool allowed) → allow/deny additional slasher EOA/contracts; Emits SlasherUpdated(slasher, allowed)
    • setTreasury(address t)Emits TreasuryUpdated(old, t)
    • setSlashDestination(address d)Emits SlashDestinationUpdated(old, d)
    • setNodePool(address p)Emits NodePoolUpdated(old, p)
    • setSlashDistribution(uint16 treasuryBps, uint16 nodePoolBps, uint16 burnBps)must sum to 10,000; Emits SlashDistributionUpdated(oldTreasury, oldNodePool, oldBurn, treasuryBps, nodePoolBps, burnBps)
  • Ops: pause() / unpause() and UUPS upgradeTo/upgradeToAndCall(...) (owner‑gated)

Events (node lifecycle & ops)

  • NodeRegistered(address indexed node, address payout, uint96 initialStake, string metadata)
  • NodeActivated(address indexed node)
  • NodeDeactivated(address indexed node, uint96 stake)
  • StakeIncreased(address indexed node, uint96 amount, uint96 newStake)
  • PayoutUpdated(address indexed node, address payout)
  • MetadataUpdated(address indexed node, string metadata)
  • UnstakeRequested(address indexed node, uint96 amount, uint64 unlockAt)
  • UnstakeCanceled(address indexed node)
  • UnstakeFinalized(address indexed node, uint96 amount, uint96 remainingStake)
  • Slashed(address indexed node, uint96 amount, bytes32 reason, address indexed by)

Admin events

  • MinStakeUpdated(uint96 oldMinStake, uint96 newMinStake)
  • UnbondingPeriodUpdated(uint64 oldSeconds, uint64 newSeconds)
  • ConsensusUpdated(address oldConsensus, address newConsensus)
  • SlasherUpdated(address slasher, bool allowed)
  • TreasuryUpdated(address oldTreasury, address newTreasury)
  • SlashDestinationUpdated(address oldDestination, address newDestination)
  • NodePoolUpdated(address oldNodePool, address newNodePool)
  • SlashDistributionUpdated(uint16 oldTreasuryBps, uint16 oldNodePoolBps, uint16 oldBurnBps, uint16 newTreasuryBps, uint16 newNodePoolBps, uint16 newBurnBps)

Minimal ABI (client‑facing)

For staking dashboards and operator portals:

[ {"type":"function","stateMutability":"view","name":"isActiveNode","inputs":[{"name":"node","type":"address"}],"outputs":[{"type":"bool"}]}, {"type":"function","stateMutability":"view","name":"nodeOf","inputs":[{"name":"node","type":"address"}],"outputs":[{"type":"tuple","components":[ {"name":"stake","type":"uint96"}, {"name":"unbondRequestedAt","type":"uint64"}, {"name":"status","type":"uint8"}, {"name":"payout","type":"address"}, {"name":"metadata","type":"string"} ]}]}, {"type":"function","stateMutability":"nonpayable","name":"registerAndStake","inputs":[{"name":"amount","type":"uint96"},{"name":"payout","type":"address"},{"name":"metadata","type":"string"}],"outputs":[]}, {"type":"function","stateMutability":"nonpayable","name":"stakeMore","inputs":[{"name":"amount","type":"uint96"}],"outputs":[]}, {"type":"function","stateMutability":"nonpayable","name":"setPayout","inputs":[{"name":"payout","type":"address"}],"outputs":[]}, {"type":"function","stateMutability":"nonpayable","name":"setMetadata","inputs":[{"name":"metadata","type":"string"}],"outputs":[]}, {"type":"function","stateMutability":"nonpayable","name":"requestUnstake","inputs":[{"name":"amount","type":"uint96"}],"outputs":[]}, {"type":"function","stateMutability":"nonpayable","name":"cancelUnstake","inputs":[],"outputs":[]}, {"type":"function","stateMutability":"nonpayable","name":"finalizeUnstake","inputs":[],"outputs":[]} ]

Integration tips

  • Approvals: Nodes must approve(registry, amount) on SYL before registerAndStake or stakeMore.
  • Countdowns: show unlockAt from UnstakeRequested to drive a UX timer (unbondingPeriod may change between requests; honor event data).
  • Status logic: treat Active as eligible; display Unbonding distinctly and disable new votes if your operator policy requires it.
  • Payout safety: encourage setting a dedicated payout address (can be a multisig) via setPayout.

Security & invariants

  • BPS sum = 10,000 across {treasuryBps, nodePoolBps, burnBps}.
  • Authorized slashing only: owner, consensus, and allow‑listed slashers can call slash.
  • Pause blocks writes (register/stake/unstake/slash), but reads continue.
  • Reentrancy protected around transfers and finalization.

Out of scope for this page

  • Consensus rules & rewards distribution logic → APIConsensus / APIEscrow
  • Token details → Sylan Token
  • Addresses & ABIs → Architecture → Addresses & ABIs
Last updated on