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()); 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(); 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}