SourceLessExplorer · SourceLess AsgardSourceLess Asgard · chain 1313
LiveHeight23,745Head age7 minLast interval4 minFinality23,734Behind−11Pending0 / 8,192Tx in head0
XPlorer following the chain as its own read-only node

height 23745 of 23745 on the network. 9 peer(s) connected, 77 in its peer directory; 1041 block(s) followed and 865 pulled in by sync since this follower started. Caught up: this follower holds every block any peer has claimed, so every panel below is the whole chain. Genesis 1cf8655125cc1bbb…, producers genesis.

What this follower is, locally

Following 7 seed(s) — their addresses are not published here, for the reason the peer table gives. Its store is held under its own Cerberus lease — no other node’s store is opened, touched or stopped.

Vesting for your fork

Status
a draft, not deployed nothing here is on any chain; never send value to a draft
Profile
native STR vesting
Holds
native STR
The property that matters
nothing is payable before the cliff; the schedule pays vested minus released

Edit the draft

Set the constants the template marks for editing, and name a deployer to see where it would land.

This template marks no constant to edit: its parties and amounts are given to its functions after it is deployed. You can still name a deployer to see where it would land.

The form asks this explorer to compile your draft and show it back. It signs nothing, stores nothing and reaches no node.

The draft as shipped

Contract
Vesting
Compiler
areslang-1.3.0
Bytecode SHA-256
2e6f7e39cccb11dad877e1d3595cb0f246b71a9f0fe6e1a1c17fa309117e83e5
Bytecode
1,198 bytes
Source SHA-256
9cf22c4d30dca943b353a3a379e454615924663b6b90f73954595da49e5b9052
The catalogue text?
Yes, byte for byte: matches the published vector
Ready to deploy?
no catalogue examples left
Would land at
name a deployer to see it

Deploy it to your fork

Only the last step signs, and only it reaches a node.

  1. Save the draft. Download it above, or start from the catalogue text with strblk contract template vesting > vesting.ares and edit the marked constants yourself.
  2. Dry-run it on your machine. strblk wallet deploy vesting.ares --dry-run --address <your address on the fork> compiles it locally and prints its digest and address. They must match this page; if they do not, the file is not this draft.
  3. Find your fork's RPC. It is the rpc (Iris, wallets) line of the fork's banner, and the rpcUrl of each node in <data root>/fork.json.
  4. Deploy it. strblk wallet deploy vesting.ares --node <your fork's RPC>, from a wallet whose account holds a proved STR.domain on your fork. Before it signs, the wallet prints the network: the realm reads unregistered and the genesis is your fork's hash. Check that hash; it is what the signature binds.

Measured gas

Charged by the STARW VM under areslang-1.3.0, the same on your fork. Measured, not estimated.

Entry pointGas
release3,105
release(final)2,890
start1,971
Declare at least
31,050 ten times the most expensive call

Functions

KindNameInputsOutputsMutability
functionstartwho: address, cliffAfterMs: uint, durationMs: uintuintnonpayable
functionreleaseuintnonpayable
functionbeneficiaryIsaddressview
functiongrantTotaluintview
functionreleasedToDateuintview
functioncliffuintview
functionendsAtuintview
functionhelduintview
functionhasStartedboolview
eventStartedbeneficiary: address, total: uint, cliffAtMs: uint, endsAtMs: uint
eventReleasedto: address, amount: uint, releasedToDate: uint

Your draft's source

templates/vesting.ares, as shipped

// =========================================================================================
// Vesting — releases native STR to one beneficiary over time, with a cliff.
//
// WHAT IT IS FOR
//   A grantor funds this contract, then starts a schedule. From the cliff onwards the
//   beneficiary can draw the linearly vested share; after the end date they can draw the
//   rest. Nobody can draw it earlier and nobody can redirect it.
//
// WHAT IT GUARANTEES
//   * Nothing is payable before the cliff. The clock is the BLOCK's timestamp; there is no
//     time argument in the ABI, so there is no unlock date a caller can supply.
//   * Every release pays `vested - alreadyReleased`, never `vested`. The classic vesting bug
//     is paying the vested total on every call, which pays the whole schedule repeatedly.
//   * The sum of everything ever paid is exactly `total`, and never more, however many times
//     `release` is called and in whatever order.
//   * `release` pays the STORED beneficiary. Any caller may trigger it — a beneficiary
//     without gas is not a beneficiary who cannot be paid — but the money goes to one
//     address and there is no function that changes it.
//   * The schedule cannot be restarted, revoked, cancelled or accelerated. The grantor keeps
//     NO power after `start`. Read the ABI: there is no such function to call.
//
// THE IDENTITY RULE — CORRECTED 2026-09-08
//   This block described a rule that no longer exists. `ValueJournal.receiveRefusal` used to
//   require a payment recipient to hold an ALLOCATED STR.domain or be a deployed contract; it
//   now refuses only Hnitbjorg reserved accounts. The gate became MORE permissive, so nothing
//   built against the old text is unsafe — but it instructed issuers to pre-flight a check that
//   no longer applies, and told them a payout would revert when it will not.
//
//   Today: any well-formed, non-reserved address, or a deployed contract, may be paid. What a
//   recipient with no proved domain still cannot do is SPEND what it receives — a fact about
//   that holder, not about this contract.
//
// HOW IT IS FUNDED, AND THE LIMIT THAT FOLLOWS
//   A call record carries no value, so the grantor funds this contract with a PLAIN STR
//   TRANSFER first, and `start` then snapshots `balance(self)` as the granted total. The
//   contract cannot see who paid it or when, which is why the total has to be taken as a
//   snapshot rather than accumulated per deposit.
//   ANYTHING TRANSFERRED IN AFTER `start` IS STRANDED. It is not part of `total`, it is not
//   vested, and this contract has no sweep — deliberately, because a grantor sweep is a
//   revocation power wearing a support tool's clothes. Fund fully, check `grantTotal()`,
//   then start.
//
// HOW IT IS TYPICALLY MISUSED
//   * Starting before funding. `start` refuses a zero balance, but it cannot refuse a
//     PARTIAL one: it snapshots whatever is there. Check `grantTotal()` after starting and
//     before the beneficiary relies on it.
//   * Assuming the grantor can cancel. It cannot, and that is the point. If your agreement
//     needs revocation, this is the wrong template and the beneficiary must be able to see
//     that in the ABI before accepting the grant.
//   * Reading `height` and `timestamp` as interchangeable. This template uses time because a
//     vesting date is a date. For anything adversarial prefer height: a producer has some
//     latitude over the timestamp it writes and none over the height.
//   * Under-declaring the gas limit. MEASURED: start 1,971, release 3,105 during the linear
//     period and 2,890 after the end date. Declare at least 32,000.
// =========================================================================================

contract Vesting {
  // The deployer. `caller` in a state initializer is the authenticated deployer, so there is
  // no claim race between deployment and setup.
  state grantor: address = caller;

  state beneficiary: address = "";
  state startedAtMs: uint = 0;
  state cliffAtMs: uint = 0;
  state endsAtMs: uint = 0;
  state total: uint = 0;
  state released: uint = 0;
  state started: bool = false;

  event Started(beneficiary: address, total: uint, cliffAtMs: uint, endsAtMs: uint);
  event Released(to: address, amount: uint, releasedToDate: uint);

  // Fund the contract FIRST, then call this. The balance at this instant is the grant.
  fn start(who: address, cliffAfterMs: uint, durationMs: uint) -> uint {
    require(caller == grantor, "not the grantor");
    require(started == false, "already started");
    require(durationMs > 0, "duration must be positive");
    require(cliffAfterMs <= durationMs, "cliff must be inside the schedule");
    // THE GRANT IS LOST IF THIS LINE IS REMOVED. The value ledger refuses a contract that
    // pays itself, so a schedule whose beneficiary is this contract reverts on every
    // `release` forever — and there is no sweep, no restart and no revocation here by
    // design. MEASURED 2026-10-07: `start` accepted it, reported the grant total, emitted
    // `Started`, and every later release failed with `a contract cannot transfer to itself`.
    // Refused at the door, where the grantor can still fix it.
    require(who != self, "self is not payable");
    let held = balance(self);
    require(held > 0, "fund the contract before starting");
    beneficiary = who;
    startedAtMs = timestamp;
    cliffAtMs = timestamp + cliffAfterMs;
    endsAtMs = timestamp + durationMs;
    total = held;
    started = true;
    emit Started(who, held, cliffAtMs, endsAtMs);
    return held;
  }

  fn release() -> uint {
    require(started == true, "not started");
    require(timestamp >= cliffAtMs, "cliff not reached");

    // Vested-to-date. Multiplication happens before division so the integer division
    // truncates once, at the end; AresLang integers are arbitrary precision to 4096 bits, so
    // `total * elapsed` cannot wrap.
    let vested = total;
    if (timestamp < endsAtMs) {
      let elapsed = timestamp - startedAtMs;
      let span = endsAtMs - startedAtMs;
      vested = total * elapsed / span;
    }

    // The line the whole template turns on: pay the DIFFERENCE, not the vested total.
    let due = vested - released;
    require(due > 0, "nothing to release");

    // Effects before the payment.
    released = released + due;
    transfer(beneficiary, due);
    emit Released(beneficiary, due, released);
    return due;
  }

  view fn beneficiaryIs() -> address {
    return beneficiary;
  }

  view fn grantTotal() -> uint {
    return total;
  }

  view fn releasedToDate() -> uint {
    return released;
  }

  view fn cliff() -> uint {
    return cliffAtMs;
  }

  view fn endsAt() -> uint {
    return endsAtMs;
  }

  view fn held() -> uint {
    return balance(self);
  }

  view fn hasStarted() -> bool {
    return started;
  }
}