Skip to main content

r402_casper/
networks.rs

1//! Well-known Casper network definitions and token deployments.
2//!
3//! This module provides static network metadata and the wCSPR (CEP-18)
4//! deployments r402 ships with.
5
6use std::sync::LazyLock;
7
8use r402_core::chain::NetworkInfo;
9
10use crate::chain::{CasperChainReference, CasperTokenDeployment, ContractPackageHash};
11use crate::motes::CSPR_DECIMALS;
12
13/// Well-known Casper networks with their names and CAIP-2 identifiers.
14///
15/// Casper uses its chain name as the CAIP-2 reference, so the `name` and
16/// `reference` fields coincide.
17pub static CASPER_NETWORKS: &[NetworkInfo] = &[
18    NetworkInfo {
19        name: "casper",
20        namespace: "casper",
21        reference: "casper",
22    },
23    NetworkInfo {
24        name: "casper-test",
25        namespace: "casper",
26        reference: "casper-test",
27    },
28];
29
30/// EIP-712 domain `name` of the wCSPR CEP-18 contract.
31pub const WCSPR_NAME: &str = "Wrapped CSPR";
32
33/// EIP-712 domain `version` of the wCSPR CEP-18 contract.
34pub const WCSPR_VERSION: &str = "1";
35
36/// wCSPR testnet package hash.
37///
38/// Verify: <https://testnet.cspr.live/contract-package/3d80df21ba4ee4d66a2a1f60c32570dd5685e4b279f6538162a5fd1314847c1e>
39const WCSPR_TESTNET_PACKAGE: &str =
40    "3d80df21ba4ee4d66a2a1f60c32570dd5685e4b279f6538162a5fd1314847c1e";
41
42/// Well-known wCSPR deployments on Casper networks.
43///
44/// Only networks with an x402-enabled CEP-18 deployment appear here. Chains
45/// without a built-in entry are still fully usable — construct a
46/// [`CasperTokenDeployment`] directly with the package hash you operate.
47#[allow(
48    clippy::expect_used,
49    reason = "hardcoded package hashes are validated at crate build time by the unit tests"
50)]
51static WCSPR_DEPLOYMENTS: LazyLock<Vec<CasperTokenDeployment>> = LazyLock::new(|| {
52    vec![CasperTokenDeployment::new(
53        CasperChainReference::CASPER_TEST,
54        WCSPR_TESTNET_PACKAGE
55            .parse::<ContractPackageHash>()
56            .expect("built-in wCSPR testnet package hash is valid"),
57        CSPR_DECIMALS,
58        WCSPR_NAME,
59        WCSPR_VERSION,
60    )]
61});
62
63/// Returns all known wCSPR deployments on Casper chains.
64#[must_use]
65pub fn wcspr_casper_deployments() -> &'static [CasperTokenDeployment] {
66    &WCSPR_DEPLOYMENTS
67}
68
69/// Returns the wCSPR deployment for a specific Casper chain, if known.
70#[must_use]
71pub fn wcspr_casper_deployment(
72    chain: CasperChainReference,
73) -> Option<&'static CasperTokenDeployment> {
74    WCSPR_DEPLOYMENTS
75        .iter()
76        .find(|deployment| deployment.chain_reference == chain)
77}
78
79/// Ergonomic accessors for wCSPR token deployments on well-known Casper
80/// chains.
81///
82/// Combine with [`CasperTokenDeployment::amount`] for a fluent pricing API:
83///
84/// ```ignore
85/// use r402_casper::{CasperExact, WCSPR};
86///
87/// let tag = CasperExact::price_tag(pay_to, WCSPR::casper_test().amount(1_000_000_000));
88/// ```
89#[derive(Debug, Clone, Copy)]
90#[allow(
91    clippy::upper_case_acronyms,
92    reason = "WCSPR is a well-known token ticker"
93)]
94pub struct WCSPR;
95
96#[allow(
97    clippy::missing_panics_doc,
98    clippy::expect_used,
99    reason = "static deployment lookups are infallible for built-in data"
100)]
101impl WCSPR {
102    /// Looks up a wCSPR deployment by chain reference.
103    ///
104    /// Returns `None` if the chain is not in the built-in deployment table.
105    #[must_use]
106    pub fn on(chain: CasperChainReference) -> Option<&'static CasperTokenDeployment> {
107        wcspr_casper_deployment(chain)
108    }
109
110    /// Returns all known wCSPR deployments on Casper chains.
111    #[must_use]
112    pub fn all() -> &'static [CasperTokenDeployment] {
113        wcspr_casper_deployments()
114    }
115
116    /// wCSPR on Casper testnet (`casper:casper-test`).
117    #[must_use]
118    pub fn casper_test() -> &'static CasperTokenDeployment {
119        wcspr_casper_deployment(CasperChainReference::CASPER_TEST)
120            .expect("built-in wCSPR deployment for Casper testnet missing")
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn network_table_matches_caip2_ids() {
130        let ids: Vec<String> = CASPER_NETWORKS
131            .iter()
132            .map(|net| net.chain_id().to_string())
133            .collect();
134        assert_eq!(ids, vec!["casper:casper", "casper:casper-test"]);
135    }
136
137    #[test]
138    fn every_network_info_maps_to_a_chain_reference() {
139        for network in CASPER_NETWORKS {
140            let chain_id = network.chain_id();
141            assert!(
142                CasperChainReference::try_from(chain_id).is_ok(),
143                "network {} has no chain reference",
144                network.name
145            );
146        }
147    }
148
149    #[test]
150    fn builtin_wcspr_deployment_is_well_formed() {
151        let deployment = WCSPR::casper_test();
152        assert_eq!(
153            deployment.chain_reference,
154            CasperChainReference::CASPER_TEST
155        );
156        assert_eq!(deployment.decimals, CSPR_DECIMALS);
157        assert_eq!(deployment.name, WCSPR_NAME);
158        assert_eq!(deployment.version, WCSPR_VERSION);
159        assert_eq!(deployment.address.to_string(), WCSPR_TESTNET_PACKAGE);
160    }
161
162    #[test]
163    fn lookup_returns_none_for_chains_without_deployments() {
164        assert!(WCSPR::on(CasperChainReference::CASPER).is_none());
165        assert_eq!(WCSPR::all().len(), 1);
166    }
167
168    #[test]
169    fn deployment_amount_is_exact() {
170        let amount = WCSPR::casper_test().amount(1_000_000_000);
171        assert_eq!(amount.amount.inner(), 1_000_000_000);
172        assert_eq!(amount.amount.to_cspr_string(), "1");
173    }
174}