Skip to content

feat(samples): add Tenzro DID-based settlement sample#256

Closed
hilarl wants to merge 1 commit intogoogle-agentic-commerce:mainfrom
hilarl:tenzro/did-settlement-sample
Closed

feat(samples): add Tenzro DID-based settlement sample#256
hilarl wants to merge 1 commit intogoogle-agentic-commerce:mainfrom
hilarl:tenzro/did-settlement-sample

Conversation

@hilarl
Copy link
Copy Markdown

@hilarl hilarl commented May 2, 2026

feat(samples): add Tenzro DID-based settlement sample

Summary

Adds a new runnable Python sample under
code/samples/python/tenzro_did_settlement/ that demonstrates the AP2
mandate chain (IntentMandateCartMandatePaymentMandate)
running against a DID-based identity layer, with an on-chain
delegation primitive and a STARK-based settlement commitment.

The sample uses TDIP and
did:tenzro: as one concrete instance of a DID-based identity layer,
but the structure is identity-system-agnostic — the chain-specific glue
is isolated in a single thin tenzro_client.TenzroClient module so the
same sample skeleton works against did:web, did:key, did:ion,
did:eth, or any other W3C DID method whose chain exposes a comparable
delegation primitive and a settlement-proof primitive.

What's in the sample

code/samples/python/tenzro_did_settlement/
├── README.md           # sample-level explanation
├── main.py             # ~440-line runnable end-to-end flow
├── tenzro_client.py    # thin Tenzro JSON-RPC client (~210 lines)
├── requirements.txt    # pinned deps: requests, pydantic, pytest, responses
└── test_main.py        # pytest end-to-end test against a stub RPC

The flow exercises all four nested validation ceilings that an AP2
mandate chain can be subject to in a DID-based deployment:

  1. AP2 IntentMandate constraintsmax_amount, allowed
    merchants, allowed SKUs, expiry. (Plain AP2 SDK semantics.)
  2. AP2 CartMandate consistency — total = sum of line items,
    parent intent matches, expiry. (Plain AP2 SDK semantics.)
  3. TDIP DelegationScope::enforce_operation — a protocol-level
    ceiling set when the machine identity was registered
    (max_transaction_value, allowed_operations,
    allowed_payment_protocols, allowed_chains, time_bound).
  4. TDIP runtime SpendingPolicy::check — an execution-level
    ceiling that the agent's runtime registry enforces (e.g. daily-spend
    windows). Mutable; orthogonal to the protocol ceiling.

Settlement: a Plonky3 STARK over the settlement AIR is generated via
tenzro_createZkProof; its 32-byte SHA-256 commitment matches the
on-chain ZkCommitmentRegistry format so the EVM ZK_VERIFY
precompile becomes an O(1) HashSet lookup.

The PaymentMandate.payment_method.supported_methods is
tenzro:micropayment-channel, dispatching to a Tenzro per-message
billing channel.

The sample also adds a one-paragraph subsection
"DID-based identity layer (example: TDIP)" to
docs/ap2/implementation_considerations.md (see the diff below).

Why this fills a gap

The existing samples in code/samples/python/scenarios/a2a/ are
excellent demonstrations of card-based and x402-based flows, but they
use opaque identifiers for the user / agent / merchant parties. They do
not show what AP2 looks like when those identifiers are first-class
DIDs with on-chain delegation enforcement and verifiable-credential-
style scope binding.

AP2's mandate types are identity-system-agnostic — the principal,
agent, and merchant identifiers are opaque strings. This PR makes
that explicit by giving implementers a runnable, end-to-end example
of layering AP2 on top of a DID method.

What this PR does NOT touch

  • No spec changes. docs/ap2/specification.md, payment_mandate.md,
    flows.md, etc. are FIDO Alliance's responsibility now.
  • No SDK changes. code/sdk/python/ap2/ is untouched. The sample
    consumes the SDK exactly as published.
  • No new external dependencies. The sample uses requests,
    pydantic (a transitive dep of ap2 already), and the test uses
    responses for HTTP mocking. All deps are publicly available on
    PyPI; nothing private, nothing wallet-secret.

Test plan

The sample ships with test_main.py, which runs the full
main.run() flow end-to-end against a stub Tenzro JSON-RPC backend
mocked with responses:

cd code/samples/python/tenzro_did_settlement
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
pip install -e ../../../sdk/python/ap2   # AP2 SDK from this repo
pytest test_main.py -v

Expected: 5 tests pass.

  • test_intent_and_cart_build_and_round_trip — AP2 SDK objects
    build correctly and round-trip into the Tenzro VDC envelope shape.
  • test_local_validation_passes_within_all_ceilings — all four
    ceilings green-light a within-budget cart.
  • test_local_validation_rejects_when_daily_window_exceeded — the
    SpendingPolicy ceiling correctly fails when the daily window is
    too small.
  • test_compute_zk_commitment_matches_expected_shape — the SHA-256
    commitment matches Tenzro's compute_zk_commitment shape.
  • test_run_end_to_end_against_stub_rpc — the full main.run()
    flow drives the stub RPC through resolve_did,
    ap2ValidateMandatePair, and createZkProof.
  • test_run_offline_mode_skips_rpc--offline flag works.

The sample also runs against the live testnet at rpc.tenzro.network
without modification — the read-only RPCs (DID resolution, mandate
validation, ZK proof generation) require no funded keys; the channel
update is intentionally simulated because that step requires a
pre-opened, funded channel.

CLA acknowledgment

TODO(@hilarl) — replace this block with a confirmation reference
from https://cla.developers.google.com/ (individual or covered by
employer corporate CLA) before submitting the PR.

I have signed the Google CLA. This sample is a Tenzro-funded
contribution to the AP2 ecosystem; the copyright headers in
tenzro_client.py and main.py declare Apache-2.0 licensing,
matching the rest of the repository.

AI assistance disclosure

Author: Hilal Agil <hilal@tenzro.com>, GitHub
@hilarl.

Parts of this contribution were AI-assisted via Claude Code. All
technical decisions — the choice to layer AP2 on top of TDIP, the
mapping from AP2's three mandate types onto Tenzro's IntentMandate /
CartMandate VDC envelope, the use of the settlement Plonky3 AIR for
the commitment, the four-ceiling validation model — are human-authored
and consistent with Tenzro's production AP2 validator
(tenzro_payments::ap2::MandateValidator::validate_with_delegation_and_policy).
The drafting of boilerplate (docstrings, repetitive Pydantic model
construction, test scaffolding) was AI-assisted; every file was
reviewed and tested by the author before submission.

Checklist

  • Google CLA signed (placeholder above must be filled in).
  • Sample under code/samples/python/tenzro_did_settlement/ only.
  • requirements.txt pinned to specific versions.
  • Test passes locally (pytest test_main.py).
  • No changes to docs/ap2/specification.md (FIDO scope).
  • No changes to code/sdk/python/ap2/ (SDK scope).
  • One small additive change to
    docs/ap2/implementation_considerations.md (descriptive doc, not
    spec) — single paragraph cross-referencing the sample. Reviewers
    who consider that file out of scope can drop it; the sample
    stands on its own.
  • Apache-2.0 copyright headers on all new source files, matching
    the existing repo convention.
  • Sample runs against the live rpc.tenzro.network testnet
    end-to-end, no environment variables required.
  • AI-assistance disclosure included.

DCO

Signed-off-by: Hilal Agil <hilal@tenzro.com>

Adds a runnable Python sample demonstrating the AP2 mandate chain (IntentMandate → CartMandate → PaymentMandate) running against a DID-based identity layer with on-chain delegation and a STARK settlement commitment. Uses TDIP/did:tenzro: as one concrete instance; the structure is identity-system-agnostic.

Signed-off-by: Hilal Agil <hilaal@gmail.com>

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
Signed-off-by: Hilal Agil <hilaal@gmail.com>
@hilarl hilarl requested a review from a team as a code owner May 2, 2026 10:53
@google-cla
Copy link
Copy Markdown

google-cla Bot commented May 2, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive sample for DID-based settlement using the Tenzro Decentralized Identity Protocol (TDIP) and the AP2 SDK, featuring mandate chain construction, validation against four distinct ceilings, and a simulated ZK-proof settlement. The implementation includes a Tenzro JSON-RPC client and end-to-end tests. Feedback focuses on improving the ZK proof witness generation by utilizing the full hash for service proofs to maintain entropy and suggests replacing magic numbers in the RPC client with named constants for better maintainability.

service_proof_hash = hashlib.sha256(
payment_mandate.model_dump_json().encode("utf-8")
).digest()
service_proof_int = int.from_bytes(service_proof_hash[:4], "little")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

Truncating the service_proof_hash to 4 bytes (32 bits) reduces the entropy of the witness field. Since the KoalaBear field prime is approximately 31 bits ($2^{31} - 2^{24} + 1$), a 4-byte integer can still exceed the prime, requiring the RPC to handle the modulus anyway. Using the full hash and letting the RPC perform the reduction would provide a stronger binding between the mandate and the settlement proof without increasing complexity, as Python handles large integers automatically.

Suggested change
service_proof_int = int.from_bytes(service_proof_hash[:4], "little")
service_proof_int = int.from_bytes(service_proof_hash, "little")

if "error" in envelope and envelope["error"] is not None:
err = envelope["error"]
raise TenzroRpcError(
code=int(err.get("code", -32603)),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

The magic number -32603 is the standard JSON-RPC 'Internal error' code. Consider defining it as a constant (e.g., JSONRPC_INTERNAL_ERROR = -32603) to improve readability and maintainability.

@hilarl hilarl closed this May 2, 2026
@hilarl hilarl deleted the tenzro/did-settlement-sample branch May 2, 2026 11:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant