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 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/prompts.ts
4
+ import crypto from "crypto";
5
+ import * as p from "@clack/prompts";
6
+ import pc from "picocolors";
7
+ async function runPrompts() {
8
+ p.intro(pc.green("Create Sia App"));
9
+ const projectName = await p.text({
10
+ message: "What is your project name?",
11
+ placeholder: "my-sia-app",
12
+ validate(value) {
13
+ if (!value.trim()) return "Project name is required";
14
+ if (!/^[a-z0-9._-]+$/i.test(value.trim()))
15
+ return "Use only letters, numbers, dashes, dots, and underscores";
16
+ }
17
+ });
18
+ if (p.isCancel(projectName)) {
19
+ p.cancel("Cancelled.");
20
+ return null;
21
+ }
22
+ const keyChoice = await p.select({
23
+ message: "App key setup",
24
+ options: [
25
+ {
26
+ value: "generate",
27
+ label: "Generate a new app key",
28
+ hint: "Recommended"
29
+ },
30
+ { value: "existing", label: "Enter an existing app key" }
31
+ ]
32
+ });
33
+ if (p.isCancel(keyChoice)) {
34
+ p.cancel("Cancelled.");
35
+ return null;
36
+ }
37
+ let appKey;
38
+ if (keyChoice === "existing") {
39
+ const existingKey = await p.text({
40
+ message: "Enter your app key (64-char hex)",
41
+ validate(value) {
42
+ if (!/^[a-f0-9]{64}$/i.test(value.trim()))
43
+ return "App key must be a 64-character hex string";
44
+ }
45
+ });
46
+ if (p.isCancel(existingKey)) {
47
+ p.cancel("Cancelled.");
48
+ return null;
49
+ }
50
+ appKey = existingKey.trim();
51
+ } else {
52
+ appKey = crypto.randomBytes(32).toString("hex");
53
+ p.log.info(`Generated app key: ${pc.cyan(appKey)}`);
54
+ }
55
+ const indexerUrl = await p.text({
56
+ message: "Indexer URL",
57
+ initialValue: "https://app.sia.storage"
58
+ });
59
+ if (p.isCancel(indexerUrl)) {
60
+ p.cancel("Cancelled.");
61
+ return null;
62
+ }
63
+ const appDescription = await p.text({
64
+ message: "App description (optional)",
65
+ placeholder: "My decentralized storage app",
66
+ defaultValue: "A Sia storage app"
67
+ });
68
+ if (p.isCancel(appDescription)) {
69
+ p.cancel("Cancelled.");
70
+ return null;
71
+ }
72
+ return {
73
+ projectName: projectName.trim(),
74
+ appKey,
75
+ indexerUrl: indexerUrl.trim(),
76
+ appDescription: appDescription.trim() || "A Sia storage app"
77
+ };
78
+ }
79
+
80
+ // src/scaffold.ts
81
+ import { execSync } from "child_process";
82
+ import fs from "fs";
83
+ import path from "path";
84
+ import { fileURLToPath } from "url";
85
+ import * as p2 from "@clack/prompts";
86
+ import pc2 from "picocolors";
87
+ var __dirname = path.dirname(fileURLToPath(import.meta.url));
88
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", ".git"]);
89
+ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([".wasm", ".png", ".jpg", ".ico", ".svg"]);
90
+ function findTemplateDir() {
91
+ const published = path.resolve(__dirname, "..", "template");
92
+ if (fs.existsSync(published)) return published;
93
+ const local = path.resolve(__dirname, "..", "..", "..", "template");
94
+ if (fs.existsSync(local)) return local;
95
+ throw new Error("Could not find template directory");
96
+ }
97
+ function copyDir(src, dest, replacements) {
98
+ fs.mkdirSync(dest, { recursive: true });
99
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
100
+ if (SKIP_DIRS.has(entry.name)) continue;
101
+ const srcPath = path.join(src, entry.name);
102
+ let destName = entry.name;
103
+ if (destName === "_gitignore") destName = ".gitignore";
104
+ const destPath = path.join(dest, destName);
105
+ if (entry.isDirectory()) {
106
+ copyDir(srcPath, destPath, replacements);
107
+ } else {
108
+ const ext = path.extname(entry.name).toLowerCase();
109
+ if (BINARY_EXTENSIONS.has(ext)) {
110
+ fs.copyFileSync(srcPath, destPath);
111
+ } else {
112
+ let content = fs.readFileSync(srcPath, "utf-8");
113
+ for (const [search, replace] of replacements) {
114
+ content = content.replaceAll(search, replace);
115
+ }
116
+ fs.writeFileSync(destPath, content);
117
+ }
118
+ }
119
+ }
120
+ }
121
+ function detectPackageManager() {
122
+ try {
123
+ execSync("bun --version", { stdio: "ignore" });
124
+ return "bun";
125
+ } catch {
126
+ return "npm";
127
+ }
128
+ }
129
+ async function scaffold(options) {
130
+ const { projectName, appKey, indexerUrl, appDescription } = options;
131
+ const targetDir = path.resolve(process.cwd(), projectName);
132
+ if (fs.existsSync(targetDir)) {
133
+ const entries = fs.readdirSync(targetDir);
134
+ if (entries.length > 0) {
135
+ p2.log.error(
136
+ `Directory ${pc2.red(projectName)} already exists and is not empty.`
137
+ );
138
+ process.exit(1);
139
+ }
140
+ }
141
+ const spinner2 = p2.spinner();
142
+ spinner2.start("Creating project...");
143
+ const templateDir = findTemplateDir();
144
+ const replacements = [
145
+ ["{{APP_NAME}}", projectName],
146
+ ["{{APP_KEY}}", appKey],
147
+ ["{{INDEXER_URL}}", indexerUrl],
148
+ ["{{APP_DESCRIPTION}}", appDescription]
149
+ ];
150
+ copyDir(templateDir, targetDir, replacements);
151
+ spinner2.message("Copied template files");
152
+ const pm = detectPackageManager();
153
+ spinner2.message(`Installing dependencies with ${pm}...`);
154
+ try {
155
+ execSync(`${pm} install`, { cwd: targetDir, stdio: "ignore" });
156
+ spinner2.stop("Project created successfully");
157
+ } catch {
158
+ spinner2.stop("Project created (install failed \u2014 run manually)");
159
+ }
160
+ p2.note(
161
+ [
162
+ `${pc2.green("cd")} ${projectName}`,
163
+ `${pc2.green(pm === "bun" ? "bun dev" : "npm run dev")}`
164
+ ].join("\n"),
165
+ "Next steps"
166
+ );
167
+ p2.outro(pc2.green("Happy building!"));
168
+ }
169
+
170
+ // src/index.ts
171
+ async function main() {
172
+ const options = await runPrompts();
173
+ if (!options) process.exit(0);
174
+ await scaffold(options);
175
+ }
176
+ main().catch((e) => {
177
+ console.error(e);
178
+ process.exit(1);
179
+ });
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "create-sia-app",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "bin": {
6
+ "create-sia-app": "./dist/index.js"
7
+ },
8
+ "files": [
9
+ "dist/",
10
+ "template/"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsup src/index.ts --format esm --dts --clean && rm -rf ./template && cp -r ../../template ./template && rm -rf ./template/node_modules",
14
+ "dev": "tsup src/index.ts --format esm --watch",
15
+ "release:patch": "npm version --no-git-tag-version patch && bun run release:push",
16
+ "release:minor": "npm version --no-git-tag-version minor && bun run release:push",
17
+ "release:major": "npm version --no-git-tag-version major && bun run release:push",
18
+ "release:push": "git add package.json && git commit -m \"v$(node -p 'require(\"./package.json\").version')\" && git tag v$(node -p 'require(\"./package.json\").version') && git push && git push --tags"
19
+ },
20
+ "dependencies": {
21
+ "@clack/prompts": "^0.10.0",
22
+ "picocolors": "^1.1.1"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^22.15.0",
26
+ "tsup": "^8.4.0",
27
+ "typescript": "~5.9.3"
28
+ }
29
+ }
@@ -0,0 +1,160 @@
1
+ # Sia Starter — AI Assistant Guide
2
+
3
+ ## Overview
4
+
5
+ A starter template for building decentralized storage apps on the Sia network. Uses a WASM SDK compiled from Rust to handle encryption, uploads, downloads, and key management.
6
+
7
+ **Tech stack:** React 19, TypeScript, Vite, Tailwind CSS 4, Zustand, WASM (Rust)
8
+
9
+ ## Architecture
10
+
11
+ ### Auth Flow State Machine
12
+
13
+ The app uses a step-based auth flow managed by Zustand (`src/stores/auth.ts`):
14
+
15
+ ```
16
+ loading → connect → approve → recovery → connected
17
+ ```
18
+
19
+ - **loading**: WASM initializes, checks for stored app key
20
+ - **connect**: User enters indexer URL, app requests connection
21
+ - **approve**: User visits approval URL in another tab
22
+ - **recovery**: User generates or enters 12-word recovery phrase
23
+ - **connected**: SDK is ready, main app UI renders
24
+
25
+ ### WASM SDK
26
+
27
+ The SDK (`indexd_wasm`) is a Rust-compiled WASM module that handles:
28
+ - Encrypted file uploads/downloads (erasure coding + encryption)
29
+ - Key derivation from recovery phrases (BIP-39)
30
+ - Object pinning and metadata management
31
+ - Connection auth with indexers
32
+
33
+ Initialized via `src/lib/sdk.ts`, which wraps the WASM init and re-exports types.
34
+
35
+ ### Zustand Persistence
36
+
37
+ Auth state persists to localStorage via Zustand's `persist` middleware. The storage key is `{app-name}-auth`. Persisted fields: `storedKeyHex`, `indexerUrl`, `performancePreset`.
38
+
39
+ ## Key Files
40
+
41
+ | File | Description |
42
+ |------|-------------|
43
+ | `src/lib/sdk.ts` | WASM init + re-exports (AppKey, Builder, SDK, PinnedObject) |
44
+ | `src/lib/constants.ts` | App key, indexer URL, app metadata |
45
+ | `src/lib/hex.ts` | Hex encode/decode utilities |
46
+ | `src/lib/format.ts` | FileMetadata type, formatBytes, formatDate |
47
+ | `src/lib/wasm-env.ts` | `now()` stub for WASM chrono |
48
+ | `src/stores/auth.ts` | Auth state machine + performance presets |
49
+ | `src/components/auth/AuthFlow.tsx` | Auth orchestrator |
50
+ | `src/components/auth/ConnectScreen.tsx` | Indexer connection + CORS fallback |
51
+ | `src/components/auth/ApproveScreen.tsx` | Approval polling |
52
+ | `src/components/auth/RecoveryScreen.tsx` | Recovery phrase generation/import |
53
+ | `src/components/upload/UploadZone.tsx` | File upload dropzone + file list |
54
+ | `src/components/DevNote.tsx` | Developer callout component (remove in production) |
55
+ | `wasm/indexd_wasm/` | Pre-built WASM binary + JS glue + types |
56
+ | `rust/sia-sdk-rs/` | Rust SDK source (cloned on install, gitignored) |
57
+
58
+ ## SDK Usage Patterns
59
+
60
+ ### Upload a file
61
+
62
+ ```ts
63
+ const data = new Uint8Array(arrayBuffer)
64
+ const pinnedObject = await sdk.uploadWithProgress(data, (current, total) => {
65
+ console.log(`${current}/${total} shards`)
66
+ })
67
+ pinnedObject.updateMetadata(new TextEncoder().encode(JSON.stringify({
68
+ name: 'file.txt',
69
+ type: 'text/plain',
70
+ size: data.byteLength,
71
+ hash: '...',
72
+ })))
73
+ await sdk.pinObject(pinnedObject)
74
+ await sdk.updateObjectMetadata(pinnedObject)
75
+ ```
76
+
77
+ ### Download a file
78
+
79
+ ```ts
80
+ const object = await sdk.object(objectId)
81
+ const data = await sdk.downloadWithProgress(object, (current, total) => {
82
+ console.log(`${current}/${total} slabs`)
83
+ })
84
+ // data is Uint8Array
85
+ ```
86
+
87
+ ### List files
88
+
89
+ ```ts
90
+ const events = await sdk.objectEvents(null, 100)
91
+ for (const event of events) {
92
+ if (!event.deleted && event.object) {
93
+ const meta = JSON.parse(new TextDecoder().decode(event.object.metadata()))
94
+ console.log(meta.name, event.object.size())
95
+ }
96
+ }
97
+ ```
98
+
99
+ ### Delete a file
100
+
101
+ ```ts
102
+ await sdk.deleteObject(objectId)
103
+ ```
104
+
105
+ ### Share a file
106
+
107
+ ```ts
108
+ const validUntil = Date.now() + 7 * 24 * 60 * 60 * 1000 // 7 days
109
+ const shareUrl = sdk.shareObject(pinnedObject, validUntil)
110
+ ```
111
+
112
+ ### Download shared file
113
+
114
+ ```ts
115
+ const object = await sdk.sharedObject(shareUrl)
116
+ const data = await sdk.download(object)
117
+ ```
118
+
119
+ ## Customization
120
+
121
+ ### Change app key
122
+
123
+ Edit `src/lib/constants.ts`. The app key is a 32-byte hex string that identifies your app to the indexer. Generate one with:
124
+
125
+ ```ts
126
+ crypto.randomBytes(32).toString('hex')
127
+ ```
128
+
129
+ ### Replace the upload UI
130
+
131
+ The post-auth UI is rendered in `src/App.tsx`. Replace `<UploadZone />` with your own component. The SDK is available via `useAuthStore((s) => s.sdk)`.
132
+
133
+ ### Add routes
134
+
135
+ Install `react-router-dom` and wrap your app. The auth flow should gate all routes.
136
+
137
+ ### Change performance defaults
138
+
139
+ Edit the `PRESETS` object in `src/stores/auth.ts`.
140
+
141
+ ## WASM / Rust Setup
142
+
143
+ The `wasm/indexd_wasm/` directory contains pre-built WASM artifacts. The `rust/sia-sdk-rs/` directory is cloned automatically on `bun install` from [alexfreska/sia-sdk-rs](https://github.com/alexfreska/sia-sdk-rs) (branch `alex/wasm-experimental`). To rebuild:
144
+
145
+ ```bash
146
+ cd rust/sia-sdk-rs
147
+ cargo install wasm-pack
148
+ wasm-pack build indexd_wasm --target web --out-dir pkg
149
+ ```
150
+
151
+ Then copy the output to `wasm/indexd_wasm/`.
152
+
153
+ ## Common Commands
154
+
155
+ ```bash
156
+ bun install # Install dependencies
157
+ bun dev # Start dev server
158
+ bun run build # Production build
159
+ bun run check # Lint with Biome
160
+ ```
@@ -0,0 +1,102 @@
1
+ # Sia Starter
2
+
3
+ Build private, encrypted storage apps on the [Sia](https://sia.tech) network. Data is end-to-end encrypted in the browser and transferred directly to and from Sia hosts — no proxies, gateways, or portals. Nobody but the user can see their data.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ bun install
9
+ bun dev
10
+ ```
11
+
12
+ Open [http://localhost:5173](http://localhost:5173) in your browser.
13
+
14
+ ## Why Sia
15
+
16
+ - **Private by default** — All data is encrypted client-side before it leaves the browser. Encryption keys are derived from the user's recovery phrase and never shared.
17
+ - **Direct host connections** — Uploads and downloads happen directly between the browser and Sia hosts over WebTransport. There is no middleman that can see or intercept data.
18
+ - **No trusted third parties** — The indexer service handles payments and trustlessly repairs data so your app doesn't need to run 24/7, but it never sees the encrypted data shards. It can't read, modify, or withhold your files.
19
+ - **Erasure coded** — Data is split into redundant shards across many hosts. Files survive even if individual hosts go offline.
20
+ - **Decentralized** — Storage is provided by a global network of independent hosts competing on price and performance.
21
+
22
+ ## What This Template Includes
23
+
24
+ - **Full auth flow** — connect to an indexer, approve the connection, set up a recovery phrase
25
+ - **File upload** — drag & drop files, track upload progress, list uploaded files
26
+ - **WASM SDK** — Rust-compiled SDK handles encryption, erasure coding, and direct host transfers in the browser
27
+ - **Developer notes** — amber callout boxes throughout the UI explaining how everything works (remove them when you ship)
28
+
29
+ ## How It Works
30
+
31
+ ### Architecture
32
+
33
+ ```
34
+ Browser (your app)
35
+ ├── encrypts data client-side
36
+ ├── erasure codes into shards
37
+ └── uploads/downloads shards directly to/from Sia hosts (WebTransport)
38
+
39
+ Indexer service
40
+ ├── manages payments to hosts
41
+ ├── tracks which hosts store which shards
42
+ └── repairs data if hosts go offline
43
+ └── cannot see encrypted data
44
+ ```
45
+
46
+ ### Auth Flow
47
+
48
+ 1. **Connect** — Enter an indexer URL (default: `https://app.sia.storage`). The app sends your app metadata to request a connection.
49
+ 2. **Approve** — Visit the approval URL in another tab to authorize your app.
50
+ 3. **Recovery Phrase** — Generate a new 12-word phrase or enter an existing one. This deterministically derives all cryptographic keys.
51
+ 4. **Connected** — The SDK is ready. Your app key is saved to localStorage for future sessions.
52
+
53
+ ### Upload Flow
54
+
55
+ Files are encrypted in the browser, erasure coded into shards, and uploaded directly to Sia hosts. The SDK handles all of this — your code just calls `sdk.upload(data)`. Metadata (filename, type, size) is also encrypted and pinned to the indexer.
56
+
57
+ ## Customization
58
+
59
+ ### App Key & Metadata
60
+
61
+ Edit `src/lib/constants.ts` to set your app key, name, description, and indexer URL.
62
+
63
+ ### Replace the Upload UI
64
+
65
+ The main post-auth component is `src/components/upload/UploadZone.tsx`. Replace it with your own UI — the SDK is available via:
66
+
67
+ ```tsx
68
+ const sdk = useAuthStore((s) => s.sdk)
69
+ ```
70
+
71
+ ### Performance Presets
72
+
73
+ Edit `src/stores/auth.ts` to change the `PRESETS` object for concurrent operation limits.
74
+
75
+ ## Tech Stack
76
+
77
+ - [React](https://react.dev) 19
78
+ - [TypeScript](https://www.typescriptlang.org)
79
+ - [Vite](https://vite.dev)
80
+ - [Tailwind CSS](https://tailwindcss.com) 4
81
+ - [Zustand](https://zustand.docs.pmnd.rs) (state management)
82
+ - [Biome](https://biomejs.dev) (linting & formatting)
83
+ - [indexd_wasm](https://github.com/SiaFoundation/sia-sdk-rs) (Sia SDK, compiled from Rust to WASM)
84
+
85
+ ## Project Structure
86
+
87
+ ```
88
+ src/
89
+ ├── lib/ # SDK wrapper, constants, utilities
90
+ ├── stores/ # Zustand auth store
91
+ └── components/
92
+ ├── auth/ # Auth flow screens
93
+ ├── upload/ # Upload dropzone
94
+ └── DevNote.tsx # Developer callout (remove in production)
95
+ wasm/ # Pre-built WASM binary
96
+ rust/ # Rust SDK source (cloned on install)
97
+ ```
98
+
99
+ ## Learn More
100
+
101
+ - [Sia Documentation](https://docs.sia.tech)
102
+ - [Sia SDK (Rust)](https://github.com/SiaFoundation/sia-sdk-rs)
@@ -0,0 +1,5 @@
1
+ node_modules
2
+ dist
3
+ *.local
4
+ .DS_Store
5
+ rust/sia-sdk-rs/
@@ -0,0 +1,40 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.4.1/schema.json",
3
+ "vcs": {
4
+ "enabled": false,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": false
7
+ },
8
+ "files": {
9
+ "ignoreUnknown": false,
10
+ "includes": ["src/**", "scripts/**", "vite.config.ts"]
11
+ },
12
+ "formatter": {
13
+ "enabled": true,
14
+ "indentStyle": "space",
15
+ "indentWidth": 2
16
+ },
17
+ "linter": {
18
+ "enabled": true,
19
+ "rules": {
20
+ "recommended": true,
21
+ "suspicious": {
22
+ "noArrayIndexKey": "off"
23
+ }
24
+ }
25
+ },
26
+ "javascript": {
27
+ "formatter": {
28
+ "quoteStyle": "single",
29
+ "semicolons": "asNeeded"
30
+ }
31
+ },
32
+ "assist": {
33
+ "enabled": true,
34
+ "actions": {
35
+ "source": {
36
+ "organizeImports": "on"
37
+ }
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>{{APP_NAME}}</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "{{APP_NAME}}",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "preview": "vite preview",
10
+ "check": "biome check .",
11
+ "postinstall": "node scripts/setup-rust.js"
12
+ },
13
+ "dependencies": {
14
+ "react": "^19.2.0",
15
+ "react-dom": "^19.2.0",
16
+ "zustand": "^5.0.11"
17
+ },
18
+ "devDependencies": {
19
+ "@biomejs/biome": "^2.4.0",
20
+ "@tailwindcss/vite": "^4.1.18",
21
+ "@types/react": "^19.2.7",
22
+ "@types/react-dom": "^19.2.3",
23
+ "@vitejs/plugin-react": "^5.1.1",
24
+ "tailwindcss": "^4.1.18",
25
+ "typescript": "~5.9.3",
26
+ "vite": "^7.3.1",
27
+ "vite-plugin-top-level-await": "^1.6.0",
28
+ "vite-plugin-wasm": "^3.5.0"
29
+ }
30
+ }
@@ -0,0 +1,16 @@
1
+ # Rust SDK Source
2
+
3
+ The Rust SDK source (`sia-sdk-rs/`) is cloned automatically on `bun install` from [alexfreska/sia-sdk-rs](https://github.com/alexfreska/sia-sdk-rs) (branch `alex/wasm-experimental`).
4
+
5
+ The pre-built WASM in `../wasm/` is what the app actually uses at runtime. The Rust source is only needed if you want to rebuild the WASM module.
6
+
7
+ ## Rebuilding WASM
8
+
9
+ ```bash
10
+ cd sia-sdk-rs
11
+ cargo install wasm-pack
12
+ wasm-pack build indexd_wasm --target web --out-dir pkg
13
+ cp indexd_wasm/pkg/indexd_wasm_bg.wasm ../../wasm/indexd_wasm/
14
+ cp indexd_wasm/pkg/indexd_wasm.js ../../wasm/indexd_wasm/
15
+ cp indexd_wasm/pkg/indexd_wasm.d.ts ../../wasm/indexd_wasm/
16
+ ```
@@ -0,0 +1,6 @@
1
+ ---
2
+ indexd: patch
3
+ indexd_ffi: patch
4
+ ---
5
+
6
+ # Added cancel function to cancel inflight packed uploads.
@@ -0,0 +1,16 @@
1
+ ---
2
+ sia_sdk: patch
3
+ indexd: patch
4
+ indexd_ffi: patch
5
+ sia_sdk_derive: patch
6
+ ---
7
+
8
+ # Check if we have enough hosts prior to encoding in upload_slabs
9
+
10
+ #261 by @Alrighttt
11
+
12
+ Fixes https://github.com/SiaFoundation/sia-sdk-rs/issues/251
13
+
14
+ - Added an `available_for_upload` method that returns the amount of known hosts marked `good_for_upload`.
15
+ - Added a check in `upload_slabs` that verifies we have enough good hosts prior to encoding any data.
16
+ - Adds a variant to `QueueError` for `upload_slabs`'s new failure case. This enables testing for this new case specifically.
@@ -0,0 +1,5 @@
1
+ ---
2
+ indexd_ffi: patch
3
+ ---
4
+
5
+ # Fix slab length in packed object
@@ -0,0 +1,13 @@
1
+ ---
2
+ indexd: patch
3
+ ---
4
+
5
+ # Fix upload racing race conditon
6
+
7
+ #258 by @Alrighttt
8
+
9
+ This fixes a race condition in the upload logic that could happen when the amount of healthy hosts is nearly the same as the amount of shards. This could happen when the racing mechanism was triggered prior to all of the initial shards being assigned a host. The slow hosts would be consumed from the HostQueue without completing the upload. This would cause a latter shard to hit a QueueError::NoMoreHosts error.
10
+
11
+ This changes the upload behavior so that each shard has a host assigned before any upload begins.
12
+
13
+ A `set_slow_hosts` method was added to the `MockRHP4Client` to allow easily testing these conditions. This mimics a similar mechanism from the Go SDK.
@@ -0,0 +1,5 @@
1
+ ---
2
+ indexd_ffi: patch
3
+ ---
4
+
5
+ # Improved parallelism of packed uploads.
@@ -0,0 +1,5 @@
1
+ ---
2
+ indexd_ffi: patch
3
+ ---
4
+
5
+ # Progress callback will now be called as expected for packed uploads.
@@ -0,0 +1,10 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: cargo
4
+ directory: "/"
5
+ schedule:
6
+ interval: weekly
7
+ groups:
8
+ all-dependencies:
9
+ patterns:
10
+ - "*"