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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
use ethers::prelude::{Address, Http, LocalWallet, Provider, SignerMiddleware, U256};
use std::{path::PathBuf, sync::Arc};
use tdn::prelude::{Config as TdnConfig, PeerKey};

use crate::{
    contracts::{Network, NetworkConfig, RoomMarket, Token},
    types::{env_value, env_values, hex_address, Result, Z4_ROOM_MARKET_GROUP},
};

/// config of engine
#[derive(Default)]
pub struct Config {
    /// default groups
    pub groups: Vec<u64>,
    /// supported games
    pub games: Vec<String>,
    /// main room market
    pub room_market: String,
    /// the server secret key (SECP256K1)
    pub secret_key: String,
    /// the server websocket port
    pub ws_port: Option<u16>,
    /// the server rpc port
    pub http_port: u16,
    /// the p2p port
    pub p2p_port: u16,
    /// the chain network name
    pub chain_network: String,
    /// the chain rpcs
    pub chain_rpcs: Vec<String>,
    /// scan start block
    pub chain_start_block: Option<u64>,
    /// auto stake to sequencer
    pub auto_stake: bool,
    /// http url for this service
    pub url_http: String,
    /// http url for this service
    pub url_websocket: String,
}

impl Config {
    pub fn from_env() -> Result<Self> {
        dotenv::dotenv().ok();

        let network = env_value("NETWORK", None)?;
        let games: Vec<String> = env_values("GAMES", None)?;
        let secret_key = env_value("SECRET_KEY", None)?;
        let start_block = env_value("START_BLOCK", None).ok();

        let chain_rpcs = env_values("RPC_ENDPOINTS", Some(vec![]))?;
        let room_market = env_value("ROOM_MARKET", Some(games[0].clone()))?;
        let url_http = env_value("URL_HTTP", Some("".to_owned()))?;
        let url_websocket = env_value("URL_WEBSOCKET", Some("".to_owned()))?;
        let http_port = env_value("HTTP_PORT", Some(8080))?;
        let ws_port = env_value("WS_PORT", Some(8000))?;
        let p2p_port = env_value("P2P_PORT", Some(7364))?;
        let auto_stake = env_value("AUTO_STAKE", Some(false))?;

        let mut config = Config::default();
        config.http_port = http_port;
        config.ws_port = Some(ws_port);
        config.p2p_port = p2p_port;
        config.secret_key = secret_key;
        config.chain_network = network;
        config.chain_rpcs = chain_rpcs;
        config.chain_start_block = start_block;
        config.games = games;
        config.room_market = room_market;
        config.auto_stake = auto_stake;
        config.url_http = url_http;
        config.url_websocket = url_websocket;

        Ok(config)
    }

    pub fn to_tdn(&self) -> (TdnConfig, PeerKey) {
        let rpc_addr = format!("0.0.0.0:{}", self.http_port).parse().unwrap();
        let p2p_addr = format!("0.0.0.0:{}", self.p2p_port).parse().unwrap();
        let mut config = TdnConfig::with_addr(p2p_addr, rpc_addr);
        config.rpc_ws = match self.ws_port {
            Some(port) => Some(format!("0.0.0.0:{}", port).parse().unwrap()),
            None => None,
        };
        config.group_ids = self.groups.clone();
        config.group_ids.push(Z4_ROOM_MARKET_GROUP);

        // TODO boostrap seed

        let sk_str = self.secret_key.trim_start_matches("0x");
        let sk_bytes = hex::decode(&sk_str).expect("Invalid secret key");
        let key = PeerKey::from_db_bytes(&sk_bytes).expect("Invalid secret key");

        config.db_path = Some(PathBuf::from(&format!("./.tdn/{:?}", key.peer_id())));

        (config, key)
    }

    pub async fn to_chain(
        &self,
    ) -> Option<(
        Vec<Arc<Provider<Http>>>,
        Arc<SignerMiddleware<Arc<Provider<Http>>, LocalWallet>>,
        Address,
        Option<u64>,
    )> {
        if self.chain_network.is_empty() {
            return None;
        }

        let network = Network::from_str(&self.chain_network);
        let nc = NetworkConfig::from(network);
        let rpcs = if self.chain_rpcs.is_empty() {
            &nc.rpc_urls
        } else {
            &self.chain_rpcs
        };
        let providers: Vec<_> = rpcs
            .iter()
            .map(|rpc| Arc::new(Provider::<Http>::try_from(rpc).unwrap()))
            .collect();
        if providers.is_empty() {
            panic!("NO RPCS");
        }

        let sk_str = self.secret_key.trim_start_matches("0x");
        let signer = LocalWallet::from_bytes(&hex::decode(&sk_str).unwrap()).unwrap();
        let signer_provider = Arc::new(
            SignerMiddleware::new_with_provider_chain(providers[0].clone(), signer)
                .await
                .unwrap(),
        );

        let room_market = if self.room_market.is_empty() {
            &self.games[0]
        } else {
            &self.room_market
        };
        let market_address = hex_address(room_market).expect("Invalid room market address");
        if self.auto_stake && (!self.url_http.is_empty() || !self.url_websocket.is_empty()) {
            // check & register sequencer
            let contract = RoomMarket::new(market_address, signer_provider.clone());
            let token_address = contract.token().await.unwrap();
            let token = Token::new(token_address, signer_provider.clone());

            // TODO check staking is enough

            let amount = contract.min_staking().await.unwrap() * U256::from(100);
            info!("Auto staking: {}", amount);
            match token.approve(market_address, amount).send().await {
                Ok(pending) => {
                    if let Ok(_receipt) = pending.await {
                        let _ = contract
                            .stake_sequencer(
                                self.url_http.clone(),
                                self.url_websocket.clone(),
                                amount,
                            )
                            .send()
                            .await;
                    } else {
                        error!("Failed to approve token");
                    }
                }
                Err(err) => {
                    if let Some(rcode) = err.decode_revert::<String>() {
                        error!("{}", rcode);
                    } else {
                        error!("{}", err);
                    }
                }
            }
        }

        Some((
            providers,
            signer_provider,
            market_address,
            self.chain_start_block,
        ))
    }
}