@meddleware/walrus-storage-ui 0.0.0-stage → 0.1.63

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 ADDED
@@ -0,0 +1,81 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@meddleware/walrus-ui` are documented here.
4
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows
5
+ [Semantic Versioning](https://semver.org/). Before 0.2.0, breaking changes ship as patch releases.
6
+
7
+ ## [0.1.63] - 2026-10-24
8
+
9
+ ### Changed
10
+
11
+ - Machine-zone endpoints (the indexer and the Walrus relays) can be overridden at deploy time by a same-origin `/runtime-config.json` (`@meddleware/ui` 0.1.34 `loadRuntimeConfig`), so the image is built once and moved between Cloudflare zones by configuration (workspace ADR-0005). Without the file the build-time values apply, as before.
12
+ - Ships its own `robots.txt` (Content Signals `search=yes, ai-input=yes`) and `llms.txt` instead of static-server's defaults.
13
+
14
+ ## [0.1.62] - 2026-10-24
15
+
16
+ ### Changed
17
+
18
+ - Renamed from `walrus-ui` to `walrus-storage-ui` (workspace naming convention, `docs/architecture/ADR-0003-naming.md`). No code change beyond names, paths, hostnames and dependency names; the history below keeps the old names.
19
+
20
+ ## [0.1.61] - 2026-10-22
21
+
22
+ ### Changed
23
+
24
+ - Dependencies: walrus-client ^0.0.30 (flow passes the real challenge expiry; nft-gate-client 0.0.18 rules), walrus-relay ^0.1.33, access-gate-client ^0.0.10 (stale indexer fallback); image base static-server 0.1.11 (Go 1.26.10 standard library, CVE-2026-111943)
25
+
26
+ ## [0.1.60] - 2026-10-19
27
+
28
+ ### Changed
29
+
30
+ - Image rebuilt without the `VITE_UPLOAD_RELAY_MAX_TIP_MIST` repository variable (deleted; it still held the old 0.05 SUI ceiling and overrode the 0.005 SUI default introduced in the previous release)
31
+
32
+ ## [0.1.59] - 2026-10-19
33
+
34
+ ### Changed
35
+
36
+ - The default relay tip ceiling (`VITE_UPLOAD_RELAY_MAX_TIP_MIST` unset) is now `5_000_000` MIST (0.005 SUI), down
37
+ from 0.05 SUI: about 8× the worst-case tip of the re-priced operator relay (`100_000` MIST + 1 MIST per encoded
38
+ KiB; workspace cost model, decision D57). The repository variable `VITE_UPLOAD_RELAY_MAX_TIP_MIST`, when set,
39
+ still wins.
40
+
41
+ ## [0.1.58] - 2026-10-18
42
+
43
+ ### Changed (breaking, pre-v0.2)
44
+
45
+ - **Adopts `@meddleware/walrus-relay` 0.1.31.** `useAccessGate` is given the network and a client getter
46
+ that serves the active network only; the access gate is still built by `relayGateConfig`, so it names the
47
+ published `access_gate` deployment. The purchase prompt (`AccessGateCta`) now shows the price, payees,
48
+ network and pass kind read from the chain, with a "Read the price again" button when they cannot be read.
49
+ - **`VITE_ACCESS_GATE_PRICE_MIST_TESTNET` is removed** (code, `.env.example`, Dockerfile build argument,
50
+ release workflow and README). The price is read from the gate on the chain; a build that still sets the
51
+ variable ignores it.
52
+ - `WalrusUpload` gets `sessionKey` (`<network>:<address>`), so a change of account or network discards the
53
+ chosen file and any held retry; the upload approvals hint follows the held pass (`singleUse`).
54
+ - Dependencies: `@meddleware/walrus-client` ^0.0.28, `@meddleware/walrus-relay` ^0.1.31,
55
+ `@meddleware/design-tokens` ^0.1.10, `@meddleware/access-gate-client` ^0.0.9. The unused
56
+ `@mysten/wallet-standard` dependency is dropped.
57
+ - Image base: `static-server` 0.1.8 (`sha256:e32cf42b…4000`).
58
+
59
+ ### Fixed
60
+
61
+ - **A slower owned-blob load no longer overwrites a newer one** after a wallet-account or network switch,
62
+ a disconnect, or a switch back to a list that is already held (a stale result, error or `loading` flag is
63
+ dropped); a cleared list no longer reads as loaded for the previous account.
64
+ - **Extend shows its cost before the wallet opens.** The seeded +10 epochs is priced on the first press
65
+ (the button reads "Show cost"), the amount and `≈ WAL` cost are shown, and the wallet opens on the next
66
+ press (`Extend`). An amount that cannot be priced says "price unavailable".
67
+ - **My Blobs text meets AA contrast in both themes.** It read `--mw-color-*` custom properties that
68
+ design-tokens does not define, so literal fallbacks (`#888`, `#c00`, `#ddd`, `#f90`) ignored theme and
69
+ season; it now uses `--muted`, `--danger`, `--border`, `--warning` (tint only) and `--accent`.
70
+ A test fails if a component reads a custom property design-tokens does not define.
71
+
72
+ ### Security
73
+
74
+ - `.env*` is git-ignored (except `.env.example`).
75
+ - `SECURITY.md` invariant 1 names the real wiring of the tip ceiling (`maxTipMist` to `runBlobUpload`).
76
+
77
+ ### Added
78
+
79
+ - `CHANGELOG.md` (it was listed in the package `files` without existing).
80
+ - Tests: stale-load guard (`useOwnedBlobs.stale`), extend quotes, the custom-property check, and the
81
+ gate-config/`useAccessGate` pairing.
package/LICENSE ADDED
@@ -0,0 +1,14 @@
1
+ BSD Zero Clause License
2
+
3
+ Copyright (c) 2026 MeddleWare
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
10
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
12
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
13
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
14
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,73 @@
1
- # Temporary Holding Version
1
+ # @meddleware/walrus-storage-ui
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![License: 0BSD](https://img.shields.io/badge/license-0BSD-blue)](LICENSE)
4
+
5
+ A Vue 3 SPA for uploading and managing blobs on [Walrus](https://walrus.xyz) decentralised
6
+ storage on Sui. Supports NFT-gated operator relays (commission-enforced on-chain via
7
+ `access_gate`) with automatic fallback to the public Mysten relay.
8
+
9
+ ## Features
10
+
11
+ - Connect any Sui wallet (wallet-standard)
12
+ - Select between operator relay (supports the app) and public relay (free)
13
+ - Upload blobs through an NFT-gated or open relay
14
+ - View and extend your owned Walrus blobs
15
+ - Two-approval upload flow: register tx → upload → certify tx
16
+
17
+ ## Local development
18
+
19
+ ```bash
20
+ npm install
21
+ npm run dev
22
+ ```
23
+
24
+ > `@meddleware/walrus-storage-kit` is resolved from the npm registry. For local development against an
25
+ > unpublished library, use `npm link @meddleware/walrus-storage-kit` or an `overrides` entry pointing at
26
+ > a local checkout.
27
+
28
+ ## Environment variables
29
+
30
+ All `VITE_*` vars are baked into the static bundle at build time.
31
+
32
+ | Variable | Default | Description |
33
+ | --- | --- | --- |
34
+ | `VITE_NETWORK` | `testnet` | Network the standalone build selects (`testnet` or `mainnet`); embedded, the host's selector rules |
35
+ | `VITE_WALRUS_RELAY_TESTNET` | Public Mysten relay | Operator relay URL for testnet |
36
+ | `VITE_WALRUS_RELAY_MAINNET` | Public Mysten relay | Operator relay URL for mainnet |
37
+ | `VITE_ACCESS_GATE_ID_TESTNET` | — | Gate object ID (testnet; unset = no gate) |
38
+ | `VITE_ACCESS_GATE_SOULBOUND_TESTNET` | `false` | `true` if NFTs are soulbound |
39
+ | `VITE_ACCESS_GATE_ID_MAINNET` | — | Gate object ID (mainnet) |
40
+ | `VITE_UPLOAD_RELAY_MAX_TIP_MIST` | `5000000` | Max relay tip cap in MIST (0.005 SUI) |
41
+
42
+ The `access_gate` package and `PlatformConfig` come from the published deployment
43
+ (`relayGateConfig` in `@meddleware/walrus-storage-kit`) — operators only configure the values listed
44
+ above.
45
+
46
+ ## Docker build
47
+
48
+ The Docker context is this repo root. `@meddleware/walrus-storage-kit` resolves from the npm registry,
49
+ so the library must be published before building the image.
50
+
51
+ ```bash
52
+ docker build \
53
+ --build-arg VITE_NETWORK=testnet \
54
+ --build-arg VITE_WALRUS_RELAY_TESTNET=https://sui-walrus-storage-relay.example.com \
55
+ --build-arg VITE_ACCESS_GATE_ID_TESTNET=0x... \
56
+ -t walrus-storage-ui:latest .
57
+ ```
58
+
59
+ ## Architecture
60
+
61
+ Thin SPA — no accounting logic, no chain state derivation. The app:
62
+ 1. Reads config from env vars
63
+ 2. Wires `@meddleware/walrus-storage-kit` composables (`useAccessGate`, `useWalrusRelay`) to the
64
+ connected wallet
65
+ 3. Passes a `performUpload` callback to `WalrusUpload`, which holds the `@mysten/walrus` client
66
+ and the upload flow logic
67
+ 4. Displays results
68
+
69
+ All financial truth is on-chain. This UI is an orchestration shell.
70
+
71
+ ## License
72
+
73
+ 0BSD
package/index.html ADDED
@@ -0,0 +1,14 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg">
7
+ <title>Walrus Uploader · MeddleWare</title>
8
+ <meta name="description" content="Upload blobs to Walrus decentralised storage on Sui through the MeddleWare relay. Client-side; your wallet signs and pays." />
9
+ </head>
10
+ <body>
11
+ <div id="app"></div>
12
+ <script type="module" src="/src/main.ts"></script>
13
+ </body>
14
+ </html>
package/package.json CHANGED
@@ -1,6 +1,95 @@
1
1
  {
2
2
  "name": "@meddleware/walrus-storage-ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.63",
4
+ "homepage": "https://docs.meddleware.co.uk/blockchain/sui/walrus-storage/",
5
+ "description": "Standalone Vue 3 SPA for uploading blobs to Walrus decentralised storage and managing owned blobs — wallet-connected, NFT-gated relay support.",
6
+ "author": "Meddleware <dev@meddleware.co.uk>",
7
+ "license": "0BSD",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/meddleware-org/walrus-storage-ui.git"
11
+ },
12
+ "type": "module",
13
+ "sideEffects": [
14
+ "*.css",
15
+ "*.vue"
16
+ ],
17
+ "exports": {
18
+ ".": {
19
+ "types": "./src/index.ts",
20
+ "default": "./src/index.ts"
21
+ },
22
+ "./package.json": "./package.json"
23
+ },
24
+ "files": [
25
+ "src",
26
+ "index.html",
27
+ "vite.config.ts",
28
+ "tsconfig.json",
29
+ "CHANGELOG.md"
30
+ ],
31
+ "scripts": {
32
+ "dev": "vite",
33
+ "build": "vue-tsc --noEmit && vite build",
34
+ "preview": "vite preview",
35
+ "type-check": "vue-tsc --noEmit",
36
+ "test": "vitest run",
37
+ "test:watch": "vitest",
38
+ "test:e2e": "playwright test",
39
+ "lint:css": "stylelint \"src/**/*.{css,vue}\"",
40
+ "lint:js": "eslint .",
41
+ "lint": "npm run lint:css && npm run lint:js && npm run lint:html",
42
+ "lint:html": "html-validate \"src/**/*.vue\"",
43
+ "licenses": "node scripts/third-party-licenses.mjs",
44
+ "check:licenses": "node scripts/third-party-licenses.mjs --check"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "dependencies": {
50
+ "@meddleware/design-tokens": "^0.1.10",
51
+ "@meddleware/ui": "^0.1.34",
52
+ "@meddleware/walrus-storage-client": "^0.0.32",
53
+ "@meddleware/walrus-storage-kit": "^0.1.35",
54
+ "@mysten/sui": "^2.33.2",
55
+ "@mysten/walrus": "~1.2.32",
56
+ "@mysten/walrus-wasm": "~0.3.1",
57
+ "vue": "^3.5.43"
58
+ },
59
+ "peerDependencies": {
60
+ "@meddleware/wallet-adapter": ">=0.0.12 <0.2.0"
61
+ },
62
+ "devDependencies": {
63
+ "@meddleware/access-gate-client": "^0.0.12",
64
+ "@meddleware/eslint-config": "^0.0.4",
65
+ "@meddleware/wallet-adapter": "^0.0.18",
66
+ "@playwright/test": "~1.64.1",
67
+ "@types/node": "~26.6.5",
68
+ "@vitejs/plugin-vue": "^6.0.8",
69
+ "@vue/eslint-config-typescript": "^14.9.0",
70
+ "autoprefixer": "^10.4.20",
71
+ "eslint": "^10.11.0",
72
+ "eslint-plugin-vue": "^10.11.1",
73
+ "eslint-plugin-vuejs-accessibility": "^2.6.0",
74
+ "html-validate": "^11.16.1",
75
+ "html-validate-vue": "^9.3.1",
76
+ "jiti": "^2.7.0",
77
+ "postcss": "^8.4.49",
78
+ "postcss-html": "^2.0.0",
79
+ "stylelint": "^17.15.0",
80
+ "stylelint-config-standard": "^40.0.0",
81
+ "typescript": "~6.0.0",
82
+ "vite": "^8.3.0",
83
+ "vitest": "~5.0.2",
84
+ "vue-eslint-parser": "^10.4.1",
85
+ "vue-tsc": "~3.3.0"
86
+ },
87
+ "engines": {
88
+ "node": "^22.18.0 || >=24.12.0"
89
+ },
90
+ "browserslist": [
91
+ "> 0.5%",
92
+ "last 2 versions",
93
+ "not dead"
94
+ ]
95
+ }
package/src/App.vue ADDED
@@ -0,0 +1,56 @@
1
+ <script setup lang="ts">
2
+ // Standalone shell for the Walrus SPA: app header + footer wrapping the core tool view.
3
+ // The core UI lives in WalrusStorageView.vue (also exported for inline embedding in the dashboard).
4
+ import { AppHeader, AppFooter, ColorModeControl, useColorMode } from '@meddleware/ui'
5
+ import { TipConfigBadge } from '@meddleware/walrus-storage-kit'
6
+ import { walrusNetwork, OPERATOR_RELAY_HOSTS } from './config.js'
7
+ import WalrusStorageView from './components/WalrusStorageView.vue'
8
+
9
+ const { mode, set } = useColorMode('dark')
10
+ const DOCS_URL = import.meta.env.VITE_DOCS_URL || 'https://docs.meddleware.co.uk/blockchain/sui/walrus-storage/'
11
+ const DEV_URL = import.meta.env.VITE_DEV_URL || 'https://dev.meddleware.co.uk/sui/walrus-storage/'
12
+ </script>
13
+
14
+ <template>
15
+ <div class="app">
16
+ <AppHeader variant="dark">
17
+ <template #brand>
18
+ <h1 class="brand-title">Walrus Assets</h1>
19
+ </template>
20
+ <template #actions>
21
+ <TipConfigBadge v-if="walrusNetwork" :host="OPERATOR_RELAY_HOSTS[walrusNetwork]" />
22
+ <ColorModeControl :model-value="mode" @update:model-value="set" />
23
+ </template>
24
+ </AppHeader>
25
+
26
+ <main class="app__content">
27
+ <WalrusStorageView />
28
+ </main>
29
+
30
+ <AppFooter :docs-url="DOCS_URL" :dev-url="DEV_URL" />
31
+ </div>
32
+ </template>
33
+
34
+ <style scoped>
35
+ .app {
36
+ min-height: 100vh;
37
+ display: flex;
38
+ flex-direction: column;
39
+ }
40
+
41
+ /* Centre the tool at the shared tool-content width when running standalone. The dashboard
42
+ supplies its own width container, so this lives in the shell, not WalrusStorageView. */
43
+ .app__content {
44
+ flex: 1;
45
+ width: 100%;
46
+ max-width: var(--mw-tool-content-max);
47
+ margin: 0 auto;
48
+ box-sizing: border-box;
49
+ padding: 1.5rem 1.25rem 4rem;
50
+ }
51
+
52
+ .brand-title {
53
+ font: inherit;
54
+ margin: 0;
55
+ }
56
+ </style>
@@ -0,0 +1,68 @@
1
+ // Group a wallet's owned Walrus blobs by `blobId` for display. Re-uploading the same content creates
2
+ // multiple independent Blob objects (each its own storage reservation); grouping collapses those into
3
+ // one row so My Blobs matches how Walruscan thinks per-blobId, while still exposing each copy.
4
+ import type { OwnedBlob } from '@meddleware/walrus-storage-client'
5
+
6
+ export interface BlobGroup {
7
+ /** Shared Walrus blob id. */
8
+ blobId: string
9
+ /** Size in bytes (identical across copies of the same content). */
10
+ size: number
11
+ /** Furthest storage end epoch across copies — the effective availability of this content. */
12
+ maxEndEpoch: number
13
+ /** True if any copy is certified (i.e. the content is currently available on-chain). */
14
+ anyCertified: boolean
15
+ /** The copy that defines availability (max end epoch); the default target for Extend. */
16
+ representative: OwnedBlob
17
+ /** All underlying Blob objects for this blobId, furthest-expiry first. */
18
+ copies: OwnedBlob[]
19
+ }
20
+
21
+ /** Collapse owned blobs into per-blobId groups, each sorted by end epoch (furthest first). */
22
+ export function groupBlobs(blobs: OwnedBlob[]): BlobGroup[] {
23
+ const byId = new Map<string, OwnedBlob[]>()
24
+ for (const b of blobs) {
25
+ const list = byId.get(b.blobId)
26
+ if (list) list.push(b)
27
+ else byId.set(b.blobId, [b])
28
+ }
29
+ const groups: BlobGroup[] = []
30
+ for (const [blobId, copies] of byId) {
31
+ const sorted = [...copies].sort((a, b) => b.endEpoch - a.endEpoch)
32
+ const representative = sorted[0]
33
+ groups.push({
34
+ blobId,
35
+ size: representative.size,
36
+ maxEndEpoch: representative.endEpoch,
37
+ anyCertified: sorted.some((b) => b.certified),
38
+ representative,
39
+ copies: sorted,
40
+ })
41
+ }
42
+ // Most-expiring-soonest groups first (matches the pre-grouping sort), so attention lands on them.
43
+ return groups.sort((a, b) => a.maxEndEpoch - b.maxEndEpoch)
44
+ }
45
+
46
+ /** Epochs of storage left before expiry (clamped at 0). */
47
+ export function epochsLeft(endEpoch: number, currentEpoch: number): number {
48
+ return Math.max(0, endEpoch - currentEpoch)
49
+ }
50
+
51
+ /** Human label for a blob's remaining lifetime, transport-independent (no fabricated day estimate). */
52
+ export function expiryLabel(endEpoch: number, currentEpoch: number): string {
53
+ const left = epochsLeft(endEpoch, currentEpoch)
54
+ if (left <= 0) return `epoch ${endEpoch} · expired`
55
+ return `epoch ${endEpoch} · ${left} epoch${left === 1 ? '' : 's'} left`
56
+ }
57
+
58
+ /**
59
+ * Epochs that can still be added to a blob before hitting `max_epochs_ahead` (a blob's end epoch
60
+ * cannot exceed `currentEpoch + maxReservation`). Zero means it's already at max lifetime.
61
+ */
62
+ export function maxExtendableEpochs(
63
+ endEpoch: number,
64
+ currentEpoch: number,
65
+ maxReservation: number,
66
+ ): number {
67
+ return Math.max(0, currentEpoch + maxReservation - endEpoch)
68
+ }