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.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +179 -0
- package/package.json +29 -0
- package/template/CLAUDE.md +160 -0
- package/template/README.md +102 -0
- package/template/_gitignore +5 -0
- package/template/biome.json +40 -0
- package/template/index.html +13 -0
- package/template/package.json +30 -0
- package/template/rust/README.md +16 -0
- package/template/rust/sia-sdk-rs/.changeset/added_cancel_function_to_cancel_inflight_packed_uploads.md +6 -0
- package/template/rust/sia-sdk-rs/.changeset/check_if_we_have_enough_hosts_prior_to_encoding_in_upload_slabs.md +16 -0
- package/template/rust/sia-sdk-rs/.changeset/fix_slab_length_in_packed_object.md +5 -0
- package/template/rust/sia-sdk-rs/.changeset/fix_upload_racing_race_conditon.md +13 -0
- package/template/rust/sia-sdk-rs/.changeset/improved_parallelism_of_packed_uploads.md +5 -0
- package/template/rust/sia-sdk-rs/.changeset/progress_callback_will_now_be_called_as_expected_for_packed_uploads.md +5 -0
- package/template/rust/sia-sdk-rs/.github/dependabot.yml +10 -0
- package/template/rust/sia-sdk-rs/.github/workflows/main.yml +36 -0
- package/template/rust/sia-sdk-rs/.github/workflows/prepare-release.yml +34 -0
- package/template/rust/sia-sdk-rs/.github/workflows/release.yml +30 -0
- package/template/rust/sia-sdk-rs/.rustfmt.toml +4 -0
- package/template/rust/sia-sdk-rs/Cargo.lock +4127 -0
- package/template/rust/sia-sdk-rs/Cargo.toml +3 -0
- package/template/rust/sia-sdk-rs/LICENSE +21 -0
- package/template/rust/sia-sdk-rs/README.md +30 -0
- package/template/rust/sia-sdk-rs/indexd/CHANGELOG.md +79 -0
- package/template/rust/sia-sdk-rs/indexd/Cargo.toml +79 -0
- package/template/rust/sia-sdk-rs/indexd/benches/upload.rs +258 -0
- package/template/rust/sia-sdk-rs/indexd/src/app_client.rs +1710 -0
- package/template/rust/sia-sdk-rs/indexd/src/builder.rs +354 -0
- package/template/rust/sia-sdk-rs/indexd/src/download.rs +379 -0
- package/template/rust/sia-sdk-rs/indexd/src/hosts.rs +659 -0
- package/template/rust/sia-sdk-rs/indexd/src/lib.rs +827 -0
- package/template/rust/sia-sdk-rs/indexd/src/mock.rs +162 -0
- package/template/rust/sia-sdk-rs/indexd/src/object_encryption.rs +125 -0
- package/template/rust/sia-sdk-rs/indexd/src/quic.rs +575 -0
- package/template/rust/sia-sdk-rs/indexd/src/rhp4.rs +52 -0
- package/template/rust/sia-sdk-rs/indexd/src/slabs.rs +497 -0
- package/template/rust/sia-sdk-rs/indexd/src/upload.rs +629 -0
- package/template/rust/sia-sdk-rs/indexd/src/wasm_time.rs +41 -0
- package/template/rust/sia-sdk-rs/indexd/src/web_transport.rs +398 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/CHANGELOG.md +76 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/Cargo.toml +47 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/examples/python/README.md +10 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/examples/python/example.py +130 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/src/bin/uniffi-bindgen.rs +3 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/src/builder.rs +377 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/src/io.rs +155 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/src/lib.rs +1039 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/src/logging.rs +58 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/src/tls.rs +23 -0
- package/template/rust/sia-sdk-rs/indexd_wasm/Cargo.toml +33 -0
- package/template/rust/sia-sdk-rs/indexd_wasm/src/lib.rs +818 -0
- package/template/rust/sia-sdk-rs/knope.toml +54 -0
- package/template/rust/sia-sdk-rs/sia_derive/CHANGELOG.md +38 -0
- package/template/rust/sia-sdk-rs/sia_derive/Cargo.toml +19 -0
- package/template/rust/sia-sdk-rs/sia_derive/src/lib.rs +278 -0
- package/template/rust/sia-sdk-rs/sia_sdk/CHANGELOG.md +91 -0
- package/template/rust/sia-sdk-rs/sia_sdk/Cargo.toml +59 -0
- package/template/rust/sia-sdk-rs/sia_sdk/benches/merkle_root.rs +12 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/blake2.rs +22 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/consensus.rs +767 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/encoding/v1.rs +257 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/encoding/v2.rs +291 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/encoding.rs +26 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/encoding_async/v2.rs +367 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/encoding_async.rs +6 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/encryption.rs +303 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/erasure_coding.rs +347 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/lib.rs +15 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/macros.rs +435 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/merkle.rs +112 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/merkle.rs +357 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/rpc.rs +1507 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/types.rs +146 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/rhp.rs +7 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/seed.rs +278 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/signing.rs +236 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/common.rs +677 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/currency.rs +450 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/specifier.rs +110 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/spendpolicy.rs +778 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/utils.rs +117 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/v1.rs +1737 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/v2.rs +1726 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/work.rs +59 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types.rs +16 -0
- package/template/scripts/setup-rust.js +29 -0
- package/template/src/App.tsx +13 -0
- package/template/src/components/DevNote.tsx +21 -0
- package/template/src/components/auth/ApproveScreen.tsx +84 -0
- package/template/src/components/auth/AuthFlow.tsx +77 -0
- package/template/src/components/auth/ConnectScreen.tsx +214 -0
- package/template/src/components/auth/LoadingScreen.tsx +8 -0
- package/template/src/components/auth/RecoveryScreen.tsx +182 -0
- package/template/src/components/upload/UploadZone.tsx +314 -0
- package/template/src/index.css +9 -0
- package/template/src/lib/constants.ts +8 -0
- package/template/src/lib/format.ts +35 -0
- package/template/src/lib/hex.ts +13 -0
- package/template/src/lib/sdk.ts +25 -0
- package/template/src/lib/wasm-env.ts +5 -0
- package/template/src/main.tsx +12 -0
- package/template/src/stores/auth.ts +86 -0
- package/template/tsconfig.app.json +31 -0
- package/template/tsconfig.json +7 -0
- package/template/tsconfig.node.json +26 -0
- package/template/vite.config.ts +18 -0
- package/template/wasm/indexd_wasm/indexd_wasm.d.ts +309 -0
- package/template/wasm/indexd_wasm/indexd_wasm.js +1507 -0
- package/template/wasm/indexd_wasm/indexd_wasm_bg.wasm +0 -0
- package/template/wasm/indexd_wasm/package.json +31 -0
package/dist/index.d.ts
ADDED
|
@@ -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,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,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,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.
|