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
182
183
184
185
//! suibase
//!
//! API to suibase mostly intended for development of Sui tool/test automation and production backends.
//!
//! This API is:
//!   * multi-thread safe.
//!   * UniFFI bindings compatible (Sync+Send)
//!
//! See <https://suibase.io> for more info.
//!
//! What is Suibase?
//!
//! Suibase makes it easy for Sui devs to simultaneously interact with multiple Sui
//! networks (localnet/devnet/testnet/mainnet) without having to "switch env".
//!
//! Your dev setup gains stability by having a client binary match every network version.
//!
//! How to use?
//!
//! Suibase is not available on crates.io. It is instead installed (github cloned) to your local machine and
//! your app references it. That way your app and the suibase installation are certainly matching versions.
//!
//! After installing suibase ([more info](https://suibase.io/how-to/install)), add to your Cargo.toml:
//!
//! ```toml
//! [dependencies]
//! suibase = { path = "../../suibase/rust/helper" }
//! ```
//!
//! You may have to adjust the number "../" depending on where your Cargo.toml is located relative to ~/suibase.

mod error;
pub use crate::error::Error;

mod suibase_helper_impl;
mod suibase_root;
mod suibase_workdir;

use crate::suibase_helper_impl::SuibaseHelperImpl;

use std::sync::{Arc, Mutex};
use sui_types::base_types::{ObjectID, SuiAddress};

#[cfg(feature = "build-with-uniffi")]
uniffi::include_scaffolding!("suibase");
/// A lightweight API to suibase. Multiple instance can be created within the same app.
///
/// You interact with Suibase in 3 steps:
///
/// 1. Check if suibase is_installed()
/// 2. Call select_workdir() to pick among "localnet", "devnet", "testnet", "mainnet" or the one currently set "active" by the user.
/// 3. You can now call any other API functions (in any order). Most calls will relate to the selected workdir.
///
/// You can call again select_workdir() to switch to another workdir.
pub struct Helper(Arc<Mutex<SuibaseHelperImpl>>);

/// This is the documentation for the impl Default
impl Default for Helper {
    fn default() -> Self {
        Self::new()
    }
}

impl Helper {
    /// Constructs a new `Helper`.
    ///
    /// # Example
    /// ```
    /// use suibase::Helper;
    /// let sbh = Helper::new();
    /// if sbh.is_installed()? {
    ///    sbh.select_workdir("localnet")?;
    ///    println!("active address is {}", sbh.client_address("active"));
    /// }
    /// ```
    pub fn new() -> Self {
        Helper(Arc::new(Mutex::new(SuibaseHelperImpl::new())))
    }

    /// Check first if suibase is installed, otherwise
    /// most of the other calls will fail in some ways.
    pub fn is_installed(&self) -> Result<bool, Error> {
        self.0.lock().unwrap().is_installed()
    }

    /// Select an existing workdir by name.
    ///
    /// Possible values are:
    ///   "active", "cargobin", "localnet", "devnet", "testnet", "mainnet" and
    ///    other custom names might be supported in future.
    ///
    /// Note: "active" is special. It will resolve the active workdir at the moment of the
    ///       call. Example: if "localnet" is the active, then this call is equivalent to
    ///       to be done for "localnet". The selection does not change even if the user
    ///       externally change the active after this call.
    ///
    pub fn select_workdir(&self, workdir_name: &str) -> Result<(), Error> {
        self.0.lock().unwrap().select_workdir(workdir_name)
    }

    /// Get the name of the selected workdir.
    pub fn workdir(&self) -> Result<String, Error> {
        self.0.lock().unwrap().workdir()
    }

    /// Get the pathname of the file keystore (when available).
    ///
    /// Context: Selected Workdir by this API.
    pub fn keystore_pathname(&self) -> Result<String, Error> {
        self.0.lock().unwrap().keystore_pathname()
    }

    /// Get the ObjectID of the last successfully published "package_name".
    ///
    /// package_name is the "name" field specified in the "Move.toml".
    ///
    /// Related path: ~/suibase/workdirs/<workdir_name>/published-data/<package_name>/
    pub fn package_object_id(&self, package_name: &str) -> Result<ObjectID, Error> {
        self.0.lock().unwrap().package_object_id(package_name)
    }

    /// Alternative for string-based API.
    pub fn package_id(&self, package_name: &str) -> Result<String, Error> {
        let id = self.package_object_id(package_name)?;
        Ok(id.to_string())
    }

    /// Get the ObjectID of the objects that were created when the package was published.
    ///
    /// object_type format is the Sui Move "package::module::type".
    ///
    /// Example:
    ///
    ///    module acme::Tools {
    ///       struct Anvil has key, drop { ... }
    ///       ...
    ///       fun init(ctx: &mut TxContext) {
    ///          Anvil::new(ctx);
    ///          ...
    ///       }
    ///    }
    ///
    /// The object_type is "acme::Tools::Anvil"
    ///
    /// Related path: ~/suibase/workdirs/<workdir_name>/published-data/<package_name>/
    pub fn published_new_object_ids(&self, object_type: &str) -> Result<Vec<ObjectID>, Error> {
        self.0.lock().unwrap().published_new_object_ids(object_type)
    }

    /// Alternative to published_new_object_ids() for string-based API.
    pub fn published_new_objects(&self, object_type: &str) -> Result<Vec<String>, Error> {
        let res = self.published_new_object_ids(object_type)?;
        Ok(res.iter().map(|c| c.to_string()).collect())
    }

    /// Get an address by name.
    ///
    /// Suibase localnet/devnet/testnet/mainnet workdir are created with a set of pre-defined client addresses.
    /// These addresses are useful for testing. In particular, with a localnet, they are prefunded.
    ///
    /// Values for the `address_name` argument can be: `active | sb-[1-5]-[ed25519|scp256k1|scp256r1]`
    ///
    /// Examples: "active", "sb-1-ed25519", "sb-3-scp256r1", "sb-5-scp256k1" ...
    ///
    /// Choosing "active" is same as doing "sui client active-address" for the selected workdir.
    pub fn client_sui_address(&self, address_name: &str) -> Result<SuiAddress, Error> {
        self.0.lock().unwrap().client_sui_address(address_name)
    }

    /// Alternative to client_sui_address() for string-based API.
    pub fn client_address(&self, address_name: &str) -> Result<String, Error> {
        let addr = self.client_sui_address(address_name)?;
        Ok(addr.to_string())
    }

    /// Get a RPC URL for the selected workdir.
    pub fn rpc_url(&self) -> Result<String, Error> {
        self.0.lock().unwrap().rpc_url()
    }

    /// Get a Websocket URL for the selected workdir.
    pub fn ws_url(&self) -> Result<String, Error> {
        self.0.lock().unwrap().ws_url()
    }
}