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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//! HTTP provider builders for the `decdn` CLI and SDK clients — a
//! wallet-filled builder that signs and sends transactions, and a wallet-free
//! builder for reads that run before a keystore is unlocked — plus the
//! retryable-vs-permanent classifier for a failed chain read
//! ([`is_permanent_contract_error`](crate::provider::is_permanent_contract_error),
//! [`is_permanent_rpc_error`](crate::provider::is_permanent_rpc_error)).
//!
//! It lives in the SDK so any client of the `cdn/client/v1` data plane — not
//! just the `decdn` CLI bin crate — can build the signing provider it opens, tops
//! up, closes and reclaims payment pools with. The daemon's boot chain reads and the
//! client's registry discovery classify a typed chain error with the same
//! classifier.
//!
//! Both builders install ring as the process-wide rustls provider first
//! ([`decdn_common::tls::install_crypto_provider`]): alloy's HTTP transport
//! builds a reqwest client, which panics without one. An SDK host that never
//! installs a provider itself still gets a working chain client.
use EthereumWallet;
use ;
use PrivateKeySigner;
use Context;
/// Build a wallet-filled HTTP provider that signs and sends transactions as
/// `signer`. It reads the pending nonce on every send, so it stays correct
/// after a send that pins its own nonce ([`crate::buyer_pool::top_up`]) or a
/// transaction sent from elsewhere.
///
/// # Errors
///
/// Fails if `rpc_url` is not a valid URL. The error never echoes the value,
/// which can carry an API key.
// `use<>` pins the returned provider to capture *no* input lifetimes: it owns
// a clone of `signer` and the parsed URL, so it is genuinely `'static`. Without
// the precise-capturing bound, Rust 2024's RPIT rules over-capture `&signer`,
// which would stop callers (e.g. `setup`'s USDC swap venue) from handing the
// provider to APIs that need `P: 'static` (`DynProvider::erased`).
/// Build a wallet-free HTTP provider for read-only calls. It lets a command
/// read chain state before it unlocks a keystore, so a check that fails asks
/// for no password.
///
/// # Errors
///
/// Fails if `rpc_url` is not a valid URL. The error never echoes the value,
/// which can carry an API key.
/// Parse the configured RPC endpoint into the type the caller's
/// `connect_http` takes. It is generic so that type is inferred at each call
/// site; naming `Url` would need `alloy::transports`, which this crate's alloy
/// feature set does not expose.
///
/// # Errors
///
/// Fails if `rpc_url` is not a valid URL. The value is never echoed into the
/// error: an `rpc_url` secret commonly lives in the path/query, which userinfo
/// redaction wouldn't scrub, so the value is hidden entirely (in the style of
/// `config validate`'s `<redacted> (N chars)`).
/// Whether a failed contract call is deterministic — the same call fails the
/// same way however often it is repeated, so retrying it only spends a retry
/// budget before reporting the error it was always going to report.
///
/// Only a transport failure can be transient; [`is_permanent_rpc_error`]
/// decides those. Every other variant is a contract-level fault that repeats
/// identically: no contract at the configured address
/// ([`ZeroData`](alloy::contract::Error::ZeroData) — the typo'd-address case),
/// an ABI that does not match the binding, an unknown function or selector, a
/// failed deployment.
///
/// The classifier is for reads: it calls every non-transport failure permanent,
/// including a pending-transaction error, which a write path must judge on its
/// own.
/// Whether a failed JSON-RPC request is deterministic, so a retry cannot
/// succeed.
///
/// This is a denylist — retry unless the failure is known to repeat. The
/// permanent failures are the ones a request carries with it:
///
/// - a JSON-RPC error response that is a revert (code `3`, or a message that
/// names a revert, in any case), or an invalid request, unknown method or
/// invalid params (`-32600`, `-32601`, `-32602`);
/// - an HTTP 4xx other than 408 and 429 — a wrong path, an unauthorized or
/// expired API key. An HTTP error whose body is a JSON-RPC error reaches
/// here as an error response and is judged by its code, not its status;
/// - a request that could not be serialized, or that the transport rejects
/// locally.
///
/// Every other failure is transient: refused connections, resets, timeouts,
/// truncated bodies, HTTP 5xx, a 4xx that carries `Retry-After`, and every other
/// JSON-RPC error response. Providers report rate limits and upstream outages
/// as JSON-RPC error responses over HTTP 200, with codes that differ by
/// provider — for example `1` "no available upstreams" and `19` "Temporary
/// internal error".
///
/// alloy's own retry checks are allowlists, and each misses a transient class.
/// `ErrorPayload::is_retry_err` names some providers' rate-limit codes, but not
/// outage codes such as `1` and `19`. `TransportErrorKind::is_retry_err` accepts
/// only a few transport kinds, so it rejects an ordinary refused connection or
/// reset.