jcp-x402: demonstrate the settlement coupling in the runnable example #25

Merged
dcharlot merged 1 commit from jcp/x402-example-settlement into main 2026-07-18 04:34:26 -04:00

View file

@ -8,7 +8,7 @@
use ed25519_dalek::SigningKey;
use jcp_schema::{Did, Funds, Verdict};
use jcp_x402::{ingest_requirements, Gated, X402Profile, X402Requirements};
use jcp_x402::{ingest_requirements, Gated, X402Profile, X402Requirements, X402Settlement};
use serde_json::json;
fn main() {
@ -64,7 +64,42 @@ fn main() {
let eurc = X402Requirements { asset: "EURC".into(), max_amount: 100, ..req.clone() };
println!("charge in EURC (grant is USDC) → {}", verdict(&profile.authorize(&eurc, 1)));
// 6. Every decision sealed a JCR-1 receipt that verifies under the steward
// 6. THE COUPLING. The host settles the authorized charge on chain and gets
// back a transaction; the meter reports what the paid compute actually
// consumed. Sealing them together produces the artifact neither side has:
// the payment rail knows the transaction but not the energy, an energy
// meter knows the joules but not the transaction.
let settled = X402Settlement {
transaction: Some("0xdeadbeefcafe".into()),
network: "base".into(),
payer: Some("0xAgentWallet".into()),
};
let measured_uj = 5_812; // what the meter read, not the 6000 µJ estimate
let g = profile.seal_settlement(&req, &settled, measured_uj);
println!("\nsettled {} → sealed {}", settled.transaction.as_deref().unwrap_or("-"), verdict(&g));
// A third party verifies the binding without trusting the gateway: it
// recomputes the coupling id from the settlement record it holds and
// checks it against the receipt's payload_hash (jcp/2, SPEC §8).
let core = jcp_receipt::extract(&g.receipt, profile.steward(), &profile.steward_pubkey())
.expect("settlement receipt verifies");
let expected = settled.coupling_id(&req.resource, measured_uj).to_hex();
println!(
" coupling verified: {} (tx + {} µJ bound into payload_hash {}…)",
core.payload_hash.as_deref() == Some(expected.as_str()),
measured_uj,
&expected[..16]
);
// Re-pointing the receipt at a different transaction — or a different
// energy figure — yields a different id, so the binding cannot be moved.
let other = X402Settlement { transaction: Some("0xfeed".into()), ..settled.clone() };
println!(
" re-point to another tx → id matches: {}",
core.payload_hash.as_deref() == Some(other.coupling_id(&req.resource, measured_uj).to_hex().as_str())
);
// 7. Every decision sealed a JCR-1 receipt that verifies under the steward
// key — a third party trusts the decision, not the gateway.
let steward = profile.steward().to_string();
let pk = profile.steward_pubkey();