Axiomatic ProofKit (Python) is the client SDK for building and publishing p1 Proof-of-Valuation (PoVal) attestations on Algorand.
The goal is a simple, trust-minimized flow:
- Build a canonical p1 payload (JCS/ACJ-style JSON).
- Publish it on-chain as the note of a 0-ALGO self-transaction.
- Keep signing and key management on the client side.
- Allow anyone to independently recompute and verify what was published.
This SDK does not depend on any Axiomatic backend.
Who is this for?
Use axiomatic_proofkit if you:
- Build Python back-ends that need to issue PoVal attestations on Algorand.
- Integrate model-based valuations into RWA / tokenization / lending flows.
- Want deterministic, auditable
p1payloads aligned with the Node SDKs.
If you only need to verify on-chain notes, see axiomatic_verifier.
Typical use cases
-
Real estate tokenization
- Compute a fair value
vand intervalufor a property. - Build a
p1attestation withaxiomatic_proofkit. - Anchor the PoVal proof on Algorand TestNet/MainNet as a 0-ALGO self-tx.
- Compute a fair value
-
On-chain collateral / lending
- Issue PoVal attestations for assets posted as collateral.
- Use the on-chain note + txid as input for your credit/risk policies.
-
Back-office & audit tooling
- Periodically issue updated valuations.
- Keep a verifiable trail of model versions, hashes and timestamps.
Installation
Requires Python 3.10+.
pip install axiomatic_proofkit
pip install py-algorand-sdk
Exposed API
From axiomatic_proofkit:
build_p1(...)canonical_note_bytes_p1(p1)assert_note_size_ok(p1, max_bytes=...)build_canonical_input(raw_input, allowed_keys=...)compute_input_hash(canonical_input, allowed_keys=...)publish_p1(p1, from_addr=..., sign=..., network=..., algod=..., wait_rounds=...)PublishError
These are the only functions you need for typical integrations.
p1 structure
build_p1 returns a dict of the form:
p1 = {
"s": "p1", # schema marker
"a": "re:EUR", # asset tag (e.g. "re:EUR")
"mv": "v2", # model version
"mh": "", # model hash (hex, optional)
"ih": "...", # input hash (hex, required)
"v": 550000.0, # point estimate (e.g. value)
"u": [520000.0, 580000.0], # uncertainty range [low, high]
"ts": 1762609210, # unix epoch seconds
}
The object is normalized and serialized using a JCS/ACJ-style canonical JSON encoder for stable hashing and cross-language parity.
Quickstart: publish a p1 to Algorand TestNet
The following example mirrors the internal smoke test used to validate the SDK.
1. Environment
Create a .env file next to your script:
ALGORAND_MNEMONIC=your 25-word mnemonic for a TestNet account
ALGORAND_NETWORK=testnet
# Optional: override default node
# ALGOD_URL=https://testnet-api.algonode.cloud
For production, use a secure signing setup (wallet, KMS, HSM, etc.). This example uses a mnemonic only for demonstration.
2. Example script (publish_p1_example.py)
import os
import sys
import json
import base64
from pathlib import Path
from algosdk import account, mnemonic, encoding, transaction
from algosdk.v2client.algod import AlgodClient
from axiomatic_proofkit import (
build_p1,
canonical_note_bytes_p1,
assert_note_size_ok,
build_canonical_input,
compute_input_hash,
publish_p1,
)
ROOT = Path(__file__).resolve().parent
def load_env_from_file(env_path: Path) -> None:
try:
for line in env_path.read_text(encoding="utf-8").splitlines():
if not line or line.strip().startswith("#") or "=" not in line:
continue
k, v = line.split("=", 1)
k, v = k.strip(), v.strip()
if k and (k not in os.environ):
os.environ[k] = v
except FileNotFoundError:
pass
load_env_from_file(ROOT / ".env")
MNEMONIC = os.getenv("ALGORAND_MNEMONIC")
NETWORK = (os.getenv("ALGORAND_NETWORK") or "testnet").strip()
ALGOD_URL = (
os.getenv("ALGOD_URL")
or (
"https://mainnet-api.algonode.cloud"
if NETWORK == "mainnet"
else "https://testnet-api.algonode.cloud"
)
).strip()
if not MNEMONIC:
print("❌ ALGORAND_MNEMONIC missing.", file=sys.stderr)
sys.exit(1)
def compute_input_hash_from_golden() -> str:
"""
If golden/p1_input.json exists, compute its canonical ACJ/JCS hash.
Otherwise return a deterministic placeholder for the smoke test.
"""
golden_dir = ROOT / "golden"
inp_path = golden_dir / "p1_input.json"
if not inp_path.is_file():
return "0" * 64
try:
raw = json.loads(inp_path.read_text(encoding="utf-8"))
except Exception as e:
print(f"⚠️ Unable to read golden/p1_input.json: {e}")
return "0" * 64
allowed_keys = list(raw.keys())
cin = build_canonical_input(raw, allowed_keys=allowed_keys)
ih = compute_input_hash(cin, allowed_keys=allowed_keys)
print(f"✅ Golden input hash: {ih}")
return ih
def main() -> None:
# 1) Derive private key and address from mnemonic (demo only)
sk = mnemonic.to_private_key(MNEMONIC)
addr = account.address_from_private_key(sk)
# 2) Compute input hash (from golden file if available)
ih = compute_input_hash_from_golden()
# 3) Build canonical p1
p1 = build_p1(
asset_tag="re:EUR",
model_version="v2",
model_hash_hex="",
input_hash_hex=ih,
value_eur=550_000,
uncertainty_low_eur=520_000,
uncertainty_high_eur=580_000,
timestamp_epoch=None, # use "now"
)
# 4) Inspect note size and hash
note_bytes, note_sha, note_len = canonical_note_bytes_p1(p1)
assert note_bytes is not None # just to show it's a bytes-like object
assert_note_size_ok(p1)
print(f"P1 note size: {note_len} bytes | sha256: {note_sha}")
# 5) Client-side signer: keep your keys local
def sign(unsigned_bytes: bytes) -> bytes:
"""
unsigned_bytes: msgpack-encoded UnsignedTransaction.
Return: msgpack bytes of the SignedTransaction.
"""
tx_dict = encoding.msgpack.unpackb(unsigned_bytes)
txn = transaction.Transaction.undictify(tx_dict)
stx = txn.sign(sk)
stx_b64 = encoding.msgpack_encode(stx) # base64(msgpack signed)
return base64.b64decode(stx_b64) # raw msgpack bytes
# 6) Algod client (Algonode by default)
algod = AlgodClient("", ALGOD_URL)
# 7) Publish p1 as a 0-ALGO self-transaction note
res = publish_p1(
p1,
network=NETWORK,
algod=algod,
from_addr=addr,
sign=sign,
wait_rounds=4,
)
print("PUBLISHED:")
print(json.dumps(res, indent=2))
if __name__ == "__main__":
main()
Run:
python publish_p1_example.py
You should see a JSON result with:
txidexplorer_urlnote_sha256note_sizenetwork
You can open the explorer URL and inspect the on-chain note.
Publish → verify (end-to-end)
To verify a published p1 attestation from Python, use axiomatic_verifier:
from axiomatic_verifier import verify_tx
res = verify_tx(
txid="YOUR_TXID",
network="testnet",
# or your own indexer:
# indexer_url="https://testnet-idx.algonode.cloud",
max_skew_past_sec=3600,
max_skew_future_sec=300,
)
print(res["verified"], res.get("reason"))
This checks:
- that the note is structurally valid,
- that canonicalization and
sha256match, - and that the timestamp
tsis within your allowed time window.
Security notes
-
The
signfunction is fully controlled by you. -
For real integrations, replace inline mnemonic signing with:
- wallet connectors,
- custodial services,
- HSM/KMS or cloud KMS.
-
The SDK never sends your secrets anywhere and does not rely on Axiomatic servers.
This is an early-access SDK: feedback and issues are very welcome.
Metadata
Release files for axiomatic-proofkit 0.1.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| axiomatic_proofkit-0.1.4.tar.gz | 7.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| axiomatic_proofkit-0.1.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 16.3 kB
Release files / axiomatic_proofkit-0.1.4.tar.gz
| Download URL | axiomatic_proofkit-0.1.4.tar.gz |
|---|---|
| Size | 7.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e33c48a552c7796552fee7cb9f550efa89c4fb6ad7ec24ba9b6be3378a9a52dd
|
|
BLAKE2b-256 checksum How to use checksums |
a667b1a62bf00f37cfb0a16a7a482aca3054dd3c7938d690e7e7628d567e9762
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|
Release files / axiomatic_proofkit-0.1.4-py3-none-any.whl
| Download URL | axiomatic_proofkit-0.1.4-py3-none-any.whl |
|---|---|
| Size | 8.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
919f1cc149dafa0f2a609e2719ca0f400f1b2d19f47119eda4cd978236e20275
|
|
BLAKE2b-256 checksum How to use checksums |
4f21c345cefa1527f1bb0fc01d06016f979d7728f70cf11188df477db3c43f68
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|