@arcadiasystems/morse-uploader 0.1.0
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/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/README.md +167 -0
- package/dist/chunk-2FYBTRTH.js +562 -0
- package/dist/chunk-2FYBTRTH.js.map +1 -0
- package/dist/chunk-A2AMSP5S.js +136 -0
- package/dist/chunk-A2AMSP5S.js.map +1 -0
- package/dist/index.d.ts +241 -0
- package/dist/index.js +138 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +472 -0
- package/dist/types-COaFRpWK.d.ts +251 -0
- package/dist/ui.d.ts +27 -0
- package/dist/ui.js +225 -0
- package/dist/ui.js.map +1 -0
- package/dist/wallet-standard.d.ts +29 -0
- package/dist/wallet-standard.js +3 -0
- package/dist/wallet-standard.js.map +1 -0
- package/package.json +116 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@arcadiasystems/morse-uploader` will be documented in this file. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
4
|
+
|
|
5
|
+
## [0.1.0] - 2026-06-05
|
|
6
|
+
|
|
7
|
+
Built on `@arcadiasystems/morse-sdk` `>=0.4.2 <0.5.0` (0.4.0 failed every upload via a type-origin lookup bug; 0.4.1 broke `getRecipientFile` over the gRPC client via an `Option<ID>` parse bug; 0.4.2 is the floor): encrypted file sharing where each file (a `RecipientFile`) carries its own recipient set. `useMorseFiles` passes the full `morseConfig` output (including `recipientFileEventOriginPackageId`) to the upload helpers, which they need to match created objects by their type-origin package after a contract upgrade. An encrypted upload to N recipients is 2 wallet popups and the connected wallet is always a recipient (owner can always decrypt). The 0.4.0 SDK also ships pure event-reconciliation helpers for file listing; a listing UI is intentionally deferred here pending a production indexer (see README "Limitations and roadmap").
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Project scaffold: ESM build (tsup), strict TypeScript, Biome, Vitest with a coverage gate, and CI.
|
|
12
|
+
- `useMorseFiles`: setup hook that builds the SDK handles (Sui client, `WalletStandardAdapter`, `WalletStandardSigner`, Walrus read/write adapters, Seal adapter, `RpcRecipientFilesReader`) from a connected wallet, plus `MorseFilesProvider` / `useMorseFilesContext` to inject them. Accepts an optional `suiClient` (a `SuiGrpcClient`, e.g. on a dedicated RPC) for reads/builds; the `WalletStandardAdapter` also backfills created-object types via `getObject` when the receipt omits them.
|
|
13
|
+
- `useFileUpload`: upload one file, public or encrypted for a recipient set. Reports the upload phase stepper and returns `{ fileId, blobId, encrypted, sealIdPrefix, sealNonce, shareLink }`.
|
|
14
|
+
- `useFileDownload` + `useFileDecrypt`: load a file's metadata, then read (public) or decrypt (recipient, one reusable Seal SessionKey) the bytes with the correct filename and MIME type.
|
|
15
|
+
- `useRecipientFile`: load a `RecipientFile`'s on-chain metadata (name, contentType, size, members, blobId).
|
|
16
|
+
- `useStorageCostEstimate` + `estimateStorageUsd`: USD storage-cost estimate using Walrus predictable pricing ($0.023/GB/month) and the network's nominal epoch length.
|
|
17
|
+
- Share links: `buildShareLink` / `parseShareLink` carry the file id and Seal material (prefix + nonce) in the URL fragment (not a secret; decryption is gated by the file's recipient set).
|
|
18
|
+
- Optional styled components at `@arcadiasystems/morse-uploader/ui`: `MorseFileUploader` (driven by `encrypt` + `recipients` props), `MorseFileDownloader`, plus a themeable stylesheet at `@arcadiasystems/morse-uploader/styles.css` (`--mu-*` variables, `mu-*` classes). Components are presentational; all logic lives in the hooks.
|
|
19
|
+
- Headless `useDropzone` hook: drag-and-drop file selection via prop-getters (keyboard-operable, ARIA-labelled, drag state), no inline styles.
|
|
20
|
+
- Headless `useClipboard` hook: copy text and expose a `copied` flag that resets after a delay, powering the uploader's "Copy link" button.
|
|
21
|
+
- Optional `WalletStandardAdapter` helper (separate entry `@arcadiasystems/morse-uploader/wallet-standard`): a browser `WalletAdapter` backed by a wallet sign-and-execute callback. Depends only on morse-sdk and @mysten/sui.
|
|
22
|
+
- Utilities: `bytesToHex`, `hexToBytes`, `formatBytes`, `formatUsd`, `isPreviewableImage`, `triggerDownload`, `resolveOrigin`, `epochDurationMs`, `uploadPhases`, `uploadPhaseLabel`, and the `DEFAULT_ENCRYPTION_OVERHEAD_BYTES` constant.
|
|
23
|
+
- Contribution, security, and code-of-conduct docs for public collaboration.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arcadia Systems
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# @arcadiasystems/morse-uploader
|
|
2
|
+
|
|
3
|
+
Headless-first React components and hooks for encrypted file sharing on Sui and Walrus, built on [`@arcadiasystems/morse-sdk`](https://github.com/arcadiasystems/morse-dcms) 0.4.0. Upload a file to Walrus, optionally encrypt it for a set of recipient wallets so only they can decrypt, and share a link. The connected wallet pays for and signs everything.
|
|
4
|
+
|
|
5
|
+
- **Headless-first**: composable hooks for every step (`useMorseFiles`, `useFileUpload`, `useFileDownload`, `useFileDecrypt`, `useRecipientFile`), plus an optional styled component set.
|
|
6
|
+
- **Recipient-scoped encryption**: each file carries its own recipient set; an encrypted upload to N recipients is **2 wallet popups**, regardless of N. The connected wallet is always a recipient, so the owner can always decrypt. Public (unencrypted) uploads need no recipients.
|
|
7
|
+
- **Dependency-injected**: you supply the network, wallet callbacks, and (optionally) custom Walrus adapters. Wallet connection stays in your app.
|
|
8
|
+
- **Share links** carry the file id and Seal material in the URL fragment; decryption is still gated by the file's recipient set, so the link is not a secret.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install @arcadiasystems/morse-uploader
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Peer dependencies (install in your app, pinned to the same ranges as `@arcadiasystems/morse-sdk` so you do not end up with duplicate Sui/Walrus/Seal instances):
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npm install react react-dom @arcadiasystems/morse-sdk \
|
|
20
|
+
@mysten/sui @mysten/walrus @mysten/seal
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
### 1. Build the handles from your wallet
|
|
26
|
+
|
|
27
|
+
`useMorseFiles` turns a connected wallet into the SDK handles every other hook and component consumes. Wallet connection is your app's concern (this example uses [`@mysten/dapp-kit`](https://sdk.mystenlabs.com/dapp-kit)). Call it only when a wallet is connected, then wrap your tree in `MorseFilesProvider`.
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { useMorseFiles, MorseFilesProvider } from "@arcadiasystems/morse-uploader";
|
|
31
|
+
import {
|
|
32
|
+
useCurrentAccount,
|
|
33
|
+
useSignTransaction,
|
|
34
|
+
useSignPersonalMessage,
|
|
35
|
+
useSignAndExecuteTransaction,
|
|
36
|
+
} from "@mysten/dapp-kit";
|
|
37
|
+
import walrusWasmUrl from "@mysten/walrus-wasm/web/walrus_wasm_bg.wasm?url";
|
|
38
|
+
|
|
39
|
+
function FilesApp({ account }) {
|
|
40
|
+
const { mutateAsync: signTransaction } = useSignTransaction();
|
|
41
|
+
const { mutateAsync: signPersonalMessage } = useSignPersonalMessage();
|
|
42
|
+
const { mutateAsync: signAndExecuteTransaction } = useSignAndExecuteTransaction();
|
|
43
|
+
|
|
44
|
+
const setup = useMorseFiles({
|
|
45
|
+
network: "testnet",
|
|
46
|
+
account,
|
|
47
|
+
callbacks: {
|
|
48
|
+
signTransaction: ({ transaction }) => signTransaction({ transaction }),
|
|
49
|
+
signPersonalMessage: ({ message }) => signPersonalMessage({ message }),
|
|
50
|
+
signAndExecuteTransaction: ({ transaction }) =>
|
|
51
|
+
signAndExecuteTransaction({ transaction }),
|
|
52
|
+
},
|
|
53
|
+
// Optional: pass a SuiGrpcClient on a dedicated RPC so reads/builds resolve
|
|
54
|
+
// the same chain state your wallet executes against (see "Limitations").
|
|
55
|
+
// Defaults to a gRPC client on the network's public RPC.
|
|
56
|
+
// suiClient: new SuiGrpcClient({ network: "testnet", baseUrl: DEDICATED_RPC_URL }),
|
|
57
|
+
// Browser direct writes go through the Walrus upload relay and need the wasm URL.
|
|
58
|
+
walrusWriteConfig: {
|
|
59
|
+
wasmUrl: walrusWasmUrl,
|
|
60
|
+
uploadRelay: { host: "https://upload-relay.testnet.walrus.space" },
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (setup.status !== "ready" || setup.handles === null) {
|
|
65
|
+
return <p>Preparing your wallet...</p>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<MorseFilesProvider value={setup.handles}>
|
|
70
|
+
{/* hooks and components below */}
|
|
71
|
+
</MorseFilesProvider>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 2. Drop in the components
|
|
77
|
+
|
|
78
|
+
Import the styled components from the `/ui` entry and the stylesheet once:
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import {
|
|
82
|
+
MorseFileUploader,
|
|
83
|
+
MorseFileDownloader,
|
|
84
|
+
} from "@arcadiasystems/morse-uploader/ui";
|
|
85
|
+
import "@arcadiasystems/morse-uploader/styles.css";
|
|
86
|
+
|
|
87
|
+
// Public upload:
|
|
88
|
+
<MorseFileUploader onUploaded={(r) => console.log(r.shareLink)} />
|
|
89
|
+
|
|
90
|
+
// Encrypted upload to specific recipients (the connected wallet is always added):
|
|
91
|
+
<MorseFileUploader
|
|
92
|
+
encrypt
|
|
93
|
+
recipients={["0xbob...", "0xcarol..."]}
|
|
94
|
+
onUploaded={(r) => console.log(r.shareLink, r.sealIdPrefix, r.sealNonce)}
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
// Single-file viewer/downloader for a share link:
|
|
98
|
+
<MorseFileDownloader fileId={fileId} sealIdPrefix={prefix} sealNonce={nonce} />
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Theme by overriding the `--mu-*` CSS variables (e.g. `--mu-accent-color`, `--mu-surface-color`, `--mu-fg-color`) on any ancestor, or restyle the `mu-*` classes directly.
|
|
102
|
+
|
|
103
|
+
## Headless hooks
|
|
104
|
+
|
|
105
|
+
All hooks must be used inside `MorseFilesProvider`.
|
|
106
|
+
|
|
107
|
+
| Hook | Purpose |
|
|
108
|
+
|---|---|
|
|
109
|
+
| `useMorseFiles(options)` | Build the handles from a connected wallet. Used once, above the provider. |
|
|
110
|
+
| `useFileUpload()` | Upload one file: `upload({ file, epochs, recipients?, encrypt? })`. Encrypted when `encrypt` is set. Returns `{ fileId, blobId, encrypted, sealIdPrefix, sealNonce, shareLink }` and exposes a phase stepper. |
|
|
111
|
+
| `useFileDownload(fileId, seal)` | Drive a download page: loads metadata, then `load()` / `download()` reads (public, `seal` null) or decrypts (encrypted, `seal` = `{ sealIdPrefix, sealNonce }`). |
|
|
112
|
+
| `useFileDecrypt()` | Lower-level decrypt: `decrypt({ file, sealIdPrefix, sealNonce })` returns plaintext bytes (one SessionKey signature, reused across files). |
|
|
113
|
+
| `useRecipientFile(fileId)` | Load a `RecipientFile`'s on-chain metadata (name, contentType, size, members, blobId). |
|
|
114
|
+
| `useStorageCostEstimate({ sizeBytes, epochs, encrypt })` | USD storage-cost estimate using Walrus [predictable pricing](https://blog.walrus.xyz/announcing-predictable-pricing-in-usd-on-walrus/) ($0.023/GB/month). |
|
|
115
|
+
| `useDropzone(options)` | Generic headless drag-and-drop file selection (prop-getters). |
|
|
116
|
+
| `useClipboard(resetMs?)` | Copy text and flag `copied` for a short window (for "Copy link" buttons). |
|
|
117
|
+
|
|
118
|
+
## How encryption works
|
|
119
|
+
|
|
120
|
+
Each file is a **RecipientFile** that carries its own recipient set on chain (there is no separate allowlist object). You pass the recipients at upload time; the connected wallet is **auto-added**, so the owner can always decrypt. The upload helper encrypts under a random Seal identity and binds it to the file in a single transaction, so an encrypted upload to any number of recipients is just **2 wallet popups**. At decrypt time, Seal's key servers dry-run the file's recipient check on-chain; non-recipients cannot get the key.
|
|
121
|
+
|
|
122
|
+
Building the recipient list is entirely client-side (no signature per recipient). To change recipients after upload, the SDK exposes owner-only `addRecipient` / `removeRecipient` (not wired into this package's UI).
|
|
123
|
+
|
|
124
|
+
## Share links
|
|
125
|
+
|
|
126
|
+
`buildShareLink({ fileId, sealIdPrefix, sealNonce })` produces a `#/f/<fileId>?p=<hex>&n=<hex>` link. The fileId and Seal material live in the URL **fragment**, so they never reach a server log. The Seal material is not a secret: decryption is still gated by the file's recipient set. Parse it back on your download route:
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
import { parseShareLink } from "@arcadiasystems/morse-uploader";
|
|
130
|
+
const parsed = parseShareLink(window.location.href);
|
|
131
|
+
// { fileId, sealIdPrefix, sealNonce } | null (prefix/nonce null for public files)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Wallet popups
|
|
135
|
+
|
|
136
|
+
- **Public or encrypted upload**: two popups (Walrus `register_blob`, then a combined certify + create-RecipientFile PTB). Adding recipients is free (client-side).
|
|
137
|
+
- **Decrypt**: one popup to create a reusable Seal `SessionKey` (good for `ttlMin` minutes across any file you can access), then no popup per decrypt.
|
|
138
|
+
|
|
139
|
+
## Notes on Walrus
|
|
140
|
+
|
|
141
|
+
- Storage is a **lease**, capped at 53 epochs ahead on both networks. An epoch is ~1 day on testnet and ~2 weeks on mainnet.
|
|
142
|
+
- Blobs are raw bytes with no filename or MIME type; this package stores the name and content type in the on-chain file record so downloads come back correctly named and typed.
|
|
143
|
+
- Mainnet is not yet supported by the SDK; pass `network: "testnet"`.
|
|
144
|
+
|
|
145
|
+
## Limitations and roadmap
|
|
146
|
+
|
|
147
|
+
- **Listing a wallet's files needs an indexer (deferred).** A `RecipientFile` is a shared Sui object with no owner-indexable field, so there is no direct "list my files" query. morse-sdk 0.4.0 ships pure reconciliation helpers (`reconcileRecipientFilesOwnedBy`, `reconcileRecipientFilesAccessibleBy`) that turn a raw event stream into the current file set, but it deliberately does not fetch events. A production listing requires a Sui event source (a dedicated indexer; the legacy `suix_queryEvents` path works today but is on Mysten's deprecation track). This package does not ship a listing UI yet for that reason; it is planned once an indexer is in place.
|
|
148
|
+
- **Seal material is not on-chain.** The Seal identity needed to decrypt (prefix + nonce) is persisted out-of-band (in the share link, here). It is not recoverable from the chain or from an event listing, so a future indexer-backed list can surface encrypted files' metadata but cannot, by itself, decrypt them; decryption still requires the share-link material. Persist `sealIdPrefix`/`sealNonce` in your own store if you need list-and-open UX.
|
|
149
|
+
- **RPC consistency (use a dedicated RPC).** The SDK builds/reads over a gRPC client while the wallet (dapp-kit) executes over its own JSON-RPC client. On the public, load-balanced `fullnode.testnet.sui.io`, those two requests can hit different backend nodes at different checkpoints, so the certify step occasionally fails with an object-version conflict ("needs to be rebuilt"). For reliable uploads, point both at a **dedicated RPC**: set dapp-kit's `SuiClientProvider` URL and pass a `SuiGrpcClient` on the matching endpoint via `useMorseFiles({ suiClient })`. This is an RPC-infrastructure property, not a bug in the package logic.
|
|
150
|
+
- **Testnet only.** `network: "mainnet"` will work the moment the SDK ships mainnet addresses; until then pass `"testnet"`.
|
|
151
|
+
- **Storage is a lease.** Walrus storage is capped at 53 epochs ahead and expires; this is not permanent storage.
|
|
152
|
+
|
|
153
|
+
## Example
|
|
154
|
+
|
|
155
|
+
A deployable Vite + React example lives in [`examples/web`](./examples/web). Build the library first (`bun run build` at the repo root), then `bun run dev` in the example.
|
|
156
|
+
|
|
157
|
+
## Contributing
|
|
158
|
+
|
|
159
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md). In short: one function/component per file, logic in hooks (components stay presentational), no inline styles, and tests for everything. Run the full gate before opening a PR:
|
|
160
|
+
|
|
161
|
+
```sh
|
|
162
|
+
bun run lint && bun run typecheck && bun run test:coverage && bun run build && bun run check:exports
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
[MIT](./LICENSE)
|