SourceLessExplorer · SourceLess AsgardSourceLess Asgard · chain 1313
LiveHeight23,773Head age59 sLast interval2 minFinality23,762Behind−11Pending0 / 8,192Tx in head0
XPlorer following the chain as its own read-only node

height 23773 of 23773 on the network. 9 peer(s) connected, 77 in its peer directory; 1069 block(s) followed and 892 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.

Escrow

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/escrow.ares
Profile
native STR escrow
Holds
native STR
The property that matters
the seller cannot release to themselves; the buyer cannot refund early
Compiler in force
areslang-1.3.0
Bytecode
1,567 bytes
Bytecode SHA-256
5921cb9039b2d6c6bdf33caf0eed4cc10c898b7cbb553f5265bb114c39063498
Source SHA-256
03b4abe7e54e462324b6a849eb0494bb25785b852452253fae84f1cfa1567c0b
Published vector
matches the published vector spec/conformance-vectors.json pins 5921cb9039b2d6c6… under areslang-1.3.0

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 pointGas
release3,235
refund3,235
timeoutRefund3,233
open1,276
Declare at least
32,350 ten times the most expensive call; post-mining covers only a tenth of the declared limit

ABI

KindNameInputsOutputsMutability
functionopenbuyerAddress: address, sellerAddress: address, refundAfterMs: uintuintnonpayable
functionreleaseuintnonpayable
functionrefunduintnonpayable
functiontimeoutRefunduintnonpayable
functionhelduintview
functionescrowStatusuintview
functionbuyerIsaddressview
functionsellerIsaddressview
functionarbiterIsaddressview
functiondeadlineuintview
functionpaiduintview
eventOpenedbuyer: address, seller: address, deadlineMs: uint
eventReleasedto: address, amount: uint
eventRefundedto: address, amount: 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 escrow > escrow.ares
strblk contract template escrow --info
strblk wallet deploy escrow.ares --dry-run --address <your address>
strblk wallet deploy escrow.ares --node <your node's RPC>

Source

templates/escrow.ares, as embedded in this build

// =========================================================================================
// Escrow — holds native STR for two parties, with an arbiter and a deadline.
//
// WHAT IT IS FOR
//   A buyer funds this contract; the seller performs; the buyer (or the arbiter) releases.
//   If nobody releases, the buyer takes the money back after a deadline the contract fixes
//   at setup and nobody can move afterwards.
//
// WHAT IT GUARANTEES
//   * The seller cannot pay themselves. `release` refuses any caller who is not the buyer or
//     the arbiter, and it pays the address stored in `seller` — never the caller.
//   * The arbiter cannot be a party. `open` refuses a buyer or a seller equal to the arbiter,
//     because an arbiter that names ITSELF the seller can `release` the whole balance to
//     itself in one call — including value transferred in before `open`, which status 0 has no
//     way to return. That is not arbitration; it is theft with a settlement's paperwork. The
//     check is on both sides: an arbiter that is the buyer takes the same money via `refund`.
//   * The buyer cannot take the money back early. `timeoutRefund` reads the BLOCK's
//     timestamp; there is no time argument anywhere in the ABI, so there is no unlock date a
//     caller can supply.
//   * It closes exactly once. `status` moves open -> released or open -> refunded, and every
//     paying function refuses a status that is not open, so no payout can happen twice.
//   * A refused payout moves nothing at all. Storage and value roll back together, so an
//     escrow whose release is rejected by the ledger is still open, still funded, and can
//     still be refunded.
//
// THE IDENTITY RULE, WHICH YOU MUST READ BEFORE YOU DEPLOY THIS
//   CORRECTED 2026-09-08: this block described a rule that no longer exists, and quoted a
//   revert string that is now unreachable. `ValueJournal.receiveRefusal` used to require the
//   recipient to hold an ALLOCATED STR.domain or be a deployed contract; it now refuses only
//   Hnitbjorg reserved accounts. The gate got MORE permissive, so nothing here was unsafe — but
//   four templates instructed issuers to pre-flight a check that is gone, and this one told them
//   a `release` holding the buyer's money would revert when it will not.
//
//   The rule today: any well-formed, non-reserved address, or a deployed contract. A payout to
//   an address with no domain succeeds. What such an address still cannot do is SPEND what it
//   receives until it proves a domain of its own, which is a fact about the recipient rather
//   than about this escrow.
//
// HOW IT IS FUNDED, AND THE LIMIT THAT FOLLOWS
//   A call record carries no value, so the buyer funds this contract with a PLAIN STR
//   TRANSFER to its address, AFTER it is deployed. The address is derivable from name,
//   bytecode, deployer and salt before the contract exists, but it cannot be funded before it
//   exists: the ledger's receive gate answers from the deployed set, not from the shape of the
//   string, because crediting a derived-but-undeployed address would create a balance with no
//   key and no code behind it.
//   The consequence, and it is the sharpest limit in this template: THE CONTRACT CANNOT SEE
//   WHO PAID IT. There is no deposit hook and no `msg.value`. So this escrow does not track
//   a deposit per party — it escrows whatever balance it holds when it pays out. Anyone can
//   add to it and nobody can be credited for having done so.
//   FUND IT ONLY AFTER `open`, AND ONLY AFTER READING `buyerIs()` AND `sellerIs()`. In status
//   0 there is no buyer and no seller recorded, so there is no exit at all: no release, no
//   refund, no timeout, and none can be added, because the contract has nobody it could
//   honestly pay. Value sent before `open` is hostage to the terms the arbiter chooses next.
//
// HOW IT IS TYPICALLY MISUSED
//   * Funding it AFTER release. `release` pays `balance(self)` at the moment it runs; money
//     that arrives later is stranded in a closed escrow with no path out. Fund, then verify
//     `held()`, then perform.
//   * Treating the arbiter as neutral because the README says so. The arbiter here can send
//     the money to either party at any time before the deadline. That is custody. It is
//     visible in the ABI as `arbiterIs()` and it is the whole reason `arbiter` is a separate
//     view. What the arbiter can no longer do is keep it: it cannot be the buyer or the
//     seller, so every path it can take pays somebody else.
//   * Wanting an escrow with no arbiter. THIS TEMPLATE CANNOT BE ONE. The deployer is the
//     arbiter and `open` now refuses to name it as a party, so "open with the buyer's own
//     address as the arbiter" — which earlier versions of this header suggested — reverts.
//     Deploy from a key both parties accept as the decider, or use a template whose payout
//     needs no decision at all: `vesting.ares` pays on the clock and `multisig.ares` pays on
//     M-of-N.
//   * Deploying without a deadline in mind. An escrow whose release depends on a party who
//     disappears is funds locked forever in an immutable contract. `refundAfterMs` is
//     required and is checked to be non-zero.
//   * Under-declaring the gas limit. MEASURED: open 1,276, release 3,235, refund 3,235,
//     timeoutRefund 3,233. Declare at least 33,000. The paying calls are dominated by one
//     TRANSFER at 2,100 and one BALANCE at 400.
// =========================================================================================

contract Escrow {
  // The deployer is the arbiter. `caller` in a state initializer is the authenticated
  // deployer, so there is no window between deployment and setup in which a stranger could
  // claim this contract.
  state arbiter: address = caller;

  state buyer: address = "";
  state seller: address = "";
  state deadlineMs: uint = 0;

  // 0 = not set up, 1 = open, 2 = released to the seller, 3 = refunded to the buyer.
  state status: uint = 0;
  state paidMinor: uint = 0;

  event Opened(buyer: address, seller: address, deadlineMs: uint);
  event Released(to: address, amount: uint);
  event Refunded(to: address, amount: uint);

  // Set up once, by the arbiter, before anything is at stake. There is no function that
  // changes buyer, seller or deadline afterwards: an escrow whose terms can move is not one.
  fn open(buyerAddress: address, sellerAddress: address, refundAfterMs: uint) -> uint {
    require(caller == arbiter, "not the arbiter");
    require(status == 0, "already open");
    require(buyerAddress != sellerAddress, "buyer and seller must differ");
    // The arbiter decides who is paid, so an arbiter that is also a party decides in its own
    // favour. Naming ITSELF the seller and then calling `release` is not an edge case: it is a
    // one-call theft of everything this contract holds, including anything transferred in
    // before `open`, and it leaves a chain that still validates. Both directions are refused,
    // because an arbiter that is the buyer takes the same money through `refund`.
    require(buyerAddress != arbiter, "the arbiter cannot be the buyer");
    require(sellerAddress != arbiter, "the arbiter cannot be the seller");
    // And neither party may be this contract. The value ledger refuses a contract paying
    // itself, so an escrow whose seller is this address can never release and one whose
    // buyer is this address can never refund: the deposit sits here until the other side
    // of the switch is taken, and if both were set this way nothing could move at all.
    require(buyerAddress != self, "self is not payable");
    require(sellerAddress != self, "self is not payable");
    require(refundAfterMs > 0, "an escrow needs a deadline");
    buyer = buyerAddress;
    seller = sellerAddress;
    // The block's clock, not an argument. A caller-supplied deadline is a caller-supplied
    // escape hatch.
    deadlineMs = timestamp + refundAfterMs;
    status = 1;
    emit Opened(buyerAddress, sellerAddress, deadlineMs);
    return deadlineMs;
  }

  // The buyer accepts delivery, or the arbiter decides for them. Pays the SELLER — the
  // stored address, never the caller.
  fn release() -> uint {
    require(status == 1, "escrow is not open");
    require(caller == buyer || caller == arbiter, "only the buyer or the arbiter may release");
    let amount = balance(self);
    require(amount > 0, "nothing held");
    // Effects before the payment. There are no cross-contract calls in AresLang, so a
    // transfer cannot re-enter — the ordering is free and correct anyway.
    status = 2;
    paidMinor = amount;
    transfer(seller, amount);
    emit Released(seller, amount);
    return amount;
  }

  // The seller cancels, or the arbiter decides for them. Pays the BUYER.
  fn refund() -> uint {
    require(status == 1, "escrow is not open");
    require(caller == seller || caller == arbiter, "only the seller or the arbiter may refund");
    let amount = balance(self);
    require(amount > 0, "nothing held");
    status = 3;
    paidMinor = amount;
    transfer(buyer, amount);
    emit Refunded(buyer, amount);
    return amount;
  }

  // The way out when everybody else has vanished. Anyone may call it, because it can only
  // ever pay the buyer, and only after the deadline the block's clock has passed.
  fn timeoutRefund() -> uint {
    require(status == 1, "escrow is not open");
    require(timestamp >= deadlineMs, "deadline has not passed");
    let amount = balance(self);
    require(amount > 0, "nothing held");
    status = 3;
    paidMinor = amount;
    transfer(buyer, amount);
    emit Refunded(buyer, amount);
    return amount;
  }

  // Reads are never paused and never gated. A stuck escrow must stay auditable.
  view fn held() -> uint {
    return balance(self);
  }

  view fn escrowStatus() -> uint {
    return status;
  }

  view fn buyerIs() -> address {
    return buyer;
  }

  view fn sellerIs() -> address {
    return seller;
  }

  view fn arbiterIs() -> address {
    return arbiter;
  }

  view fn deadline() -> uint {
    return deadlineMs;
  }

  view fn paid() -> uint {
    return paidMinor;
  }
}