1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//! Machine Payments Protocol (MPP / Tempo) integration for polychrome.
//!
//! This is the polychrome-specific composition layer: the control-plane
//! payment proxy chokepoint, the wallet self-service view renderers, and the
//! env-driven [`config`] that assembles the standalone `polyc-*` payment
//! primitive crates into the shape polychrome's control plane needs (see
//! issue #717's crate map). It stays `publish = false` — a third party
//! building their own agent depends on the primitive crates directly instead:
//!
//! - **Outbound** 402-gated payment client (`TempoProvider`-backed, pays a
//! `PAYMENT-REQUIRED` challenge via the MPP `PaymentMiddleware`/`PaymentExt`
//! path, plus the per-call spend-cap wrapper, chain-id pinning guard,
//! settlement-token balance reads, and verified explorer-link resolution) now
//! lives in the standalone `polyc-payments-client` crate; [`PaymentsConfig`](config::PaymentsConfig)
//! composes its `OutboundConfig` from its own broader configuration and
//! delegates to it ([`PaymentsConfig::resolve_client`](config::PaymentsConfig::resolve_client),
//! [`PaymentsConfig::resolve_outbound_client`](config::PaymentsConfig::resolve_outbound_client)).
//! - **Inbound**: 402 challenge issuance and credential verification lives in
//! the standalone `polyc-payments-server` crate. This deployment mounts no
//! inbound gate, so this crate composes nothing for it. A consumer that
//! wants the inbound server depends on `polyc-payments-server` directly.
//!
//! The control-plane payment proxy ([`proxy`]) gates every paid fetch
//! (approval binding, SSRF, spend cap, budget) and renders the caller-facing
//! wallet self-service views ([`view`]); `polyc_wallet_delegation::provision`
//! mints and provisions a delegated key when a caller links a wallet.
//! Configuration for all of this is loaded from the environment via
//! [`config`].
//!
//! The full non-custodial Tempo wallet-delegation lifecycle — `keys.toml`
//! parsing, TIP-1053 witness binding, scoped-key provisioning, and secret
//! custody — lives in the standalone `polyc-wallet-delegation` crate; this
//! crate composes it into polychrome's own control-plane proxy and wallet
//! self-service views.
//!
//! The default network is Tempo's Moderato testnet
//! ([`MODERATO_RPC_URL`], chain id [`MODERATO_CHAIN_ID`]).
/// Settlement-amount units: dollars ↔ base units + reader-facing rendering.
/// Configuration loaded from the environment for both payment directions.
/// Control-plane payment proxy core (approval-bind + SSRF + budget + fetch).
/// Pure wallet self-service views (state machine + tool-result renderings).
/// Force-register this crate's Prometheus settlement-amount counter.
///
/// Makes `polychrome_settlement_amount_unreadable_total` appear in a
/// `/metrics` scrape — with every bounded `direction` × `reason` child
/// zero-valued — before any receipt has failed to read, so an unread amount
/// shows up as a rate on an existing series rather than a series appearing
/// from nowhere. Idempotent (backed by a `OnceLock`); call once at process
/// startup, alongside any other crate's own `init_metrics`.
/// Chain id of the Tempo Moderato testnet.
///
/// Used to build the inbound server challenge; outbound validates the chain id
/// from the challenge rather than asserting this value.
pub const MODERATO_CHAIN_ID: u64 = 42431;
/// Default JSON-RPC endpoint for the Tempo Moderato testnet.
pub const MODERATO_RPC_URL: &str = "https://rpc.moderato.tempo.xyz";
/// Default block-explorer base for the Tempo Moderato testnet.
///
/// The mainnet `explore.tempo.xyz` host does NOT index testnet transactions or
/// addresses, so any payer/settlement link must default to this testnet host.
/// The single source both the inbound config default and the harness payer link
/// resolve from.
pub const MODERATO_EXPLORER_URL: &str = "https://explore.testnet.tempo.xyz";
/// Returns the default RPC endpoint used when no override is configured.
pub const
/// Hard ceiling on how long a caller-delegated access key may stay valid, in
/// seconds (one year).
///
/// Polychrome is **non-custodial**: a linked wallet's funds stay in the caller's
/// own onchain wallet, and the control plane holds only a spend- and
/// call-scoped *delegation* the caller authorized from their passkey — never a
/// balance. This ceiling is what keeps that delegation from becoming
/// open-ended: every minted authorization expires at or before `now + this`, so
/// authority the caller granted lapses on its own and must be re-authorized by a
/// fresh passkey approval (re-link). Minting clamps to this bound and the
/// passkey can only sign an expiry at or under the minted one, so no
/// configuration or client can hold spend authority indefinitely.
///
/// A year is long enough that a linked wallet keeps working across normal use
/// without a re-link. The bound, not its length, carries the non-custodial
/// property. A caller who wants a shorter key sets
/// `SpendPolicy.max_lifetime_secs`, which narrows the minted lifetime and can
/// never widen it.
///
/// The length still matters for one case. An unlink stops local spend at once,
/// because the control plane deletes its own copy of the key. The onchain
/// authorization outlives that deletion. It stands until it expires, unless
/// the person signs the hard-revoke ceremony (TIP-1011 `revokeKey`) from their
/// own wallet. The control plane holds no root key and cannot submit that
/// revocation itself. So this ceiling also sets how long an unlinked but
/// un-revoked key stays spendable onchain.
pub const MAX_DELEGATION_LIFETIME_SECS: u64 = 365 * 86_400;