Open Net Worth
Open Net Worth is an open-source personal AI wealth assistant you run yourself: one ledger for your whole financial life, worked by an AI agent you control.
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證GenLayer Intelligent Contract: validators check that a paid APIbase API call matches its USDC payment on Base (PASS/FAIL on chain)
翻譯準備中
正在載入完整 README。
檢視完整 README ↗Validators attest that an APIbase paid-call receipt matches the USDC transfer on Base.
What it is. ApibaseReceiptAttest (contracts/apibase_receipt_attest.py) reads one public JSON
receipt published by APIbase for a paid tool call, asks a configured Base JSON-RPC node for the
transaction that receipt names, and records PASS, FAIL or NOT_FOUND. GenLayer's leader/validator
consensus replaces a single process's word with several independent fetches.
Beside it sit two dispute contracts, one per closed claim, both REFUND / RELEASE witnesses:
ApibaseReceiptDispute (contracts/apibase_receipt_dispute.py, claim upstream_failed_but_billed, reads
the receipt only) and ApibasePaymentDispute (contracts/apibase_payment_dispute.py, claim
paid_without_receipt, reads APIbase's index and Base). They are described in The refund policy and the
two claims; the rest of this file describes the attest unless it says otherwise.
What it is not. There is no LLM in it. It never makes a paid call and never spends anything. It
does not judge the tool's response, only that the payment the receipt claims is the payment on the
chain. It covers one rail and billed calls only (see v0 limits). It does not take part in Provek's
scoring and shares no code with src/.
The use case, in three sentences. An agent pays APIbase for a tool call and gets a receipt that says who paid whom, how much, and in which Base transaction. Anyone — the agent, its operator, an auditor — can ask the contract about that receipt id, with no account and no permission. The answer is a consensus-backed record that the receipt and the chain agree, or that they do not.
Where this code lives. The source of record is integrations/genlayer-apibase-attest/ in
whiteknightonhorse/provek,
where Provek's gates check it (D-66, beside D-63 for the first contract). The same directory is published, with its
history, as whiteknightonhorse/apibase-genlayer-attest — a read-only mirror made with
git subtree split (the procedure is D-64's). The mirror is never edited directly: every change enters Provek and is
re-split (D-64, second mirror). The deployed contract is this directory's contracts/apibase_receipt_attest.py as committed at
11474b4 (D-67). Paths such as DECISIONS.md, docs/adr/ and evidence/* refer to the root of the Provek
repository.
ApibaseReceiptAttest (integrations/genlayer-apibase-attest/) is deployed on the GenLayer test network, test GEN only. One real
APIbase receipt has been attested there; the record is readable by anyone with get_attestation.
| Contract | 0x1bD4C0EBC9b365905d4EBC84b944d861499afe6E |
| Explorer | https://explorer-bradbury.genlayer.com/address/0x1bD4C0EBC9b365905d4EBC84b944d861499afe6E |
| Page | https://apibase.pro/attest/ (APIbase's page for this contract; APIbase moves it to this address when its promotion goes live) |
| Network | GenLayer testnet Bradbury Phase 2 (chain 4221), RPC https://rpc-bradbury.genlayer.com; deployed 2026-10-28 from commit 11474b4 after GenLayer reset the testnet on 2026-10-24 (the earlier deployment died with the reset; its entry is kept in deploy/deployment.json, previous_deployments) |
| Rail configured | set_chain("8453", ...): Base mainnet, https://mainnet.base.org, USDC 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
Live records, as of 2026-10-28 (read back with list_by_receipt and get_attestation; every field and transaction in
deploy/deployment.json and evidence/MEASURED-019-*, the deploy and the write in evidence/MEASURED-017-*):
| result | receipt id | observation | attested at (UTC) |
|---|---|---|---|
| PASS | 9dbc77f9fd66bcb8714cd064f5681005 (myvariant.search, 1000 atomic USDC) | payment_matches | 2026-10-28T09:30:52Z |
Not measured: a live FAIL or NOT_FOUND record (none was requested), and a studionet integration run for this contract.
The deployment record is deploy/DEPLOYMENT.md.
Deployed 2026-11-09 from commit 9055f93 (runner babwp, header # v0.3.0), policy 2026-11-04.3 pinned; test GEN only.
0 disputes as of 2026-11-09 (none submitted in the deployment session; list_recent not read).
| Contract | Address | Explorer |
|---|---|---|
ApibaseReceiptDispute | 0xe4e75ac47bf8350Ff705a552b7453Cf7726eF893 | https://explorer-bradbury.genlayer.com/address/0xe4e75ac47bf8350Ff705a552b7453Cf7726eF893 |
ApibasePaymentDispute | 0x34e8335b70546fb37c9f2c06d472208b13b8f168 | https://explorer-bradbury.genlayer.com/address/0x34e8335b70546fb37c9f2c06d472208b13b8f168 (set_chain("8453", ...) configured) |
Every field is in deploy/deployment.json (dispute_contract) and deploy/DEPLOYMENT.md; the transcripts are evidence/MEASURED-021-* and -022-*.
apibase-receipt/1The contract fetches {receipt_base_url}{receipt_id}.json. receipt_id is 32–64 lowercase hex
characters. The document, as the contract reads it (a concrete instance, with a real transaction hash, is
tests/fixtures/receipt_pass.json; the repository secret scan excludes that one fixtures directory
because a transaction hash has the shape of a raw key):
{
"schema": "apibase-receipt/1",
"rail": "x402-base",
"chain_id": 8453,
"payment_tx": "0x<64 hex characters>",
"payer": "0x688130810f0bede38742b66e7a443274c01d4213",
"payee": "0x781dfd3f2687f8e5656313b2179fdefdf99d339d",
"asset": "USDC",
"amount_atomic": "1000000",
"billed": true,
"tool": "example.tool",
"upstream_status": 200,
"response_sha256": "<64 hex characters>"
}
Validation is strict: schema must be exactly apibase-receipt/1; chain_id and upstream_status
integers; payment_tx 0x plus 64 hex; payer and payee 0x plus 40 hex (stored lowercased);
asset exactly USDC; amount_atomic a decimal string (USDC has six decimals, so 1000000 is 1 USDC);
billed a boolean; response_sha256 64 hex. The first key that fails is named in the revert.
Receipts publish payer, payee, amount, transaction and the SHA-256 of the response — never the response.
One function, _judge, is called by the leader, by every validator and by attest() after
consensus. transfers are the Transfer(address,address,uint256) logs emitted by the configured
chain's USDC contract inside the transaction; amounts are compared as integers.
| Observation | Outcome | Record result / observation |
|---|---|---|
| Receipt fetch failed, or HTTP status ≠ 200, or the RPC answered with an error | revert receipt <id> is not readable at APIbase (http=<n>) | none |
| Receipt malformed | revert … not attestable by this contract: malformed receipt: <key> | none |
rail ≠ x402-base | revert … unsupported rail <rail> | none |
billed is not true | revert … not billed | none |
chain_id not configured | revert … chain <id> not configured | none |
Node returns null for the transaction | recorded | NOT_FOUND / tx_not_found |
| Transaction status ≠ 1 | recorded | FAIL / tx_reverted |
A transfer equals [payer, payee, amount] | recorded | PASS / payment_matches |
| Transaction succeeded, no such transfer | recorded | FAIL / no_matching_transfer |
A validator agrees iff its own whole observation equals the leader's (receipt, receipt_sha256,
chain_found, tx_status, transfers): every input is immutable by design, and the record is built
from all of it. Verdict-only agreement was incomplete in the design (GL-00 ruling-2), not a build
defect; the Portal steward rejected it on 2026-10-23 (D-76). A validator whose own fetch raises
votes False and no verdict is made: nobody could check is not the same as the claim being false
(CLAUDE.md invariant 1, named in the contract's docstring). NOT_FOUND, FAIL and a revert are three
different things on purpose.
__init__(operator, receipt_base_url); the URL must be http(s) and end in /.set_chain(chain_id, rpc_url, usdc) — operator only; usdc is stored lowercased.attest(receipt_id) -> attest_id — permissionless (D-66). attest_id is
sha256(json([receipt_id, checked_at, sender])); the same sender asking in the same block time reverts as a duplicate.get_attestation(attest_id), list_by_receipt(receipt_id) (JSON list, [] default),
get_chain(chain_id), get_config() (JSON: operator, receipt base URL, configured chain ids).Attestation) carries receipt_id, receipt_sha256, rail, chain_id, payment_tx,
payer, payee, amount_atomic, tool, upstream_status, billed, response_sha256, result,
observation, checked_at, requester.Two further contracts decide whether APIbase should refund a paid call. Each decides ONE claim, so that
each file stays small enough for D-67's 19 000 B rule (a single file for both claims measured 25 331 B;
D-80). Both pin the published refund policy in storage (set_policy(version, sha256), operator only; a
dispute while no policy is pinned reverts refund policy not published) and copy that pair into every
record, so a verdict names the policy text it was decided under. Validators recompute the claim from
published documents; there is no LLM and no judgement call.
A verdict never pays by itself. REFUND is an instruction to APIbase's refund worker, which recomputes the same rule, pays within the published window under its own caps, and publishes refunds/<dispute_id>.json; the refund is verifiable on Base from that document (on-chain confirmation, confirm_refund, is v1.1). Bradbury is a test network; the verdict moves real USDC only through that capped worker.
| claim | contract | recomputed from | REFUND when | RELEASE reasons (also in the record) | revert, nothing written |
|---|---|---|---|---|---|
upstream_failed_but_billed | ApibaseReceiptDispute.dispute_receipt(receipt_id) | the receipt {receipt_base_url}{receipt_id}.json | billed and upstream_status >= 500, and no earlier REFUND for the same payment_tx | not_billed; upstream_status_<n> (n < 500); refund_verdict_exists:<dispute_id> | receipt not readable, malformed (malformed receipt: <key>), unsupported rail |
paid_without_receipt | ApibasePaymentDispute.dispute_payment(chain_id, payment_tx) | the index {receipt_base_url}by-tx/<tx>.json, then the transaction on Base | the index lists no receipt and no refund, no earlier REFUND for the tx, the transaction succeeded and carries a USDC transfer to the chain's configured payee (the first such transfer fixes payer and amount_atomic) | already_refunded; receipt_exists:<id>; refund_verdict_exists:<dispute_id>; tx_not_found; tx_reverted; no_payment_to_apibase | index not readable (HTTP ≠ 200 or any shape but the published one), RPC failure, chain not configured |
An index that cannot be read is unmeasured, not "no receipt": the call reverts and writes nothing.
The sibling-URL contract. Receipts stay byte-immutable, so refund state lives at sibling URLs of the receipt base, never inside a receipt and never in a verdict record:
by-tx/<tx>.json — always answers 200 for a well-formed transaction hash:
{"schema": "apibase-receipt-index/1", "payment_tx": "<tx, lowercase>", "receipt_ids": [...], "refund_tx": "0x…" | null}.
Any other status or shape is an unreadable index.refunds/<dispute_id>.json — written once by the refund worker, after it paid:
{"schema": "apibase-refund/1", "dispute_id", "refund_tx", "chain_id", "to", "amount_atomic"}.
Nothing in these contracts reads it yet.refund_verdicts (payment_tx → dispute_id of the first REFUND) is kept per contract: a second REFUND
on the same payment_tx becomes RELEASE / refund_verdict_exists:<id>. A claim-1 REFUND and a claim-2
REFUND for one payment are excluded by the index (a payment with a receipt gets receipt_exists); the
residual case, a receipt published after a claim-2 REFUND and then disputed under claim 1, is stopped
by the refund worker's one-refund-per-payment_tx ledger.
Interface, both contracts: __init__(operator, receipt_base_url); set_policy(version, sha256)
(operator only); get_dispute(dispute_id), list_by_subject(receipt_id | payment_tx) (JSON list, []
default), list_recent() (JSON list of the last 200 dispute ids, newest last), get_config() (JSON:
operator, receipt base URL, policy version and sha-256; the payment contract adds the configured chain
ids). Receipt contract: dispute_receipt(receipt_id) -> dispute_id, permissionless;
dispute_id = sha256(json(["receipt", receipt_id, checked_at, sender])). Payment contract:
set_chain(chain_id, rpc_url, usdc, payee) (operator only; payee is APIbase's receiving wallet on that
chain, stored lowercased), get_chain(chain_id), dispute_payment(chain_id, payment_tx) -> dispute_id,
permissionless; dispute_id = sha256(json(["payment", chain_id, payment_tx, checked_at, sender])).
The record (Dispute, byte-identical in both files, immutable once written) carries dispute_id,
claim, receipt_id (empty for a payment claim), chain_id, payment_tx, payer, payee,
amount_atomic, policy_version, policy_sha256, verdict, reason, evidence_sha256 (the receipt's
or the index's SHA-256), checked_at, requester.
The validators of both contracts vote by whole-observation equality, as the attest does (D-76); a
validator whose own fetch fails votes False. test_dispute_shares_attest_helpers proves the helpers
(_validate_receipt, _error_observation, _rpc_request_body, _parse_transfers, _observe_rpc) are
verbatim copies of the attest's, because a GenVM contract is one file and cannot import another.
Not deployed yet: deployment from the merged commit's bytes is a later, separate step (D-67).
Run from this directory, with the isolated Python 3.12 toolchain of D-63 (the door uses ~/orchestra/glenv-babwp, with GENVM_VERSION=v0.6.0-rc10, GENVM_ALLOW_PRERELEASE=1):
The contract's version line # v0.3.0 must be line 1, with the # { "Depends": "py-genlayer:babwp9t3…" } line second (why): Bradbury Phase 2 runs the babwp runner as of 2026-10-28, and the documentation's 1jb45… id fails there (D-79).
python3 scripts/check_genvm_lint.py # genvm-lint check on all three contracts, with the named E014 warnings
python3 -m pytest tests/direct -v # direct-mode tests: mocks only, no network
gltest --network studionet tests/integration # live; skipped unless ATTEST_CONTRACT and APIBASE_RECEIPT_ID are set
genvm-lint check itself has no allowlist, so scripts/check_genvm_lint.py runs it with --json, once per
contract, and demands that the warning set be exactly the known false positives: the linter (0.11.1rc2) only
knows the v0.2 storage decorator name allow_storage, so it reports the classes decorated with the v0.3.0
gl.storage.allow as E014 - ChainConfig (line 40) and Attestation (line 50) in the attest, Dispute
(line 37) in apibase_receipt_dispute.py, ChainConfig (line 37) and Dispute (line 47) in
apibase_payment_dispute.py; its SDK-semantic validation, which builds the real schema, passes. Any other
shape is red.
tests/fixtures/rpc_receipt_real_base_usdc.json is a real eth_getTransactionReceipt answer from
https://mainnet.base.org (transaction 0xa7055e5e…bf58, block 52589696): two logs, one of them a USDC
transfer of 1 USDC; test_parse_transfers_golden_vector parses it.
x402-base. An MPP/Tempo receipt is refused (unsupported rail), not attested.billed: false is refused (not billed).not readable.response_sha256 and upstream_status are copied into the
record for whoever holds the response; the contract does not see it.response_mismatch (the
response differs from what was promised) and double_billed are deliberately absent: they cannot be
recomputed from a published document, and a witness that guesses is not a witness. Verdict records are
immutable; refund confirmation on chain is a later contract (v1.1).Every validator asks the same JSON-RPC URL, held in storage per chain id and replaceable by the
operator alone with set_chain. The contract therefore proves "the configured node says so". An
operator who points a chain at a node that lies can make it attest a transfer that did not happen.
The URL is public (get_chain), which makes the assumption auditable; letting each validator choose its
own node would remove it and is a different, later design. At deployment the Base endpoint is
https://mainnet.base.org. ApibaseReceiptDispute makes no such assumption: it asks no chain, only
APIbase's own receipt document; ApibasePaymentDispute makes it exactly as the attest does.
The contract file keeps short docstrings (gas, see the last paragraph of this section). The text below is the original
documentation from commit 774a184, verbatim.
APIbase Receipt Attest — a GenLayer Intelligent Contract.
WHAT THIS IS. A consensus-backed check that an APIbase paid-call receipt tells the truth about the
money. APIbase publishes, for each paid tool call, a small JSON receipt (`apibase-receipt/1`, see
README.md) that names a payer, a payee, an amount of USDC and the Base transaction that moved it.
Anyone may ask this contract about a receipt id (`attest(receipt_id)`); the leader fetches the
receipt from `receipt_base_url`, asks the configured Base JSON-RPC for that transaction's receipt,
and reports whether the chain holds a USDC `Transfer(payer -> payee, amount)` inside it. Validators
repeat both fetches themselves and agree only if they saw the same thing. The verdict is written
to storage as an `Attestation` nobody can edit afterwards.
WHAT THIS IS NOT. There is no LLM anywhere in this contract. It never makes a paid call to APIbase
and never spends anything: it reads one static JSON document and asks a chain node for one
transaction receipt. It does not attest that the tool's response was good, only that the payment
the receipt claims is the payment on the chain; `response_sha256` is carried into the record so a
holder of the response can check it themselves, nothing more. v0 covers ONE rail (`x402-base`) and
BILLED calls only (`billed is true`); an MPP/Tempo receipt, or a call that was not billed, is
refused with a named reason rather than attested as something it is not.
TRUST ASSUMPTION, STATED. The chain is reached through ONE JSON-RPC URL per chain id, held in
storage and updatable by the operator alone (`set_chain`). Every validator asks that same URL, so
the contract proves "the configured node says so", not "the chain says so": an operator who points
`rpc_url` at a node that lies can make the contract attest a transfer that did not happen. The
URL is public (`get_chain`) so the assumption is auditable; removing it would mean validators each
choosing their own node, which is a different and later design.
WHY A CUSTOM VALIDATOR, NOT `strict_eq`. `strict_eq` compares the leader's whole output to the
validator's byte for byte. The receipt observation carries fields that can legitimately differ
between two honest nodes reading the same chain (a log list that includes unrelated contracts'
events, RPC extras) and, worse, it cannot distinguish "the validator saw something different" from
"the validator could not see anything". `_judge` below is the one deterministic function leader
and validator both call, so the verdict rule lives in exactly one place; the validator agrees iff
its whole observation equals the leader's, because the record is built from all of it (D-76).
CONSENSUS SEMANTICS, NAMED EXPLICITLY. Three different outcomes must not be confused
(CLAUDE.md invariant 1: a missing measurement is never a pass, and never silently a fail):
* `FAIL` means the instrument worked and the claim is false: the transaction exists but reverted
(`tx_reverted`), or it succeeded and holds no matching USDC transfer (`no_matching_transfer`).
* `NOT_FOUND` means the node answered and does not know the transaction (`tx_not_found`). It is
recorded, not hidden: a payment that the configured node cannot see is evidence of its own.
* A revert (no record) means there is nothing to attest: the receipt is not readable at APIbase,
or it is a shape this v0 contract refuses (`unsupported rail`, `not billed`, `chain not
configured`, `malformed receipt`). Nobody is told "the payment is false" about a receipt that was
never in scope.
Separately, a validator whose OWN fetch raises returns `False` without constructing any verdict:
"nobody could check" must not be turned into "the claim is false", and GenVM rotates the leader.
FIELD NAME NOTE. `genlayer.gl.nondet.web.Response` has `status`, not `status_code` (the same
finding as the witness contract next door; running its direct tests against the pinned SDK is
what showed it). This contract uses `response.status`.
See the module docstring. Storage: `operator` (fixed at construction), `receipt_base_url`
(receipts live at `{receipt_base_url}{receipt_id}.json`), `chains` (chain id -> `ChainConfig`,
operator-updatable), `records` (attest_id -> `Attestation`, append-only), `index`
(receipt_id -> JSON list of attest_ids; a list inside a `TreeMap` is kept as a JSON string, the
same workaround the witness contract uses).
set_chainOperator-only: configure (or replace) the JSON-RPC endpoint and USDC address for a
chain id. `usdc` is stored lowercased.
attestRequest one consensus-backed attestation. PERMISSIONLESS: any sender may ask, and the
question has one answer per receipt, so who asks adds nothing. Reverts before consensus for
a malformed `receipt_id` or a duplicate `attest_id`; after consensus for the cases the
module docstring lists. Returns the `attest_id`.
get_attestationThe published record for `attest_id`; reverts for an unknown id — a missing record is a
caller error to surface, not an empty value to fabricate.
list_by_receiptJSON list of every `attest_id` published for `receipt_id`, in request order, or `"[]"`.
get_chainThe configured endpoint and USDC address for `chain_id`; reverts for an unknown chain.
get_configJSON: operator, receipt_base_url, configured chain ids (sorted).
The deployed bytes are the committed file bytes at the deploy commit, verifiable with sha256sum integrations/genlayer-apibase-attest/contracts/apibase_receipt_attest.py at that commit against the code shown by the
explorer. The Bradbury per-transaction gas cap is 16 777 216 (measured 2026-10-20) and gas is about 778 per byte, so
every contract file in this repo is kept at or under 19 000 bytes and the deploy gate is eth_estimateGas <= 16 000 000
with the exact deploy arguments. tests/direct/test_apibase_receipt_attest.py holds the size ceiling, the docs-only AST
comparison against 774a184, and the docstring limits.