arweave_rs/
lib.rs

1use std::{fs, path::PathBuf, str::FromStr};
2
3use consts::MAX_TX_DATA;
4use crypto::base64::Base64;
5use error::Error;
6use futures::{stream, Stream, StreamExt};
7use pretend::StatusCode;
8use reqwest::Client;
9use serde::{Deserialize, Serialize};
10use transaction::{
11    client::TxClient,
12    tags::{FromUtf8Strs, Tag},
13    Tx,
14};
15use types::TxStatus;
16use upload::Uploader;
17use verify::{verify, verify_transaction};
18
19pub mod client;
20pub mod consts;
21pub mod crypto;
22pub mod currency;
23pub mod error;
24pub mod network;
25pub mod signer;
26pub mod transaction;
27pub mod types;
28pub mod upload;
29mod verify;
30pub mod wallet;
31
32pub use signer::ArweaveSigner;
33
34#[derive(Serialize, Deserialize, Debug)]
35pub struct OraclePrice {
36    pub arweave: OraclePricePair,
37}
38
39#[derive(Serialize, Deserialize, Debug)]
40pub struct OraclePricePair {
41    pub usd: f32,
42}
43
44pub struct Arweave {
45    pub base_url: url::Url,
46    pub signer: Option<ArweaveSigner>,
47    tx_client: TxClient,
48    uploader: Uploader,
49}
50
51#[derive(Default)]
52pub struct ArweaveBuilder {
53    base_url: Option<url::Url>,
54    keypair_path: Option<PathBuf>,
55}
56
57impl ArweaveBuilder {
58    pub fn new() -> ArweaveBuilder {
59        Default::default()
60    }
61
62    pub fn base_url(mut self, url: url::Url) -> ArweaveBuilder {
63        self.base_url = Some(url);
64        self
65    }
66
67    pub fn keypair_path(mut self, keypair_path: PathBuf) -> ArweaveBuilder {
68        self.keypair_path = Some(keypair_path);
69        self
70    }
71
72    pub fn build(self) -> Result<Arweave, Error> {
73        let base_url = self
74            .base_url
75            .unwrap_or_else(|| url::Url::from_str(consts::ARWEAVE_BASE_URL).unwrap()); //Checked unwrap
76
77        let signer = match self.keypair_path {
78            Some(p) => Some(ArweaveSigner::from_keypair_path(p)?),
79            None => None,
80        };
81
82        Ok(Arweave {
83            signer,
84            base_url,
85            tx_client: Default::default(),
86            uploader: Default::default(),
87        })
88    }
89}
90
91impl Arweave {
92    pub fn from_keypair_path(keypair_path: PathBuf, base_url: url::Url) -> Result<Arweave, Error> {
93        let signer = Some(ArweaveSigner::from_keypair_path(keypair_path)?);
94        let tx_client = TxClient::new(reqwest::Client::new(), base_url.clone())?;
95        let uploader = Uploader::new(base_url.clone());
96        let arweave = Arweave {
97            base_url,
98            signer,
99            tx_client,
100            uploader,
101        };
102        Ok(arweave)
103    }
104
105    pub async fn create_transaction(
106        &self,
107        target: Base64,
108        other_tags: Vec<Tag<Base64>>,
109        data: Vec<u8>,
110        quantity: u128,
111        fee: u64,
112        auto_content_tag: bool,
113    ) -> Result<Tx, Error> {
114        let last_tx = self.get_last_tx().await?;
115        let signer = match &self.signer {
116            Some(s) => s,
117            None => return Err(Error::NoneError("signer".to_owned())),
118        };
119        Tx::new(
120            signer.get_provider(),
121            target,
122            data,
123            quantity,
124            fee,
125            last_tx,
126            other_tags,
127            auto_content_tag,
128        )
129    }
130
131    pub fn sign_transaction(&self, transaction: Tx) -> Result<Tx, Error> {
132        let signer = match &self.signer {
133            Some(s) => s,
134            None => return Err(Error::NoneError("signer".to_owned())),
135        };
136        signer.sign_transaction(transaction)
137    }
138
139    pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>, Error> {
140        let signer = match &self.signer {
141            Some(s) => s,
142            None => return Err(Error::NoneError("signer".to_owned())),
143        };
144        Ok(signer.sign(message)?.0)
145    }
146
147    pub fn verify_transaction(transaction: &Tx) -> Result<(), Error> {
148        verify_transaction(transaction)
149    }
150
151    pub fn verify(pub_key: &[u8], message: &[u8], signature: &[u8]) -> Result<(), Error> {
152        verify(pub_key, message, signature)
153    }
154
155    pub async fn post_transaction(&self, signed_transaction: &Tx) -> Result<(String, u64), Error> {
156        self.tx_client
157            .post_transaction(signed_transaction)
158            .await
159            .map(|(id, reward)| (id.to_string(), reward))
160    }
161
162    async fn get_last_tx(&self) -> Result<Base64, Error> {
163        self.tx_client.get_last_tx().await
164    }
165
166    pub async fn get_fee(&self, target: Base64, data: Vec<u8>) -> Result<u64, Error> {
167        self.tx_client.get_fee(target, data).await
168    }
169
170    pub async fn get_tx(&self, id: Base64) -> Result<(StatusCode, Option<Tx>), Error> {
171        self.tx_client.get_tx(id).await
172    }
173
174    pub async fn get_tx_status(&self, id: Base64) -> Result<(StatusCode, Option<TxStatus>), Error> {
175        self.tx_client.get_tx_status(id).await
176    }
177
178    pub fn get_pub_key(&self) -> Result<String, Error> {
179        let signer = match &self.signer {
180            Some(s) => s,
181            None => return Err(Error::NoneError("signer".to_owned())),
182        };
183        Ok(signer.keypair_modulus().to_string())
184    }
185
186    pub fn get_wallet_address(&self) -> Result<String, Error> {
187        let signer = match &self.signer {
188            Some(s) => s,
189            None => return Err(Error::NoneError("signer".to_owned())),
190        };
191        Ok(signer.wallet_address().to_string())
192    }
193
194    pub async fn upload_file_from_path(
195        &self,
196        file_path: PathBuf,
197        additional_tags: Vec<Tag<Base64>>,
198        fee: u64,
199    ) -> Result<(String, u64), Error> {
200        let mut auto_content_tag = true;
201        let mut additional_tags = additional_tags;
202
203        if let Some(content_type) = mime_guess::from_path(file_path.clone()).first() {
204            auto_content_tag = false;
205            let content_tag: Tag<Base64> =
206                Tag::from_utf8_strs("Content-Type", content_type.as_ref())?;
207            additional_tags.push(content_tag);
208        }
209
210        let data = fs::read(file_path)?;
211        let transaction = self
212            .create_transaction(
213                Base64(b"".to_vec()),
214                additional_tags,
215                data,
216                0,
217                fee,
218                auto_content_tag,
219            )
220            .await?;
221        let signed_transaction = self.sign_transaction(transaction)?;
222        let (id, reward) = if signed_transaction.data.0.len() > MAX_TX_DATA as usize {
223            self.post_transaction_chunks(signed_transaction, 100)
224                .await?
225        } else {
226            self.post_transaction(&signed_transaction).await?
227        };
228
229        Ok((id, reward))
230    }
231
232    async fn post_transaction_chunks(
233        &self,
234        signed_transaction: Tx,
235        chunks_buffer: usize,
236    ) -> Result<(String, u64), Error> {
237        if signed_transaction.id.0.is_empty() {
238            return Err(error::Error::UnsignedTransaction);
239        }
240
241        let transaction_with_no_data = signed_transaction.clone_with_no_data()?;
242        let (id, reward) = self.post_transaction(&transaction_with_no_data).await?;
243
244        let results: Vec<Result<usize, Error>> =
245            Self::upload_transaction_chunks_stream(self, signed_transaction, chunks_buffer)
246                .collect()
247                .await;
248
249        results.into_iter().collect::<Result<Vec<usize>, Error>>()?;
250
251        Ok((id, reward))
252    }
253
254    fn upload_transaction_chunks_stream(
255        arweave: &Arweave,
256        signed_transaction: Tx,
257        buffer: usize,
258    ) -> impl Stream<Item = Result<usize, Error>> + '_ {
259        let client = Client::new();
260        stream::iter(0..signed_transaction.chunks.len())
261            .map(move |i| {
262                let chunk = signed_transaction.get_chunk(i).unwrap(); //TODO: remove this unwrap
263                arweave
264                    .uploader
265                    .post_chunk_with_retries(chunk, client.clone())
266            })
267            .buffer_unordered(buffer)
268    }
269}
270
271#[cfg(test)]
272mod tests {
273    use std::{fs::File, io::Read, str::FromStr};
274
275    use crate::{error::Error, transaction::Tx, verify::verify_transaction};
276
277    #[test]
278    pub fn should_parse_and_verify_valid_tx() -> Result<(), Error> {
279        let mut file = File::open("res/sample_tx.json").unwrap();
280        let mut data = String::new();
281        file.read_to_string(&mut data).unwrap();
282        let tx = Tx::from_str(&data).unwrap();
283
284        match verify_transaction(&tx) {
285            Ok(_) => Ok(()),
286            Err(_) => Err(Error::InvalidSignature),
287        }
288    }
289}