height 23776 of 23776 on the network. 9 peer(s) connected, 77 in its peer directory; 1072 block(s) followed and 894 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
- Status
- template — not deployed A template is a draft, not a deployed contract. Nothing here is on the chain: it has no address until somebody deploys it, and where it lands depends on who deploys it and with which salt. Never send value to a template.
- File
templates/vesting.ares- Profile
- native STR vesting
- Holds
- native STR
- The property that matters
- nothing is payable before the cliff; the schedule pays vested minus released
- Compiler in force
areslang-1.3.0- Bytecode
- 1,198 bytes
- Bytecode SHA-256
2e6f7e39cccb11dad877e1d3595cb0f246b71a9f0fe6e1a1c17fa309117e83e5- Source SHA-256
9cf22c4d30dca943b353a3a379e454615924663b6b90f73954595da49e5b9052- Published vector
- matches the published vector spec/conformance-vectors.json pins 2e6f7e39cccb11da… under areslang-1.3.0
- Catalogue
- every template
Before you deploy
This template’s header marks no constant to edit: its parties and amounts are given to its functions after deployment.
Where your deployment would land
Name a deployer to see where its deployment of this template, unedited, would land.
The chain derives a contract’s address from its name, its bytecode, the deployer and the salt. This is that function over the bytecode above, so it holds only for the template exactly as shown and only while the chain runs the same compiler. Edit one constant and the address moves, which is correct: different code is a different contract. Nothing is at this address until somebody deploys there.
Measured gas
What the STARW VM charged for each call on a real chain, under areslang-1.3.0, and asserted exactly by the catalogue test: a figure that moved would fail the build. Measured, not estimated. A call that stores a string costs more for a longer one; those were measured with ipfs://QmExampleTokenMetadataDocument01.
| Entry point | Gas |
|---|---|
| release | 3,105 |
| release(final) | 2,890 |
| start | 1,971 |
- Declare at least
- 31,050 ten times the most expensive call; post-mining covers only a tenth of the declared limit
ABI
| Kind | Name | Inputs | Outputs | Mutability |
|---|---|---|---|---|
| function | start | who: address, cliffAfterMs: uint, durationMs: uint | uint | nonpayable |
| function | release | uint | nonpayable | |
| function | beneficiaryIs | address | view | |
| function | grantTotal | uint | view | |
| function | releasedToDate | uint | view | |
| function | cliff | uint | view | |
| function | endsAt | uint | view | |
| function | held | uint | view | |
| function | hasStarted | bool | view | |
| event | Started | beneficiary: address, total: uint, cliffAtMs: uint, endsAtMs: uint | ||
| event | Released | to: address, amount: uint, releasedToDate: uint |
From the command line
Print the source to a file, edit the marked constants, dry-run it to see the digest and your address, then deploy. Only the last command signs, and it is the only one that needs your key and a node.
strblk contract template vesting > vesting.ares strblk contract template vesting --info strblk wallet deploy vesting.ares --dry-run --address <your address> strblk wallet deploy vesting.ares --node <your node's RPC>
Source
templates/vesting.ares, as embedded in this build
// =========================================================================================
// 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;
}
}