create-sia-app 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +179 -0
  3. package/package.json +29 -0
  4. package/template/CLAUDE.md +160 -0
  5. package/template/README.md +102 -0
  6. package/template/_gitignore +5 -0
  7. package/template/biome.json +40 -0
  8. package/template/index.html +13 -0
  9. package/template/package.json +30 -0
  10. package/template/rust/README.md +16 -0
  11. package/template/rust/sia-sdk-rs/.changeset/added_cancel_function_to_cancel_inflight_packed_uploads.md +6 -0
  12. package/template/rust/sia-sdk-rs/.changeset/check_if_we_have_enough_hosts_prior_to_encoding_in_upload_slabs.md +16 -0
  13. package/template/rust/sia-sdk-rs/.changeset/fix_slab_length_in_packed_object.md +5 -0
  14. package/template/rust/sia-sdk-rs/.changeset/fix_upload_racing_race_conditon.md +13 -0
  15. package/template/rust/sia-sdk-rs/.changeset/improved_parallelism_of_packed_uploads.md +5 -0
  16. package/template/rust/sia-sdk-rs/.changeset/progress_callback_will_now_be_called_as_expected_for_packed_uploads.md +5 -0
  17. package/template/rust/sia-sdk-rs/.github/dependabot.yml +10 -0
  18. package/template/rust/sia-sdk-rs/.github/workflows/main.yml +36 -0
  19. package/template/rust/sia-sdk-rs/.github/workflows/prepare-release.yml +34 -0
  20. package/template/rust/sia-sdk-rs/.github/workflows/release.yml +30 -0
  21. package/template/rust/sia-sdk-rs/.rustfmt.toml +4 -0
  22. package/template/rust/sia-sdk-rs/Cargo.lock +4127 -0
  23. package/template/rust/sia-sdk-rs/Cargo.toml +3 -0
  24. package/template/rust/sia-sdk-rs/LICENSE +21 -0
  25. package/template/rust/sia-sdk-rs/README.md +30 -0
  26. package/template/rust/sia-sdk-rs/indexd/CHANGELOG.md +79 -0
  27. package/template/rust/sia-sdk-rs/indexd/Cargo.toml +79 -0
  28. package/template/rust/sia-sdk-rs/indexd/benches/upload.rs +258 -0
  29. package/template/rust/sia-sdk-rs/indexd/src/app_client.rs +1710 -0
  30. package/template/rust/sia-sdk-rs/indexd/src/builder.rs +354 -0
  31. package/template/rust/sia-sdk-rs/indexd/src/download.rs +379 -0
  32. package/template/rust/sia-sdk-rs/indexd/src/hosts.rs +659 -0
  33. package/template/rust/sia-sdk-rs/indexd/src/lib.rs +827 -0
  34. package/template/rust/sia-sdk-rs/indexd/src/mock.rs +162 -0
  35. package/template/rust/sia-sdk-rs/indexd/src/object_encryption.rs +125 -0
  36. package/template/rust/sia-sdk-rs/indexd/src/quic.rs +575 -0
  37. package/template/rust/sia-sdk-rs/indexd/src/rhp4.rs +52 -0
  38. package/template/rust/sia-sdk-rs/indexd/src/slabs.rs +497 -0
  39. package/template/rust/sia-sdk-rs/indexd/src/upload.rs +629 -0
  40. package/template/rust/sia-sdk-rs/indexd/src/wasm_time.rs +41 -0
  41. package/template/rust/sia-sdk-rs/indexd/src/web_transport.rs +398 -0
  42. package/template/rust/sia-sdk-rs/indexd_ffi/CHANGELOG.md +76 -0
  43. package/template/rust/sia-sdk-rs/indexd_ffi/Cargo.toml +47 -0
  44. package/template/rust/sia-sdk-rs/indexd_ffi/examples/python/README.md +10 -0
  45. package/template/rust/sia-sdk-rs/indexd_ffi/examples/python/example.py +130 -0
  46. package/template/rust/sia-sdk-rs/indexd_ffi/src/bin/uniffi-bindgen.rs +3 -0
  47. package/template/rust/sia-sdk-rs/indexd_ffi/src/builder.rs +377 -0
  48. package/template/rust/sia-sdk-rs/indexd_ffi/src/io.rs +155 -0
  49. package/template/rust/sia-sdk-rs/indexd_ffi/src/lib.rs +1039 -0
  50. package/template/rust/sia-sdk-rs/indexd_ffi/src/logging.rs +58 -0
  51. package/template/rust/sia-sdk-rs/indexd_ffi/src/tls.rs +23 -0
  52. package/template/rust/sia-sdk-rs/indexd_wasm/Cargo.toml +33 -0
  53. package/template/rust/sia-sdk-rs/indexd_wasm/src/lib.rs +818 -0
  54. package/template/rust/sia-sdk-rs/knope.toml +54 -0
  55. package/template/rust/sia-sdk-rs/sia_derive/CHANGELOG.md +38 -0
  56. package/template/rust/sia-sdk-rs/sia_derive/Cargo.toml +19 -0
  57. package/template/rust/sia-sdk-rs/sia_derive/src/lib.rs +278 -0
  58. package/template/rust/sia-sdk-rs/sia_sdk/CHANGELOG.md +91 -0
  59. package/template/rust/sia-sdk-rs/sia_sdk/Cargo.toml +59 -0
  60. package/template/rust/sia-sdk-rs/sia_sdk/benches/merkle_root.rs +12 -0
  61. package/template/rust/sia-sdk-rs/sia_sdk/src/blake2.rs +22 -0
  62. package/template/rust/sia-sdk-rs/sia_sdk/src/consensus.rs +767 -0
  63. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding/v1.rs +257 -0
  64. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding/v2.rs +291 -0
  65. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding.rs +26 -0
  66. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding_async/v2.rs +367 -0
  67. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding_async.rs +6 -0
  68. package/template/rust/sia-sdk-rs/sia_sdk/src/encryption.rs +303 -0
  69. package/template/rust/sia-sdk-rs/sia_sdk/src/erasure_coding.rs +347 -0
  70. package/template/rust/sia-sdk-rs/sia_sdk/src/lib.rs +15 -0
  71. package/template/rust/sia-sdk-rs/sia_sdk/src/macros.rs +435 -0
  72. package/template/rust/sia-sdk-rs/sia_sdk/src/merkle.rs +112 -0
  73. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/merkle.rs +357 -0
  74. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/rpc.rs +1507 -0
  75. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/types.rs +146 -0
  76. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp.rs +7 -0
  77. package/template/rust/sia-sdk-rs/sia_sdk/src/seed.rs +278 -0
  78. package/template/rust/sia-sdk-rs/sia_sdk/src/signing.rs +236 -0
  79. package/template/rust/sia-sdk-rs/sia_sdk/src/types/common.rs +677 -0
  80. package/template/rust/sia-sdk-rs/sia_sdk/src/types/currency.rs +450 -0
  81. package/template/rust/sia-sdk-rs/sia_sdk/src/types/specifier.rs +110 -0
  82. package/template/rust/sia-sdk-rs/sia_sdk/src/types/spendpolicy.rs +778 -0
  83. package/template/rust/sia-sdk-rs/sia_sdk/src/types/utils.rs +117 -0
  84. package/template/rust/sia-sdk-rs/sia_sdk/src/types/v1.rs +1737 -0
  85. package/template/rust/sia-sdk-rs/sia_sdk/src/types/v2.rs +1726 -0
  86. package/template/rust/sia-sdk-rs/sia_sdk/src/types/work.rs +59 -0
  87. package/template/rust/sia-sdk-rs/sia_sdk/src/types.rs +16 -0
  88. package/template/scripts/setup-rust.js +29 -0
  89. package/template/src/App.tsx +13 -0
  90. package/template/src/components/DevNote.tsx +21 -0
  91. package/template/src/components/auth/ApproveScreen.tsx +84 -0
  92. package/template/src/components/auth/AuthFlow.tsx +77 -0
  93. package/template/src/components/auth/ConnectScreen.tsx +214 -0
  94. package/template/src/components/auth/LoadingScreen.tsx +8 -0
  95. package/template/src/components/auth/RecoveryScreen.tsx +182 -0
  96. package/template/src/components/upload/UploadZone.tsx +314 -0
  97. package/template/src/index.css +9 -0
  98. package/template/src/lib/constants.ts +8 -0
  99. package/template/src/lib/format.ts +35 -0
  100. package/template/src/lib/hex.ts +13 -0
  101. package/template/src/lib/sdk.ts +25 -0
  102. package/template/src/lib/wasm-env.ts +5 -0
  103. package/template/src/main.tsx +12 -0
  104. package/template/src/stores/auth.ts +86 -0
  105. package/template/tsconfig.app.json +31 -0
  106. package/template/tsconfig.json +7 -0
  107. package/template/tsconfig.node.json +26 -0
  108. package/template/vite.config.ts +18 -0
  109. package/template/wasm/indexd_wasm/indexd_wasm.d.ts +309 -0
  110. package/template/wasm/indexd_wasm/indexd_wasm.js +1507 -0
  111. package/template/wasm/indexd_wasm/indexd_wasm_bg.wasm +0 -0
  112. package/template/wasm/indexd_wasm/package.json +31 -0
@@ -0,0 +1,677 @@
1
+ use core::fmt;
2
+
3
+ use crate::encoding_async::{AsyncSiaDecodable, AsyncSiaDecode, AsyncSiaEncodable, AsyncSiaEncode};
4
+ use blake2b_simd::Params;
5
+ use chrono::{DateTime, Utc};
6
+ use serde::{Deserialize, Serialize};
7
+ use thiserror::Error;
8
+
9
+ use crate::encoding::{
10
+ self, SiaDecodable, SiaDecode, SiaEncodable, SiaEncode, V1SiaDecodable, V1SiaDecode,
11
+ V1SiaEncodable, V1SiaEncode,
12
+ };
13
+ use crate::macros::impl_hash_id;
14
+ use crate::types::currency::Currency;
15
+ use crate::types::v1;
16
+
17
+ use super::{Specifier, specifier};
18
+
19
+ impl_hash_id!(Hash256);
20
+ impl_hash_id!(SiacoinOutputID);
21
+ impl_hash_id!(AttestationID);
22
+
23
+ impl_hash_id!(SiafundOutputID);
24
+
25
+ impl SiafundOutputID {
26
+ /// claim_output_id returns the SiacoinOutputID for the claim output of the siafund output
27
+ pub fn claim_output_id(&self) -> SiacoinOutputID {
28
+ let mut state = Params::new().hash_length(32).to_state();
29
+ state.update(self.as_ref());
30
+ state.finalize().into()
31
+ }
32
+
33
+ pub fn v2_claim_output_id(&self) -> SiacoinOutputID {
34
+ let mut state = Params::new().hash_length(32).to_state();
35
+ state.update(b"sia/id/v2siacoinclaimoutput|");
36
+ state.update(self.as_ref());
37
+ state.finalize().into()
38
+ }
39
+ }
40
+
41
+ impl_hash_id!(BlockID);
42
+
43
+ impl BlockID {
44
+ const FOUNDATION_OUTPUT_ID_PREFIX: Specifier = specifier!("foundation");
45
+
46
+ pub fn foundation_output_id(&self) -> SiacoinOutputID {
47
+ let mut state = Params::new().hash_length(32).to_state();
48
+ state.update(self.as_ref());
49
+ state.update(Self::FOUNDATION_OUTPUT_ID_PREFIX.as_ref());
50
+ state.finalize().into()
51
+ }
52
+
53
+ pub fn miner_output_id(&self, i: usize) -> SiacoinOutputID {
54
+ let mut state = Params::new().hash_length(32).to_state();
55
+ state.update(self.as_ref());
56
+ state.update(&(i as u64).to_le_bytes());
57
+ state.finalize().into()
58
+ }
59
+ }
60
+
61
+ impl_hash_id!(TransactionID);
62
+
63
+ impl TransactionID {
64
+ const V2_SIACOIN_OUTPUT_PREFIX: &[u8] = b"sia/id/siacoinoutput|";
65
+ const V2_SIAFUND_OUTPUT_PREFIX: &[u8] = b"sia/id/siafundoutput|";
66
+ const V2_FILE_CONTRACT_PREFIX: &[u8] = b"sia/id/filecontract|";
67
+ const V2_ATTESTATION_PREFIX: &[u8] = b"sia/id/attestation|";
68
+
69
+ fn derive_v2_child_id<T: From<blake2b_simd::Hash>>(&self, prefix: &[u8], i: usize) -> T {
70
+ let mut state = Params::new().hash_length(32).to_state();
71
+ state.update(prefix.as_ref());
72
+ state.update(self.as_ref());
73
+ state.update(&(i as u64).to_le_bytes());
74
+ state.finalize().into()
75
+ }
76
+
77
+ /// v2_siacoin_output_id returns the SiacoinOutputID for the i-th siacoin output of the V2 transaction
78
+ pub fn v2_siacoin_output_id(&self, i: usize) -> SiacoinOutputID {
79
+ self.derive_v2_child_id(Self::V2_SIACOIN_OUTPUT_PREFIX, i)
80
+ }
81
+
82
+ /// v2_siafund_output_id returns the SiafundOutputID for the i-th siafund output of the V2 transaction
83
+ pub fn v2_siafund_output_id(&self, i: usize) -> SiafundOutputID {
84
+ self.derive_v2_child_id(Self::V2_SIAFUND_OUTPUT_PREFIX, i)
85
+ }
86
+
87
+ /// v2_file_contract_id returns the FileContractID for the i-th file contract of the V2 transaction
88
+ pub fn v2_file_contract_id(&self, i: usize) -> FileContractID {
89
+ self.derive_v2_child_id(Self::V2_FILE_CONTRACT_PREFIX, i)
90
+ }
91
+
92
+ /// v2_attestation_id returns the AttestationID for the i-th attestation of the V2 transaction
93
+ pub fn v2_attestation_id(&self, i: usize) -> AttestationID {
94
+ self.derive_v2_child_id(Self::V2_ATTESTATION_PREFIX, i)
95
+ }
96
+ }
97
+
98
+ impl_hash_id!(FileContractID);
99
+
100
+ impl FileContractID {
101
+ const PROOF_OUTPUT_ID_PREFIX: Specifier = specifier!("storage proof");
102
+ const V2_PROOF_OUTPUT_ID_PREFIX: &'static str = "id/v2filecontractoutput";
103
+ const V2_FILE_CONTRACT_RENEWAL_PREFIX: &'static str = "id/v2filecontractrenewal";
104
+
105
+ fn derive_proof_output_id<T: From<blake2b_simd::Hash>>(&self, valid: bool, i: usize) -> T {
106
+ let mut state = Params::new().hash_length(32).to_state();
107
+ state.update(Self::PROOF_OUTPUT_ID_PREFIX.as_ref());
108
+ state.update(self.as_ref());
109
+ state.update(&(valid as u8).to_le_bytes());
110
+ state.update(&(i as u64).to_le_bytes());
111
+ state.finalize().into()
112
+ }
113
+
114
+ fn derive_v2_proof_output_id<T: From<blake2b_simd::Hash>>(&self, i: usize) -> T {
115
+ let mut state = Params::new().hash_length(32).to_state();
116
+ state.update(Self::V2_PROOF_OUTPUT_ID_PREFIX.as_ref());
117
+ state.update(self.as_ref());
118
+ state.update(&(i as u64).to_le_bytes());
119
+ state.finalize().into()
120
+ }
121
+
122
+ /// valid_output_id returns the SiacoinOutputID for the i-th valid output of the contract
123
+ pub fn valid_output_id(&self, i: usize) -> SiacoinOutputID {
124
+ self.derive_proof_output_id(true, i)
125
+ }
126
+
127
+ /// missed_output_id returns the SiacoinOutputID for the i-th missed output of the contract
128
+ pub fn missed_output_id(&self, i: usize) -> SiacoinOutputID {
129
+ self.derive_proof_output_id(false, i)
130
+ }
131
+
132
+ /// v2_renter_output_id returns the SiacoinOutputID for the renter output of a V2 file contract
133
+ pub fn v2_renter_output_id(&self) -> SiacoinOutputID {
134
+ self.derive_v2_proof_output_id(0)
135
+ }
136
+
137
+ /// v2_host_output_id returns the SiacoinOutputID for the host output of a V2 file contract
138
+ pub fn v2_host_output_id(&self) -> SiacoinOutputID {
139
+ self.derive_v2_proof_output_id(1)
140
+ }
141
+
142
+ /// v2_renewal_id returns the ID of the new contract created by renewing a V2 contract
143
+ pub fn v2_renewal_id(&self) -> FileContractID {
144
+ let mut state = Params::new().hash_length(32).to_state();
145
+ state.update(Self::V2_FILE_CONTRACT_RENEWAL_PREFIX.as_ref());
146
+ state.update(self.as_ref());
147
+ state.finalize().into()
148
+ }
149
+ }
150
+
151
+ #[derive(
152
+ Debug, PartialEq, SiaEncode, SiaDecode, AsyncSiaDecode, AsyncSiaEncode, Serialize, Deserialize,
153
+ )]
154
+ pub struct ChainIndex {
155
+ pub height: u64,
156
+ pub id: BlockID,
157
+ }
158
+
159
+ impl ChainIndex {
160
+ pub fn child_height(&self) -> u64 {
161
+ self.height + 1
162
+ }
163
+ }
164
+
165
+ impl fmt::Display for ChainIndex {
166
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
167
+ write!(f, "{}:{}", self.height, hex::encode(self.id))
168
+ }
169
+ }
170
+
171
+ /// A Block is a collection of transactions
172
+ #[derive(Debug, PartialEq, Serialize, Deserialize)]
173
+ #[serde(rename_all = "camelCase")]
174
+ pub struct Block {
175
+ #[serde(rename = "parentID")]
176
+ pub parent_id: BlockID,
177
+ pub nonce: u64,
178
+ pub timestamp: DateTime<Utc>,
179
+ pub miner_payouts: Vec<SiacoinOutput>,
180
+ pub transactions: Vec<v1::Transaction>,
181
+ }
182
+
183
+ impl V1SiaEncodable for Block {
184
+ fn encode_v1<W: std::io::Write>(&self, w: &mut W) -> encoding::Result<()> {
185
+ self.parent_id.encode(w)?;
186
+ self.nonce.encode(w)?;
187
+ self.timestamp.encode(w)?;
188
+ self.miner_payouts.encode_v1(w)?;
189
+ self.transactions.encode_v1(w)
190
+ }
191
+ }
192
+
193
+ impl V1SiaDecodable for Block {
194
+ fn decode_v1<R: std::io::Read>(r: &mut R) -> encoding::Result<Self> {
195
+ Ok(Block {
196
+ parent_id: BlockID::decode(r)?,
197
+ nonce: u64::decode(r)?,
198
+ timestamp: DateTime::<Utc>::decode(r)?,
199
+ miner_payouts: Vec::<SiacoinOutput>::decode_v1(r)?,
200
+ transactions: Vec::<v1::Transaction>::decode_v1(r)?,
201
+ })
202
+ }
203
+ }
204
+
205
+ /// encapsulates the various errors that can occur when parsing a Sia object
206
+ /// from a string
207
+ #[derive(Debug, Error, PartialEq)]
208
+ pub enum HexParseError {
209
+ #[error("Missing prefix")]
210
+ MissingPrefix,
211
+
212
+ #[error("Unexpected length")]
213
+ InvalidLength(usize),
214
+
215
+ #[error("Invalid prefix {0}")]
216
+ InvalidPrefix(String),
217
+
218
+ #[error("Invalid checksum")]
219
+ InvalidChecksum, // not every object has a checksum
220
+
221
+ #[error("Hex error: {0}")]
222
+ HexError(#[from] hex::FromHexError),
223
+ }
224
+
225
+ /// An address that can be used to receive UTXOs
226
+ #[derive(
227
+ Default,
228
+ Debug,
229
+ PartialEq,
230
+ Clone,
231
+ SiaEncode,
232
+ V1SiaEncode,
233
+ SiaDecode,
234
+ V1SiaDecode,
235
+ AsyncSiaEncode,
236
+ AsyncSiaDecode,
237
+ )]
238
+ pub struct Address([u8; 32]);
239
+
240
+ impl<'de> Deserialize<'de> for Address {
241
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
242
+ where
243
+ D: serde::Deserializer<'de>,
244
+ {
245
+ let s = String::deserialize(deserializer)?;
246
+ s.parse()
247
+ .map_err(|e| serde::de::Error::custom(format!("{e:?}")))
248
+ }
249
+ }
250
+
251
+ impl Serialize for Address {
252
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
253
+ where
254
+ S: serde::Serializer,
255
+ {
256
+ self.to_string().serialize(serializer)
257
+ }
258
+ }
259
+
260
+ impl Address {
261
+ pub const fn new(addr: [u8; 32]) -> Address {
262
+ Address(addr)
263
+ }
264
+ }
265
+
266
+ impl AsRef<[u8]> for Address {
267
+ fn as_ref(&self) -> &[u8] {
268
+ &self.0
269
+ }
270
+ }
271
+
272
+ impl From<&[u8]> for Address {
273
+ fn from(val: &[u8]) -> Self {
274
+ let mut data = [0u8; 32];
275
+ data.copy_from_slice(val);
276
+ Address(data)
277
+ }
278
+ }
279
+
280
+ impl From<[u8; 32]> for Address {
281
+ fn from(val: [u8; 32]) -> Self {
282
+ Address(val)
283
+ }
284
+ }
285
+
286
+ impl std::str::FromStr for Address {
287
+ type Err = crate::types::HexParseError;
288
+
289
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
290
+ if s.len() != 76 {
291
+ return Err(HexParseError::InvalidLength(s.len()));
292
+ }
293
+
294
+ let mut data = [0u8; 38];
295
+ hex::decode_to_slice(s, &mut data).map_err(HexParseError::HexError)?;
296
+
297
+ let h = Params::new()
298
+ .hash_length(32)
299
+ .to_state()
300
+ .update(&data[..32])
301
+ .finalize();
302
+ let checksum = h.as_bytes();
303
+
304
+ if checksum[..6] != data[32..] {
305
+ return Err(HexParseError::InvalidChecksum);
306
+ }
307
+
308
+ Ok(data[..32].into())
309
+ }
310
+ }
311
+
312
+ impl fmt::Display for Address {
313
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
314
+ let mut buf = [0u8; 32 + 6];
315
+ buf[..32].copy_from_slice(&self.0);
316
+
317
+ let h = Params::new()
318
+ .hash_length(32)
319
+ .to_state()
320
+ .update(&self.0)
321
+ .finalize();
322
+
323
+ buf[32..].copy_from_slice(&h.as_bytes()[..6]);
324
+ write!(f, "{}", hex::encode(buf))
325
+ }
326
+ }
327
+
328
+ /// A SiacoinOutput is a Siacoin UTXO that can be spent using the unlock conditions
329
+ /// for Address
330
+ #[derive(
331
+ Debug,
332
+ PartialEq,
333
+ Serialize,
334
+ Deserialize,
335
+ AsyncSiaEncode,
336
+ AsyncSiaDecode,
337
+ SiaEncode,
338
+ SiaDecode,
339
+ V1SiaEncode,
340
+ V1SiaDecode,
341
+ Clone,
342
+ )]
343
+ #[serde(rename_all = "camelCase")]
344
+ pub struct SiacoinOutput {
345
+ pub value: Currency,
346
+ pub address: Address,
347
+ }
348
+
349
+ /// A SiafundOutput is a Siafund UTXO that can be spent using the unlock conditions
350
+ /// for Address
351
+ #[derive(
352
+ Debug, PartialEq, Serialize, Deserialize, SiaEncode, SiaDecode, AsyncSiaEncode, AsyncSiaDecode,
353
+ )]
354
+ #[serde(rename_all = "camelCase")]
355
+ pub struct SiafundOutput {
356
+ pub value: u64,
357
+ pub address: Address,
358
+ }
359
+
360
+ impl V1SiaEncodable for SiafundOutput {
361
+ fn encode_v1<W: std::io::Write>(&self, w: &mut W) -> encoding::Result<()> {
362
+ Currency::new(self.value as u128).encode_v1(w)?;
363
+ self.address.encode_v1(w)?;
364
+ Currency::new(0).encode_v1(w) // siad encodes a "claim start," but its an error if it's non-zero.
365
+ }
366
+ }
367
+
368
+ impl V1SiaDecodable for SiafundOutput {
369
+ fn decode_v1<R: std::io::Read>(r: &mut R) -> encoding::Result<Self> {
370
+ let se = SiafundOutput {
371
+ value: Currency::decode_v1(r)?
372
+ .try_into()
373
+ .map_err(|_| encoding::Error::Custom("invalid value".to_string()))?,
374
+ address: Address::decode_v1(r)?,
375
+ };
376
+ Currency::decode_v1(r)?; // ignore claim start
377
+ Ok(se)
378
+ }
379
+ }
380
+
381
+ /// A Leaf is a 64-byte piece of data that is stored in a Merkle tree.
382
+ #[derive(
383
+ Debug,
384
+ PartialEq,
385
+ Clone,
386
+ SiaEncode,
387
+ V1SiaEncode,
388
+ SiaDecode,
389
+ V1SiaDecode,
390
+ AsyncSiaEncode,
391
+ AsyncSiaDecode,
392
+ )]
393
+ pub struct Leaf([u8; 64]);
394
+
395
+ impl From<[u8; 64]> for Leaf {
396
+ fn from(data: [u8; 64]) -> Self {
397
+ Leaf(data)
398
+ }
399
+ }
400
+
401
+ impl std::str::FromStr for Leaf {
402
+ type Err = crate::types::HexParseError;
403
+
404
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
405
+ if s.len() != 128 {
406
+ return Err(HexParseError::InvalidLength(s.len()));
407
+ }
408
+
409
+ let mut data = [0u8; 64];
410
+ hex::decode_to_slice(s, &mut data).map_err(HexParseError::HexError)?;
411
+ Ok(Leaf(data))
412
+ }
413
+ }
414
+
415
+ impl fmt::Display for Leaf {
416
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
417
+ write!(f, "{}", hex::encode(self.0))
418
+ }
419
+ }
420
+
421
+ impl Serialize for Leaf {
422
+ fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
423
+ String::serialize(&self.to_string(), serializer)
424
+ }
425
+ }
426
+
427
+ impl<'de> Deserialize<'de> for Leaf {
428
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
429
+ where
430
+ D: serde::Deserializer<'de>,
431
+ {
432
+ let s = String::deserialize(deserializer)?;
433
+ let data = hex::decode(s).map_err(|e| serde::de::Error::custom(format!("{e:?}")))?;
434
+ if data.len() != 64 {
435
+ return Err(serde::de::Error::custom("invalid length"));
436
+ }
437
+ Ok(Leaf(data.try_into().unwrap()))
438
+ }
439
+ }
440
+
441
+ /// A StateElement is a generic element within the state accumulator.
442
+ #[derive(
443
+ Debug,
444
+ PartialEq,
445
+ Serialize,
446
+ Deserialize,
447
+ SiaEncode,
448
+ SiaDecode,
449
+ AsyncSiaDecode,
450
+ AsyncSiaEncode,
451
+ Clone,
452
+ )]
453
+ #[serde(rename_all = "camelCase")]
454
+ pub struct StateElement {
455
+ pub leaf_index: u64,
456
+ pub merkle_proof: Vec<Hash256>,
457
+ }
458
+
459
+ #[cfg(test)]
460
+ mod tests {
461
+ use crate::{
462
+ address, block_id, contract_id, public_key, siacoin_id, siafund_id, transaction_id,
463
+ };
464
+
465
+ use super::*;
466
+
467
+ #[test]
468
+ fn test_serialize_hash256() {
469
+ let hash_str = "9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6";
470
+ let hash = Hash256(hex::decode(hash_str).unwrap().try_into().unwrap());
471
+
472
+ // binary
473
+ let mut hash_serialized: Vec<u8> = Vec::new();
474
+ hash.encode(&mut hash_serialized).unwrap();
475
+ assert_eq!(hash_serialized, hex::decode(hash_str).unwrap());
476
+ let hash_deserialized = Hash256::decode(&mut &hash_serialized[..]).unwrap();
477
+ assert_eq!(hash_deserialized, hash); // deserialize
478
+
479
+ // json
480
+ let hash_serialized = serde_json::to_string(&hash).unwrap();
481
+ let hash_deserialized: Hash256 = serde_json::from_str(&hash_serialized).unwrap();
482
+ assert_eq!(hash_serialized, format!("\"{hash_str}\"")); // serialize
483
+ assert_eq!(hash_deserialized, hash); // deserialize
484
+ }
485
+
486
+ #[test]
487
+ fn test_serialize_address() {
488
+ let addr_str = "8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c";
489
+ let checksum = "df32abee86f0";
490
+ let address = address!(
491
+ "8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8cdf32abee86f0"
492
+ );
493
+
494
+ // binary
495
+ let mut addr_serialized: Vec<u8> = Vec::new();
496
+ address.encode(&mut addr_serialized).unwrap();
497
+ assert_eq!(addr_serialized, hex::decode(addr_str).unwrap()); // serialize
498
+ let addr_deserialized = Address::decode(&mut &addr_serialized[..]).unwrap();
499
+ assert_eq!(addr_deserialized, address); // deserialize
500
+
501
+ // json
502
+ let addr_serialized = serde_json::to_string(&address).unwrap();
503
+ let addr_deserialized: Address = serde_json::from_str(&addr_serialized).unwrap();
504
+ assert_eq!(addr_serialized, format!("\"{addr_str}{checksum}\"")); // serialize
505
+ assert_eq!(addr_deserialized, address); // deserialize
506
+ }
507
+
508
+ #[test]
509
+ fn test_serialize_block() {
510
+ let b = Block {
511
+ parent_id: block_id!(
512
+ "8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c"
513
+ ),
514
+ nonce: 1236112,
515
+ timestamp: DateTime::UNIX_EPOCH,
516
+ miner_payouts: vec![SiacoinOutput {
517
+ value: Currency::new(57234234623612361),
518
+ address: address!(
519
+ "000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69"
520
+ ),
521
+ }],
522
+ transactions: vec![v1::Transaction {
523
+ siacoin_inputs: vec![v1::SiacoinInput {
524
+ parent_id: siacoin_id!(
525
+ "8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c"
526
+ ),
527
+ unlock_conditions: v1::UnlockConditions::standard_unlock_conditions(
528
+ public_key!(
529
+ "ed25519:8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c"
530
+ ),
531
+ ),
532
+ }],
533
+ siacoin_outputs: vec![SiacoinOutput {
534
+ value: Currency::new(67856467336433871),
535
+ address: address!(
536
+ "000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69"
537
+ ),
538
+ }],
539
+ file_contracts: Vec::new(),
540
+ file_contract_revisions: Vec::new(),
541
+ storage_proofs: Vec::new(),
542
+ siafund_inputs: Vec::new(),
543
+ siafund_outputs: Vec::new(),
544
+ miner_fees: Vec::new(),
545
+ arbitrary_data: Vec::new(),
546
+ signatures: Vec::new(),
547
+ }],
548
+ };
549
+
550
+ const BINARY_STR: &str = "8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c90dc120000000000000000000000000001000000000000000700000000000000cb563bafbb55c90000000000000000000000000000000000000000000000000000000000000000010000000000000001000000000000008fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c000000000000000001000000000000006564323535313900000000000000000020000000000000008fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c010000000000000001000000000000000700000000000000f11318f74d10cf000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
551
+ let mut serialized = Vec::new();
552
+ b.encode_v1(&mut serialized).unwrap();
553
+ assert_eq!(serialized, hex::decode(BINARY_STR).unwrap());
554
+ let deserialized = Block::decode_v1(&mut &serialized[..]).unwrap();
555
+ assert_eq!(deserialized, b);
556
+
557
+ const JSON_STR: &str = "{\"parentID\":\"8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c\",\"nonce\":1236112,\"timestamp\":\"1970-01-01T00:00:00Z\",\"minerPayouts\":[{\"value\":\"57234234623612361\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"}],\"transactions\":[{\"siacoinInputs\":[{\"parentID\":\"8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c\",\"unlockConditions\":{\"timelock\":0,\"publicKeys\":[\"ed25519:8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c\"],\"signaturesRequired\":1}}],\"siacoinOutputs\":[{\"value\":\"67856467336433871\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"}]}]}";
558
+ let serialized = serde_json::to_string(&b).unwrap();
559
+ assert_eq!(serialized, JSON_STR);
560
+ let deserialized: Block = serde_json::from_str(&serialized).unwrap();
561
+ assert_eq!(deserialized, b);
562
+ }
563
+
564
+ #[test]
565
+ fn test_transaction_derive() {
566
+ const TXN_JSON: &str = r#"{"siacoinInputs":[{"parentID":"750d22eff727689d1d8d1c83e513a30bb68ee7f9125a4dafc882459e34c2069d","unlockConditions":{"timelock":0,"publicKeys":["ed25519:800ed6c2760e3e4ba1ff00128585c8cf8fed2e3dc1e3da1eb92d49f405bd6360"],"signaturesRequired":6312611591377486220}}],"siacoinOutputs":[{"value":"890415399000000000000000000000000","address":"480a064b5fca13002a7fe575845154bbf0b3af4cc4f147cbed387d43cce3568ae2497366eaa7"}],"fileContracts":[{"filesize":0,"fileMerkleRoot":"0000000000000000000000000000000000000000000000000000000000000000","windowStart":10536451586783908586,"windowEnd":9324702155635244357,"payout":"0","validProofOutputs":[{"value":"1933513214000000000000000000000000","address":"944524fff2c49c401e748db37cfda7569fa6df35b704fe716394f2ac3f40ce87b4506e9906f0"}],"missedProofOutputs":[{"value":"2469287901000000000000000000000000","address":"1df67838262d7109ffcd9018f183b1eb33f05659a274b89ea6b52ff3617d34a770e9dd071d2e"}],"unlockHash":"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69","revisionNumber":9657412421282982780}],"fileContractRevisions":[{"parentID":"e4e26d93771d3bbb3d9dd306105d77cfb3a6254d1cc3495903af6e013442c63c","unlockConditions":{"timelock":0,"publicKeys":["ed25519:e6b9cde4eb058f8ecbb083d99779cb0f6d518d5386f019af6ead09fa52de8567"],"signaturesRequired":206644730660526450},"revisionNumber":10595710523108536025,"filesize":0,"fileMerkleRoot":"0000000000000000000000000000000000000000000000000000000000000000","windowStart":4348934140507359445,"windowEnd":14012366839994454386,"validProofOutputs":[{"value":"2435858510000000000000000000000000","address":"543bc0eda69f728d0a0fbce08e5bfc5ed7b961300e0af226949e135f7d12e32f0544e5262d6f"}],"missedProofOutputs":[{"value":"880343701000000000000000000000000","address":"7b7f9aee981fe0d93bb3f49c6233cf847ebdd39d7dc5253f7fc330df2167073b35f035703237"}],"unlockHash":"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69"}],"storageProofs":[{"parentID":"c0b9e98c9e03a2740c75d673871c1ee91f36d1bb329ff3ddbf1dfa8c6e1a64eb","leaf":"b78fa521dc62d9ced82bc3b61e0aa5a5c221d6cca5db63d94c9879543fb98c0a971094a89cd4408487ae32902248d321b545f9a051729aa0bb1725b848e3d453","proof":["fe08c0a061475e7e5dec19e717cf98792fa7b555d0b5d3540a05db09f59ab8de"]}],"minerFees":["241119475000000000000000000000000"],"arbitraryData":["2shzIHEUJYwuNHz6c/gPz+aTEWZRTpDTmemX9yYAKlY="],"signatures":[{"parentID":"06d1fca03c5ddd9b09116db1b97c5451f7dc792b05362969f83e3e8dc1007f46","publicKeyIndex":6088345341283457116,"timelock":2014247885072555224,"coveredFields":{"wholeTransaction":true},"signature":"2XNEKGZrl9RhMa2JmGsvcmqQWAIX/uxtMwLnPI6VJPcXqub6qYIuoAThYp9NAwadk+1GG6CXC66g4rOjFYuNSA=="}]}"#;
567
+
568
+ const EXPECTED_TRANSACTION_ID: TransactionID =
569
+ transaction_id!("71a10d363f4af09c3fbce499b725067b0b19afe2bc9a8236704e85256f3244a6");
570
+ const EXPECTED_SIACOIN_OUTPUT_ID: SiacoinOutputID =
571
+ siacoin_id!("ea315efdd5914c54e8082d0de90b5afa9d4b92103d60661ec86b2a095413d836");
572
+ const EXPECTED_SIAFUND_OUTPUT_ID: SiafundOutputID =
573
+ siafund_id!("a8190ea7b4d41e08f45f27653b882faf8ff9fd57bb098d7022f105ef142279ec");
574
+ const EXPECTED_FILE_CONTRACT_ID: FileContractID =
575
+ contract_id!("ff7102bb111a64c7ff8a3cd68dbc962a03a8943065c3852a359662c8935fa979");
576
+
577
+ let txn: v1::Transaction =
578
+ serde_json::from_str(TXN_JSON).expect("transaction to deserialize");
579
+
580
+ assert_eq!(txn.id(), EXPECTED_TRANSACTION_ID, "transaction id");
581
+
582
+ assert_eq!(
583
+ txn.siacoin_output_id(678569214627704587),
584
+ EXPECTED_SIACOIN_OUTPUT_ID,
585
+ "siacoin output id"
586
+ );
587
+
588
+ assert_eq!(
589
+ txn.siafund_output_id(8940170890223196046),
590
+ EXPECTED_SIAFUND_OUTPUT_ID,
591
+ "siafund output id"
592
+ );
593
+
594
+ assert_eq!(
595
+ txn.file_contract_id(3470616158951613631),
596
+ EXPECTED_FILE_CONTRACT_ID,
597
+ "file contract id"
598
+ );
599
+ }
600
+
601
+ #[test]
602
+ fn test_transaction_id_v2_derive() {
603
+ const EXPECTED_V2_SIACOIN_OUTPUT_ID: SiacoinOutputID =
604
+ siacoin_id!("f74e0d8eae89ec820184c9bacfcad0181c781c02020f8a3fcbc82fd4ebf2fcf0");
605
+ const EXPECTED_V2_SIAFUND_OUTPUT_ID: SiafundOutputID =
606
+ siafund_id!("f7d9ad77bfe9a102ef9590f97024f3aa8f54877d10447c128b52d5ca18cca983");
607
+ const EXPECTED_V2_FILE_CONTRACT_ID: FileContractID =
608
+ contract_id!("c67764bc06df3dd933e0d4e93c6f7cbe5b56670d1baae156b578d417f08e65cf");
609
+
610
+ let txn_id =
611
+ transaction_id!("168ecf3133ae713c26f90fe1790fb7536f12cc2a492985627856b77c6ad99070");
612
+
613
+ assert_eq!(
614
+ txn_id.v2_siacoin_output_id(3543556734851495409),
615
+ EXPECTED_V2_SIACOIN_OUTPUT_ID,
616
+ "v2 siacoin output id"
617
+ );
618
+
619
+ assert_eq!(
620
+ txn_id.v2_siafund_output_id(4957302981402025980),
621
+ EXPECTED_V2_SIAFUND_OUTPUT_ID,
622
+ "v2 siafund output id"
623
+ );
624
+
625
+ assert_eq!(
626
+ txn_id.v2_file_contract_id(5375460735837768427),
627
+ EXPECTED_V2_FILE_CONTRACT_ID,
628
+ "v2 file contract id"
629
+ );
630
+ }
631
+
632
+ #[test]
633
+ fn test_block_id_derive() {
634
+ const EXPECTED_FOUNDATION_OUTPUT_ID: SiacoinOutputID =
635
+ siacoin_id!("159e2c4159a112ea9a70242d541a26f49fce41b6126f9105eab9b68dba4cfafb");
636
+ const EXPECTED_MINER_OUTPUT_ID: SiacoinOutputID =
637
+ siacoin_id!("69e68779991392663d808276e6661d94628632354e258d8ab6724de1d9ca6208");
638
+
639
+ let block_id =
640
+ block_id!("c56d879b07b27fab3bdd06b833dbd1ad7eb167058851f543a517308b634a80a1");
641
+
642
+ assert_eq!(
643
+ block_id.foundation_output_id(),
644
+ EXPECTED_FOUNDATION_OUTPUT_ID,
645
+ "foundation output id"
646
+ );
647
+
648
+ assert_eq!(
649
+ block_id.miner_output_id(3072616177397065894),
650
+ EXPECTED_MINER_OUTPUT_ID,
651
+ "miner output id"
652
+ );
653
+ }
654
+
655
+ #[test]
656
+ fn test_siafund_output_id_derive() {
657
+ const EXPECTED_CLAIM_ID: SiacoinOutputID =
658
+ siacoin_id!("8eec57722c2ac040e34322ba77cb6b488ac8081f856d93bea1bf1bef42aeaabb");
659
+ const EXPECTED_V2_CLAIM_ID: SiacoinOutputID =
660
+ siacoin_id!("b949006c65c70b5973da46cc783981d701dd854316e7efb1947c0b5f2fdc8db4");
661
+
662
+ let siafund_output_id =
663
+ siafund_id!("58ea19fd87ae5e10f928035e1021c3d9ee091fb3c0bbd5a1a6af41eea12e0f85");
664
+
665
+ assert_eq!(
666
+ siafund_output_id.claim_output_id(),
667
+ EXPECTED_CLAIM_ID,
668
+ "claim output id"
669
+ );
670
+
671
+ assert_eq!(
672
+ siafund_output_id.v2_claim_output_id(),
673
+ EXPECTED_V2_CLAIM_ID,
674
+ "v2 claim output id"
675
+ );
676
+ }
677
+ }