create-sia-app 0.1.16 → 0.1.18
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/package.json +11 -1
- package/template/CLAUDE.md +183 -91
- package/template/package.json +1 -1
- package/template/src/components/auth/ApproveScreen.tsx +1 -1
- package/template/src/components/auth/AuthFlow.tsx +1 -1
- package/template/src/components/auth/ConnectScreen.tsx +1 -1
- package/template/src/components/auth/RecoveryScreen.tsx +1 -1
- package/template/src/components/upload/UploadZone.tsx +5 -1
package/package.json
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-sia-app",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
|
+
"description": "Scaffold a private, encrypted storage app on the Sia network.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/SiaFoundation/create-sia-app.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/SiaFoundation/create-sia-app/issues"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/SiaFoundation/create-sia-app#readme",
|
|
4
14
|
"type": "module",
|
|
5
15
|
"bin": {
|
|
6
16
|
"create-sia-app": "./dist/index.js"
|
package/template/CLAUDE.md
CHANGED
|
@@ -1,157 +1,249 @@
|
|
|
1
1
|
# Sia Starter — AI Assistant Guide
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This is a starter for apps backed by the [Sia](https://sia.tech) storage network. Read this before writing code: it contains the mental model, the canonical patterns, and the footguns. If you're about to invent a pattern that isn't here, the odds are you shouldn't.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Stack
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
React 19, TypeScript, Vite, Tailwind CSS 4, Zustand, [`@siafoundation/sia-storage`](https://www.npmjs.com/package/@siafoundation/sia-storage).
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Types are the source of truth
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
The SDK's shape lives in its `.d.ts` files — more current and precise than any prose. Before calling an unfamiliar method, read:
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
- `node_modules/@siafoundation/sia-storage/dist/index.d.ts` — top-level exports.
|
|
14
|
+
- `node_modules/@siafoundation/sia-storage/wasm/sia_storage_wasm.d.ts` — WASM-bound classes with full method signatures.
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
Don't hallucinate methods. If a method isn't in those files, it doesn't exist.
|
|
17
|
+
|
|
18
|
+
## Core concepts
|
|
19
|
+
|
|
20
|
+
**Indexer** — A service that coordinates storage: it tracks which hosts hold which encrypted shards, handles payments, and repairs slabs when hosts disappear. **It sees only ciphertext.** Trusted for availability and correctness of the repair/payment flow, *not* for data privacy. The indexer URL lives in `src/lib/constants.ts`.
|
|
21
|
+
|
|
22
|
+
**Hosts** — The actual storage providers. The browser talks to them directly over WebTransport for uploads and downloads. Erasure coding means any sufficient subset of hosts is enough to reconstruct a file.
|
|
23
|
+
|
|
24
|
+
**App** — Identified to the indexer by `APP_KEY` (32-byte hex) + `APP_META` in `src/lib/constants.ts`. Apps are namespaces: objects stored under one `APP_KEY` aren't visible to another.
|
|
25
|
+
|
|
26
|
+
**User key** — An `AppKey` instance derived from the user's 12-word BIP-39 recovery phrase. It's the encryption key and the indexer-auth identity for that user *within* the app. Persisted as hex in `localStorage` so users don't re-enter the phrase every session.
|
|
27
|
+
|
|
28
|
+
> **`APP_KEY` vs `AppKey`** — `APP_KEY` (constant, screaming snake) is the *app's* identity in `APP_META.appId`. `AppKey` (class, PascalCase) is the *user's* ed25519 key. They are not the same thing.
|
|
29
|
+
|
|
30
|
+
**Object** — A file or blob you upload. Represented at rest by a `PinnedObject` handle. Has an ID, a size, one or more slabs, and encrypted metadata.
|
|
31
|
+
|
|
32
|
+
**Slab / shard** — A file is split into slabs (default ~40 MB each), and each slab is erasure-coded into shards (10 data + 20 parity by default — see `DATA_SHARDS` / `PARITY_SHARDS` in `src/lib/constants.ts`). Shards are what actually ship to hosts.
|
|
33
|
+
|
|
34
|
+
**Pin** — "This object should persist." An unpinned object is transient. Always `await sdk.pinObject(obj)` after a successful upload, or the indexer will garbage-collect it.
|
|
18
35
|
|
|
19
|
-
-
|
|
20
|
-
- **connect**: User enters indexer URL, app constructs a `Builder` and calls `requestConnection()`
|
|
21
|
-
- **approve**: User visits approval URL in another tab; app polls `builder.waitForApproval()`
|
|
22
|
-
- **recovery**: User generates or enters a 12-word BIP-39 recovery phrase; `builder.register(phrase)` returns the `Sdk`
|
|
23
|
-
- **connected**: `Sdk` is ready, main app UI renders
|
|
36
|
+
**Metadata** — Encrypted app-defined bytes attached to an object (filename, MIME type, tags, etc.). Call `event.object.metadata()` to read, `pinnedObject.updateMetadata(bytes)` + `sdk.updateObjectMetadata(pinnedObject)` to write. Keep it under a few KB — it's a descriptor, not a payload.
|
|
24
37
|
|
|
25
|
-
|
|
38
|
+
## Auth flow
|
|
26
39
|
|
|
27
|
-
|
|
40
|
+
Step-based, managed by Zustand (`src/stores/auth.ts`):
|
|
28
41
|
|
|
29
|
-
|
|
42
|
+
```
|
|
43
|
+
loading → connect → approve → recovery → connected
|
|
44
|
+
```
|
|
30
45
|
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
- Direct streaming to/from Sia hosts (no worker pool needed)
|
|
46
|
+
- **loading** — `initSia()` loads WASM; `AuthFlow` checks for a stored user key.
|
|
47
|
+
- **connect** — User enters indexer URL. App constructs `new Builder(url, APP_META)` and calls `requestConnection()`.
|
|
48
|
+
- **approve** — User visits `builder.responseUrl()` in another tab; the app polls `builder.waitForApproval()`.
|
|
49
|
+
- **recovery** — User generates or enters a BIP-39 phrase; `builder.register(phrase)` returns the `Sdk`.
|
|
50
|
+
- **connected** — `Sdk` is ready; main UI renders.
|
|
37
51
|
|
|
38
|
-
|
|
52
|
+
**Returning users** skip connect/approve/recovery entirely: `AuthFlow` constructs a `Builder` and calls `builder.connected(appKey)` with the persisted key. Returns an `Sdk` if valid, `undefined` to fall back to `connect`.
|
|
39
53
|
|
|
40
|
-
|
|
54
|
+
**Persistence**: Zustand `persist` middleware writes to `localStorage` under `sia-auth-<first-16-of-APP_KEY>` (keyed by app so two scaffolds on `localhost:5173` don't collide). Persisted: `storedKeyHex`, `indexerUrl`. The live `Sdk` is **not** persisted — it's rehydrated by calling `builder.connected(appKey)` on mount.
|
|
41
55
|
|
|
42
|
-
## Key
|
|
56
|
+
## Key files
|
|
43
57
|
|
|
44
|
-
| File |
|
|
45
|
-
|
|
46
|
-
| `src/lib/constants.ts` |
|
|
47
|
-
| `src/stores/auth.ts` |
|
|
48
|
-
| `src/stores/toast.ts` | Toast
|
|
49
|
-
| `src/components/
|
|
50
|
-
| `src/components/
|
|
51
|
-
| `src/components/
|
|
52
|
-
| `src/components/auth/
|
|
53
|
-
| `src/components/
|
|
54
|
-
| `src/components/
|
|
55
|
-
| `src/components/
|
|
56
|
-
| `src/
|
|
57
|
-
| `src/components/DevNote.tsx` | Developer callout component |
|
|
58
|
-
| `src/types/uint8array-hex.d.ts` | Ambient types for TC39 `Uint8Array.toHex`/`fromHex` (drop once TS lib ships them) |
|
|
58
|
+
| File | Role |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `src/lib/constants.ts` | `APP_KEY`, `APP_NAME`, `APP_META` (`AppMetadata`), indexer default, erasure-coding constants |
|
|
61
|
+
| `src/stores/auth.ts` | Zustand store: holds the `Sdk`, persists `storedKeyHex` + `indexerUrl` |
|
|
62
|
+
| `src/stores/toast.ts` | Toast notifications (auto-dismiss) |
|
|
63
|
+
| `src/components/auth/AuthFlow.tsx` | Orchestrator: `initSia()`, returning-user reconnect |
|
|
64
|
+
| `src/components/auth/ConnectScreen.tsx` | `new Builder(url, APP_META).requestConnection()` |
|
|
65
|
+
| `src/components/auth/ApproveScreen.tsx` | Polls `builder.waitForApproval()` |
|
|
66
|
+
| `src/components/auth/RecoveryScreen.tsx` | Generate / validate phrase → `builder.register()` → `Sdk` |
|
|
67
|
+
| `src/components/upload/UploadZone.tsx` | **Reference implementation.** Full cycle: dropzone → upload → pin → metadata → list → download. Read this first when building new features. |
|
|
68
|
+
| `src/components/Navbar.tsx` | Public key + sign out |
|
|
69
|
+
| `src/components/DevNote.tsx` | Amber callout — remove or replace for production |
|
|
70
|
+
| `src/types/uint8array-hex.d.ts` | Ambient types for TC39 `Uint8Array.{toHex,fromHex}` (drop once TS lib ships them) |
|
|
59
71
|
|
|
60
|
-
## SDK
|
|
72
|
+
## SDK usage patterns
|
|
61
73
|
|
|
62
|
-
### Upload
|
|
74
|
+
### Upload → pin → metadata
|
|
63
75
|
|
|
64
76
|
```ts
|
|
65
77
|
import { PinnedObject } from '@siafoundation/sia-storage'
|
|
78
|
+
import { DATA_SHARDS, PARITY_SHARDS } from '../../lib/constants'
|
|
66
79
|
|
|
67
80
|
const object = new PinnedObject()
|
|
68
|
-
const
|
|
81
|
+
const pinned = await sdk.upload(object, file.stream(), {
|
|
69
82
|
maxInflight: 10,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
83
|
+
dataShards: DATA_SHARDS,
|
|
84
|
+
parityShards: PARITY_SHARDS,
|
|
85
|
+
onShardUploaded: (p) => {
|
|
86
|
+
// p: { hostKey, shardSize, shardIndex, slabIndex, elapsedMs }
|
|
87
|
+
// shardSize is post-erasure-coding bytes, not source bytes.
|
|
73
88
|
},
|
|
74
89
|
})
|
|
75
90
|
|
|
76
|
-
|
|
77
|
-
name:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
})))
|
|
82
|
-
await sdk.pinObject(pinnedObject)
|
|
83
|
-
await sdk.updateObjectMetadata(pinnedObject)
|
|
91
|
+
pinned.updateMetadata(
|
|
92
|
+
new TextEncoder().encode(JSON.stringify({ name: file.name, type: file.type, size: file.size })),
|
|
93
|
+
)
|
|
94
|
+
await sdk.pinObject(pinned)
|
|
95
|
+
await sdk.updateObjectMetadata(pinned)
|
|
84
96
|
```
|
|
85
97
|
|
|
86
|
-
|
|
98
|
+
All three calls matter:
|
|
99
|
+
1. `upload` writes the encrypted shards to hosts.
|
|
100
|
+
2. `pinObject` tells the indexer to keep it (without this, it's eventually GC'd).
|
|
101
|
+
3. `updateObjectMetadata` persists the descriptor so other sessions can find it.
|
|
87
102
|
|
|
88
|
-
|
|
103
|
+
### Byte progress (source units, not on-wire units)
|
|
104
|
+
|
|
105
|
+
`onShardUploaded.shardSize` is on-wire bytes (encoded). To show a progress bar in source-file units, use `encodedSize` as the denominator:
|
|
89
106
|
|
|
90
107
|
```ts
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
console.log(`shard ${progress.shardIndex} downloaded`)
|
|
95
|
-
},
|
|
96
|
-
})
|
|
97
|
-
const blob = await new Response(stream).blob()
|
|
108
|
+
import { encodedSize } from '@siafoundation/sia-storage'
|
|
109
|
+
const encodedTotal = encodedSize(file.size, DATA_SHARDS, PARITY_SHARDS)
|
|
110
|
+
const sourceProgress = (bytesUploaded / encodedTotal) * file.size
|
|
98
111
|
```
|
|
99
112
|
|
|
100
|
-
###
|
|
113
|
+
### Download
|
|
114
|
+
|
|
115
|
+
Returns a `ReadableStream<Uint8Array>`. Buffer or pipe:
|
|
101
116
|
|
|
102
117
|
```ts
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
if (!event.deleted && event.object) {
|
|
106
|
-
const meta = JSON.parse(new TextDecoder().decode(event.object.metadata()))
|
|
107
|
-
console.log(meta.name, event.object.size())
|
|
108
|
-
}
|
|
109
|
-
}
|
|
118
|
+
const stream = sdk.download(pinnedObject, { maxInflight: 10 })
|
|
119
|
+
const blob = await new Response(stream).blob()
|
|
110
120
|
```
|
|
111
121
|
|
|
112
|
-
### Delete
|
|
122
|
+
### Delete
|
|
113
123
|
|
|
114
124
|
```ts
|
|
115
125
|
await sdk.deleteObject(objectId)
|
|
116
126
|
```
|
|
117
127
|
|
|
118
|
-
### Share a
|
|
128
|
+
### Share / consume a share URL
|
|
119
129
|
|
|
120
130
|
```ts
|
|
121
|
-
const validUntil = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
|
|
122
|
-
const
|
|
131
|
+
const validUntil = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
|
|
132
|
+
const url = sdk.shareObject(pinnedObject, validUntil)
|
|
133
|
+
// On the recipient side (can be a different app / no auth needed):
|
|
134
|
+
const obj = await sdk.sharedObject(url)
|
|
135
|
+
const stream = sdk.download(obj)
|
|
123
136
|
```
|
|
124
137
|
|
|
125
|
-
|
|
138
|
+
Share URLs embed the decryption key in the fragment (`#...`) — never sent to the indexer.
|
|
139
|
+
|
|
140
|
+
### Pack many small files
|
|
141
|
+
|
|
142
|
+
`sdk.uploadPacked()` batches small files into shared slabs to avoid wasting storage:
|
|
126
143
|
|
|
127
144
|
```ts
|
|
128
|
-
const
|
|
129
|
-
|
|
145
|
+
const packed = sdk.uploadPacked({ maxInflight: 10 })
|
|
146
|
+
await packed.add(fileA.stream())
|
|
147
|
+
await packed.add(fileB.stream())
|
|
148
|
+
for (const obj of await packed.finalize()) await sdk.pinObject(obj)
|
|
130
149
|
```
|
|
131
150
|
|
|
132
|
-
##
|
|
151
|
+
## Syncing with the indexer
|
|
133
152
|
|
|
134
|
-
|
|
153
|
+
`sdk.objectEvents(cursor, limit)` is the one sync primitive. Each `ObjectEvent` has `id`, `updatedAt: Date`, `deleted: boolean`, `object: PinnedObject | null`.
|
|
135
154
|
|
|
136
|
-
|
|
155
|
+
Cursor is `{ id: string, after: Date }`. Passing it returns events strictly after that point.
|
|
156
|
+
|
|
157
|
+
### `updatedAt` bumps
|
|
158
|
+
|
|
159
|
+
- **User actions from any device using this app key**: `updateObjectMetadata`, `deleteObject`, re-pin.
|
|
160
|
+
- **Indexer repairs**: when a host goes offline the indexer migrates shards to healthy hosts and bumps `updatedAt`. Your next poll picks up the repaired state automatically.
|
|
161
|
+
|
|
162
|
+
Both surface through the same stream, which is why polling `objectEvents` is the pattern every Sia app should implement.
|
|
163
|
+
|
|
164
|
+
### Cross-device sync
|
|
137
165
|
|
|
138
166
|
```ts
|
|
139
|
-
|
|
167
|
+
// Fresh session: no cursor, pull latest N.
|
|
168
|
+
const events = await sdk.objectEvents(undefined, 500)
|
|
169
|
+
events.forEach(apply)
|
|
170
|
+
persistCursor(latest(events))
|
|
171
|
+
|
|
172
|
+
// Periodic tick: only what changed.
|
|
173
|
+
const cursor = loadCursor() // { id, after: Date } or undefined
|
|
174
|
+
const events = await sdk.objectEvents(cursor, 200)
|
|
175
|
+
events.forEach(apply)
|
|
176
|
+
if (events.length) persistCursor(latest(events))
|
|
140
177
|
```
|
|
141
178
|
|
|
142
|
-
|
|
179
|
+
Operational tips:
|
|
180
|
+
- Persist the cursor in `localStorage` keyed by app key.
|
|
181
|
+
- Poll only while the tab is visible (`document.visibilityState === 'visible'`).
|
|
182
|
+
- Advance the cursor only after local merge succeeds.
|
|
183
|
+
- `event.deleted === true` → evict from local store.
|
|
184
|
+
|
|
185
|
+
If a specific SDK build rejects the cursor shape (edge cases with `Date` serialization happen), fall back to `undefined` + client-side filter on `event.updatedAt.getTime() > watermarkMs`.
|
|
186
|
+
|
|
187
|
+
## Gotchas
|
|
188
|
+
|
|
189
|
+
Things that look right but aren't:
|
|
190
|
+
|
|
191
|
+
- **Don't persist `Sdk` to storage.** It's a live WASM handle; rehydrate it via `Builder.connected(appKey)` on mount.
|
|
192
|
+
- **Don't forget `pinObject`.** A successful `upload` that isn't pinned is a transient object — the indexer will eventually drop it.
|
|
193
|
+
- **Don't conflate `APP_KEY` and `AppKey`.** `APP_KEY` is the app identity constant; `AppKey` is the user's key class.
|
|
194
|
+
- **Don't stuff large payloads into metadata.** It's a descriptor. Put file bytes in the object, not in metadata.
|
|
195
|
+
- **Don't re-bundle or wrap the WASM.** Vite dev needs `optimizeDeps: { exclude: ['@siafoundation/sia-storage'] }` (already set in `vite.config.ts`) because the SDK's `import.meta.url`-relative WASM path breaks under pre-bundling. If you add another bundler (Webpack, Rollup), check the SDK README for the equivalent.
|
|
196
|
+
- **Don't call `initSia()` more than once per mount.** It's already called in `AuthFlow`; additional call sites create race conditions.
|
|
197
|
+
- **Don't call `builder.waitForApproval()` twice.** React strict mode will remount — `ApproveScreen` guards this with a `pollStarted` ref. Follow that pattern.
|
|
198
|
+
- **`onShardUploaded.shardSize` is encoded bytes, not source bytes.** If you sum it, you're measuring on-wire traffic. Use `encodedSize()` for the matching denominator, or scale to source via `(bytes / encodedTotal) * file.size`.
|
|
199
|
+
- **Numeric types differ on Node vs browser.** Browser uses `number` (~9 PB safe); Node uses `bigint`. Template is browser-only, so `number` is correct here.
|
|
200
|
+
- **Sign-out should clear localStorage.** `useAuthStore.getState().reset()` + `window.location.reload()` is the pattern in `Navbar.tsx`.
|
|
201
|
+
|
|
202
|
+
## Extending the starter
|
|
203
|
+
|
|
204
|
+
### Swap out `UploadZone`
|
|
205
|
+
|
|
206
|
+
`src/App.tsx` renders `<UploadZone />` after auth. Replace it with your own post-auth component. Read `UploadZone.tsx` first — it shows the full upload → pin → metadata → list cycle that most apps will want to reuse in some form.
|
|
207
|
+
|
|
208
|
+
Access the SDK:
|
|
143
209
|
|
|
144
|
-
|
|
210
|
+
```tsx
|
|
211
|
+
const sdk = useAuthStore((s) => s.sdk)
|
|
212
|
+
if (!sdk) return null
|
|
213
|
+
```
|
|
145
214
|
|
|
146
215
|
### Add routes
|
|
147
216
|
|
|
148
|
-
Install `react-router-dom
|
|
217
|
+
Install `react-router-dom`. Gate routes on `step === 'connected'`; render `<AuthFlow />` otherwise.
|
|
218
|
+
|
|
219
|
+
### Add fields to file metadata
|
|
220
|
+
|
|
221
|
+
Extend the `FileMetadata` type in `UploadZone.tsx`, write the extra fields in the upload handler, read them back in `loadFiles`. Schema is app-owned — do whatever makes sense. Just keep it small.
|
|
222
|
+
|
|
223
|
+
### Search / filter
|
|
224
|
+
|
|
225
|
+
Metadata is encrypted at rest but decrypted client-side, so once you've hydrated from `objectEvents` you can filter/sort/search it in memory like any local list. The indexer can't do this for you (it sees ciphertext) — search is always client-side.
|
|
226
|
+
|
|
227
|
+
### Multi-device updates
|
|
149
228
|
|
|
150
|
-
|
|
229
|
+
Implement the polling pattern from **Syncing with the indexer**. That's how uploads on one device appear on another.
|
|
230
|
+
|
|
231
|
+
### Change erasure-coding parameters
|
|
232
|
+
|
|
233
|
+
Edit `DATA_SHARDS` / `PARITY_SHARDS` in `src/lib/constants.ts`. More parity = survives more host failures at the cost of more on-wire bytes. Keep `UploadZone`'s `encodedSize()` call in sync (it already reads from the same constants).
|
|
234
|
+
|
|
235
|
+
### Change the app key
|
|
236
|
+
|
|
237
|
+
`APP_KEY` in `src/lib/constants.ts`. Generate with `crypto.getRandomValues(new Uint8Array(32)).toHex()`. **Changing it makes all previously uploaded data invisible to the app** — app key is the namespace.
|
|
238
|
+
|
|
239
|
+
## Commands
|
|
151
240
|
|
|
152
241
|
```bash
|
|
153
|
-
bun install # Install
|
|
154
|
-
bun dev #
|
|
155
|
-
bun run build #
|
|
156
|
-
bun run check #
|
|
242
|
+
bun install # Install deps
|
|
243
|
+
bun dev # Vite dev server (WASM loads lazily, ~100ms)
|
|
244
|
+
bun run build # tsc + Vite production build
|
|
245
|
+
bun run check # Biome lint + format check (use --write to fix)
|
|
246
|
+
bun x playwright test e2e/smoke.spec.ts # App-loads-without-errors smoke
|
|
157
247
|
```
|
|
248
|
+
|
|
249
|
+
After any substantive change, run `bun run check` (auto-fix with `bun x biome check . --write`) and `bun run build` before committing.
|
package/template/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from 'react'
|
|
2
1
|
import type { Builder } from '@siafoundation/sia-storage'
|
|
2
|
+
import { useEffect, useRef, useState } from 'react'
|
|
3
3
|
import { useAuthStore } from '../../stores/auth'
|
|
4
4
|
import { CopyButton } from '../CopyButton'
|
|
5
5
|
import { DevNote } from '../DevNote'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react'
|
|
2
1
|
import { AppKey, Builder, initSia } from '@siafoundation/sia-storage'
|
|
2
|
+
import { useEffect, useRef } from 'react'
|
|
3
3
|
import { APP_META } from '../../lib/constants'
|
|
4
4
|
import { useAuthStore } from '../../stores/auth'
|
|
5
5
|
import { ApproveScreen } from './ApproveScreen'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
1
|
import { Builder } from '@siafoundation/sia-storage'
|
|
2
|
+
import { useState } from 'react'
|
|
3
3
|
import { APP_META, DEFAULT_INDEXER_URL } from '../../lib/constants'
|
|
4
4
|
import { useAuthStore } from '../../stores/auth'
|
|
5
5
|
import { DevNote } from '../DevNote'
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
1
|
import {
|
|
3
2
|
type Builder,
|
|
4
3
|
generateRecoveryPhrase,
|
|
5
4
|
validateRecoveryPhrase,
|
|
6
5
|
} from '@siafoundation/sia-storage'
|
|
6
|
+
import { useState } from 'react'
|
|
7
7
|
import { useAuthStore } from '../../stores/auth'
|
|
8
8
|
import { CopyButton } from '../CopyButton'
|
|
9
9
|
import { DevNote } from '../DevNote'
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
encodedSize,
|
|
3
|
+
PinnedObject,
|
|
4
|
+
type ShardProgress,
|
|
5
|
+
} from '@siafoundation/sia-storage'
|
|
1
6
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
2
|
-
import { encodedSize, PinnedObject, type ShardProgress } from '@siafoundation/sia-storage'
|
|
3
7
|
import { APP_KEY, DATA_SHARDS, PARITY_SHARDS } from '../../lib/constants'
|
|
4
8
|
import { useAuthStore } from '../../stores/auth'
|
|
5
9
|
import { DevNote } from '../DevNote'
|