Nine lines, one address.
The typed interface is published so reads are checked when your contract is built rather than when it runs. A version bump never changes an existing field, so a consumer written against version 1 keeps reading a correct answer out of a version 2 record.
the interface
@gl.contract_interface
class VouchsafeIface:
class View:
def read(self, who: str) -> dict: ...
def read_strict(self, who: str) -> dict: ...
def qualifies(self, who: str, min_settled: int,
max_failed: int, min_age_days: int,
strict: bool) -> dict: ...
def reporter_quality(self, reporter: str) -> dict: ...
def was_reported(self, reporter: str,
report_id: str) -> dict: ...
class Write:
def report(self, subject: str, report_id: str,
evidence: str) -> str: ...
def register_consumer(self, name: str) -> None: ...read cost
- read
- four counters, fixed cost
- read_strict
- the same, minus entries under appeal - also fixed
- tail length
- capped at 12 forever
- who pays
- the calling contract
Every read happens inside somebody else’s transaction, so the cost of reading is a cost they pay. That single fact decides how entries age out - and it is why neither published read iterates over anything. The one view that walks the tail is entries, which this website calls and no consuming contract needs.
reading, in your own contract
def _ratio_for(self, borrower: str) -> tuple[int, str]:
# view() is synchronous: the answer arrives inside THIS transaction,
# before any collateral is locked. That is the whole product.
verdict = VouchsafeIface(self.vouchsafe).view().qualifies(
borrower,
MIN_SETTLED, # settled dealings this market wants to see
0, # failures it tolerates
MIN_AGE_DAYS, # days of record it wants behind them
True, # strict: a failure under appeal is not counted here
)
if verdict["passes"]:
return EARNED_RATIO, str(verdict["reason"]) # 120%, earned
return BASE_RATIO, str(verdict["reason"]) # 150%, the stranger priceopen_position is payable and nothing pays back out - closing a position settles it and reports the outcome, and never returns the collateral. Repayment and liquidation are a lending market’s problem, not this file’s, and building half of them would bury the nine lines worth copying. Safe on a gasless network, and nowhere else.reporting back
@gl.public.view
def vouchsafe_ack(self) -> bool:
# Vouchsafe calls this before it accepts anything from you.
# An account cannot answer it. That is the entire check.
return True
# ... then, when a dealing finishes:
gl.get_contract_at(VOUCHSAFE).emit(on="finalized").report(
borrower, # the subject of the dealing
f"pos-{borrower}-{n}", # your own id, deduplicated per reporter
evidence, # what happened, in your own words
)Before it accepts anything from you, report calls vouchsafe_ack back on your contract. An account has no code and cannot answer, so a person cannot type a report into a form - and a contract cannot become a reporter by accident. Without that rule the record is a review site, and review sites get brigaded within a week.
The obvious version of this check does not work, and it is worth knowing why before you write your own: a cross-contract write is an emitted message, an emitted message is its own transaction, and its origin is the emitting contract. So sender == origin for your report exactly as it does for a person’s direct call.
field / meaning
who can change what
- stop new reports -
set_paused - change the appeal bond -
set_contest_bond - change the cooling period -
set_cooling - hand the role to another address -
transfer_ownership
- stop a read. The pause reaches writes only, so your transaction never fails because somebody pressed a button
- change a counter, an entry, an outcome or a weight. There is no method that does it
- delete a record, or edit one
- rewrite the criteria. They are constants in the contract, so changing them means a redeploy at a new address
- owner
- 0x3e1D268c8B1Ba7d042968ab713467C5631831513
- reports
- open
- reads
- always open
Worth checking rather than believing: the administration section of contracts/vouchsafe.py is four methods long, and none of them touches a record.
from typescript
import { createVouchsafe } from "vouchsafe-adapter";
const vouchsafe = createVouchsafe({ address: VOUCHSAFE });
// the same two view methods a consuming contract calls
const rec = await vouchsafe.readStrict(borrower);
if (rec.known && rec.settled >= 10 && rec.failed === 0) {
// ...and the same numbers that contract just saw
}The adapter is a thin wrapper over the same two view methods, so the app and the consuming contract show the same numbers. It computes nothing the contract does not - tier included, which is why that lives on chain rather than in the package.
what you need
- record
- 0x95DB78691cD6E31D99ca1FfDe129d3F3CE4114A5
- network
- GENLAYER STUDIO
- test address
- nothing reported yet
- adapter
packages/vouchsafe-adapterin the repo, not yet on npm- manifest
- /vouchsafe.json
- interface version
- 2
- criteria version
- v1.3