@meddleware/seal-ui 0.0.10 → 0.0.12
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 +8 -0
- package/package.json +3 -3
- package/src/App.vue +2 -1
- package/src/components/SealView.vue +91 -14
- package/src/config.ts +10 -0
- package/src/seal.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
All notable changes to `@meddleware/seal-ui` are documented here.
|
|
4
4
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows [Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## [0.0.12] - 2026-09-17
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- `VITE_DOCS_URL` build-time env var — configures the documentation link rendered in the app
|
|
11
|
+
footer. Defaults to `https://docs.meddleware.co.uk/blockchain/sui/sealed-storage/`.
|
|
12
|
+
- `homepage` in `package.json` — links to the Sealed Storage docs section on npmjs.com.
|
|
13
|
+
|
|
6
14
|
## [0.1.0] - 2026-09-02
|
|
7
15
|
|
|
8
16
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meddleware/seal-ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
|
+
"homepage": "https://docs.meddleware.co.uk/blockchain/sui/sealed-storage/",
|
|
4
5
|
"description": "Standalone Vue 3 SPA for Sealed Storage — client-side Seal encryption + access-gated, decentralised Walrus storage on Sui.",
|
|
5
6
|
"author": "MeddleWare <meddleware@proton.me>",
|
|
6
7
|
"license": "0BSD",
|
|
@@ -38,10 +39,9 @@
|
|
|
38
39
|
},
|
|
39
40
|
"dependencies": {
|
|
40
41
|
"@meddleware/design-tokens": "^0.1.2",
|
|
41
|
-
"@meddleware/seal-client": "^0.0.
|
|
42
|
+
"@meddleware/seal-client": "^0.0.3",
|
|
42
43
|
"@meddleware/ui": "^0.1.11",
|
|
43
44
|
"@meddleware/wallet-adapter": "^0.0.5",
|
|
44
|
-
"@mysten/seal": "^1.4.7",
|
|
45
45
|
"@mysten/sui": "^2.30.0",
|
|
46
46
|
"@mysten/wallet-standard": "^0.20.0",
|
|
47
47
|
"vue": "^3.5.40"
|
package/src/App.vue
CHANGED
|
@@ -6,6 +6,7 @@ import { NETWORK } from './config.js'
|
|
|
6
6
|
import SealView from './components/SealView.vue'
|
|
7
7
|
|
|
8
8
|
const { mode, set } = useColorMode('dark')
|
|
9
|
+
const DOCS_URL = import.meta.env.VITE_DOCS_URL || 'https://docs.meddleware.co.uk/blockchain/sui/sealed-storage/'
|
|
9
10
|
</script>
|
|
10
11
|
|
|
11
12
|
<template>
|
|
@@ -22,7 +23,7 @@ const { mode, set } = useColorMode('dark')
|
|
|
22
23
|
|
|
23
24
|
<SealView />
|
|
24
25
|
|
|
25
|
-
<AppFooter />
|
|
26
|
+
<AppFooter :docs-url="DOCS_URL" />
|
|
26
27
|
</div>
|
|
27
28
|
</template>
|
|
28
29
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// Styles are scoped to this component so the dashboard can import it without pulling seal-ui's
|
|
8
8
|
// global stylesheet (which restyles body / #app / bare inputs). The standalone app keeps those
|
|
9
9
|
// globals via main.ts → styles.css.
|
|
10
|
-
import { computed, onMounted, ref } from 'vue'
|
|
10
|
+
import { computed, onMounted, ref, watch } from 'vue'
|
|
11
11
|
import { AppTabNav, type AppTab } from '@meddleware/ui'
|
|
12
12
|
import { Transaction } from '@mysten/sui/transactions'
|
|
13
13
|
import {
|
|
@@ -15,9 +15,10 @@ import {
|
|
|
15
15
|
type SealedManifest,
|
|
16
16
|
type SealPolicyProvider,
|
|
17
17
|
type SealedContentPointer,
|
|
18
|
+
type FieldSuggestion,
|
|
18
19
|
} from '@meddleware/seal-client'
|
|
19
20
|
import { WalletGuard } from '@meddleware/wallet-adapter'
|
|
20
|
-
import { useWallet } from '../wallet.js'
|
|
21
|
+
import { useWallet, getSuiClient } from '../wallet.js'
|
|
21
22
|
import { registry, getSealController } from '../seal.js'
|
|
22
23
|
import { storeBlob, readBlob } from '../walrus.js'
|
|
23
24
|
import { discoverSealedContent } from '../sealed-content.js'
|
|
@@ -36,12 +37,15 @@ const TABS: AppTab[] = [
|
|
|
36
37
|
const tab = ref<string>('encrypt')
|
|
37
38
|
|
|
38
39
|
// Deep-link from access-gate-ui: ?gate=<id> preselects the nft-gate policy + gate.
|
|
40
|
+
// Force manual mode so the pre-set ID renders in the text input immediately, before suggestions load.
|
|
39
41
|
onMounted(() => {
|
|
40
42
|
const gate = new URLSearchParams(location.search).get('gate')
|
|
41
43
|
if (gate && providers.some((p) => p.type === 'nft-gate')) {
|
|
42
44
|
encType.value = 'nft-gate'
|
|
43
45
|
encValues.value = { ...encValues.value, gateId: gate }
|
|
46
|
+
encGateManual.value = true
|
|
44
47
|
unlockGateId.value = gate
|
|
48
|
+
unlockGateManual.value = true
|
|
45
49
|
}
|
|
46
50
|
})
|
|
47
51
|
const busy = ref(false)
|
|
@@ -63,6 +67,21 @@ function triggerDownload(blob: Blob, name: string): void {
|
|
|
63
67
|
URL.revokeObjectURL(url)
|
|
64
68
|
}
|
|
65
69
|
|
|
70
|
+
// ── Gate suggestions (nft-gate policy) ───────────────────────────────────────
|
|
71
|
+
const encGateSuggestions = ref<FieldSuggestion[]>([])
|
|
72
|
+
const encGateManual = ref(false)
|
|
73
|
+
const encGateLoading = ref(false)
|
|
74
|
+
const unlockGateSuggestions = ref<FieldSuggestion[]>([])
|
|
75
|
+
const unlockGateManual = ref(false)
|
|
76
|
+
const unlockGateLoading = ref(false)
|
|
77
|
+
|
|
78
|
+
async function fetchGateSuggestions(provider: SealPolicyProvider | null): Promise<FieldSuggestion[]> {
|
|
79
|
+
const addr = account.value?.address
|
|
80
|
+
if (!addr || !provider || !provider.suggest) return []
|
|
81
|
+
const result = await provider.suggest({ account: addr, client: getSuiClient() })
|
|
82
|
+
return result.gateId ?? []
|
|
83
|
+
}
|
|
84
|
+
|
|
66
85
|
// ── Encrypt ──────────────────────────────────────────────────────────────────
|
|
67
86
|
const encType = ref(providers[0]?.type ?? '')
|
|
68
87
|
const encProvider = computed(() => providers.find((p) => p.type === encType.value) ?? null)
|
|
@@ -75,6 +94,39 @@ function onEncFile(e: Event): void {
|
|
|
75
94
|
encFile.value = (e.target as HTMLInputElement).files?.[0] ?? null
|
|
76
95
|
}
|
|
77
96
|
|
|
97
|
+
watch(
|
|
98
|
+
[() => account.value?.address, encProvider],
|
|
99
|
+
async ([, provider]) => {
|
|
100
|
+
if (encType.value !== 'nft-gate') { encGateSuggestions.value = []; return }
|
|
101
|
+
encGateLoading.value = true
|
|
102
|
+
encGateManual.value = false
|
|
103
|
+
try {
|
|
104
|
+
encGateSuggestions.value = await fetchGateSuggestions(provider)
|
|
105
|
+
} catch {
|
|
106
|
+
encGateSuggestions.value = []
|
|
107
|
+
} finally {
|
|
108
|
+
encGateLoading.value = false
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
watch(
|
|
114
|
+
[() => account.value?.address, tab],
|
|
115
|
+
async ([, t]) => {
|
|
116
|
+
if (t !== 'unlock') return
|
|
117
|
+
const nftGateProvider = providers.find((p) => p.type === 'nft-gate') ?? null
|
|
118
|
+
unlockGateLoading.value = true
|
|
119
|
+
unlockGateManual.value = false
|
|
120
|
+
try {
|
|
121
|
+
unlockGateSuggestions.value = await fetchGateSuggestions(nftGateProvider)
|
|
122
|
+
} catch {
|
|
123
|
+
unlockGateSuggestions.value = []
|
|
124
|
+
} finally {
|
|
125
|
+
unlockGateLoading.value = false
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
)
|
|
129
|
+
|
|
78
130
|
async function performEncrypt(): Promise<void> {
|
|
79
131
|
errorMsg.value = null
|
|
80
132
|
status.value = null
|
|
@@ -308,17 +360,31 @@ async function performUnlock(item: SealedContentPointer): Promise<void> {
|
|
|
308
360
|
<p v-if="encProvider" class="muted">{{ encProvider.describe().help }}</p>
|
|
309
361
|
|
|
310
362
|
<template v-if="encProvider">
|
|
311
|
-
<
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
363
|
+
<template v-for="f in encProvider.describe().encryptFields" :key="f.name">
|
|
364
|
+
<!-- Gate ID field: show a picker when wallet-owned gates are available -->
|
|
365
|
+
<label v-if="f.name === 'gateId'" class="field">
|
|
366
|
+
<span>{{ f.label }}<template v-if="f.required"> *</template></span>
|
|
367
|
+
<span v-if="encGateLoading" class="muted" style="font-size:0.8rem">Loading your gates…</span>
|
|
368
|
+
<template v-else-if="encGateSuggestions.length && !encGateManual">
|
|
369
|
+
<select v-model="encValues[f.name]">
|
|
370
|
+
<option value="">Select a gate…</option>
|
|
371
|
+
<option v-for="s in encGateSuggestions" :key="s.value" :value="s.value">{{ s.label }}</option>
|
|
372
|
+
</select>
|
|
373
|
+
<button class="link" style="margin-top:0.25rem" @click="encGateManual = true; encValues[f.name] = ''">Enter ID manually</button>
|
|
374
|
+
</template>
|
|
375
|
+
<template v-else>
|
|
376
|
+
<input type="text" v-model="encValues[f.name]" :placeholder="f.help" />
|
|
377
|
+
<button v-if="encGateSuggestions.length" class="link" style="margin-top:0.25rem" @click="encGateManual = false; encValues[f.name] = ''">← Back to picker</button>
|
|
378
|
+
</template>
|
|
379
|
+
</label>
|
|
380
|
+
<!-- All other fields: generic rendering -->
|
|
381
|
+
<label v-else class="field" :class="{ checkbox: f.kind === 'boolean' }">
|
|
382
|
+
<span>{{ f.label }}<template v-if="f.required"> *</template></span>
|
|
383
|
+
<input v-if="f.kind === 'datetime'" type="datetime-local" v-model="encValues[f.name]" />
|
|
384
|
+
<input v-else-if="f.kind === 'boolean'" type="checkbox" v-model="encValues[f.name]" />
|
|
385
|
+
<input v-else type="text" v-model="encValues[f.name]" :placeholder="f.help" />
|
|
386
|
+
</label>
|
|
387
|
+
</template>
|
|
322
388
|
</template>
|
|
323
389
|
|
|
324
390
|
<label class="field">
|
|
@@ -361,7 +427,18 @@ async function performUnlock(item: SealedContentPointer): Promise<void> {
|
|
|
361
427
|
</p>
|
|
362
428
|
<label class="field">
|
|
363
429
|
<span>Access gate ID</span>
|
|
364
|
-
<
|
|
430
|
+
<span v-if="unlockGateLoading" class="muted" style="font-size:0.8rem">Loading your gates…</span>
|
|
431
|
+
<template v-else-if="unlockGateSuggestions.length && !unlockGateManual">
|
|
432
|
+
<select v-model="unlockGateId">
|
|
433
|
+
<option value="">Select a gate…</option>
|
|
434
|
+
<option v-for="s in unlockGateSuggestions" :key="s.value" :value="s.value">{{ s.label }}</option>
|
|
435
|
+
</select>
|
|
436
|
+
<button class="link" style="margin-top:0.25rem" @click="unlockGateManual = true; unlockGateId = ''">Enter ID manually</button>
|
|
437
|
+
</template>
|
|
438
|
+
<template v-else>
|
|
439
|
+
<input type="text" v-model="unlockGateId" placeholder="0x… gate object id" />
|
|
440
|
+
<button v-if="unlockGateSuggestions.length" class="link" style="margin-top:0.25rem" @click="unlockGateManual = false; unlockGateId = ''">← Back to picker</button>
|
|
441
|
+
</template>
|
|
365
442
|
</label>
|
|
366
443
|
<button class="primary" :disabled="busy || !unlockGateId" @click="performDiscover">
|
|
367
444
|
{{ busy ? 'Working…' : 'Find sealed content' }}
|
package/src/config.ts
CHANGED
|
@@ -96,6 +96,16 @@ export const WALRUS_AGGREGATOR: string =
|
|
|
96
96
|
/** Default blob lifetime (Walrus storage epochs). */
|
|
97
97
|
export const WALRUS_EPOCHS = Number(env.VITE_WALRUS_EPOCHS || '5')
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Meddleware's deployed `access_gate` package IDs per network.
|
|
101
|
+
* Used by the nft-gate provider's `suggest()` to list AdminCap-owned gates for the connected wallet.
|
|
102
|
+
* Kept in sync with access-gate-ui/src/constants.ts (same deployment).
|
|
103
|
+
*/
|
|
104
|
+
export const ACCESS_GATE_PACKAGE_ID: Record<Network, string> = {
|
|
105
|
+
testnet: '0x0bedd0b27d993d3292ca6a5315f7562de8bc0ff3752b445b4c53252c76f2d20d',
|
|
106
|
+
mainnet: '',
|
|
107
|
+
}
|
|
108
|
+
|
|
99
109
|
/** True when the on-chain policy package + committee are configured for the active network. */
|
|
100
110
|
export const SEAL_CONFIGURED = Boolean(SEAL_PACKAGE_ID) && SEAL_SERVERS.length > 0
|
|
101
111
|
|
package/src/seal.ts
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
import { createDefaultRegistry, type PolicyRegistry } from '@meddleware/seal-client'
|
|
7
7
|
import type { SealController } from '@meddleware/seal-client/controller'
|
|
8
8
|
import { getSuiClient } from './wallet.js'
|
|
9
|
-
import { NETWORK, SEAL_PACKAGE_ID, SEAL_THRESHOLD, SEAL_SERVERS } from './config.js'
|
|
9
|
+
import { NETWORK, SEAL_PACKAGE_ID, SEAL_THRESHOLD, SEAL_SERVERS, ACCESS_GATE_PACKAGE_ID } from './config.js'
|
|
10
10
|
|
|
11
11
|
/** The policy registry (nft-gate + time-lock as peers). Iterate `list()` to render the picker. */
|
|
12
|
-
export const registry: PolicyRegistry = createDefaultRegistry()
|
|
12
|
+
export const registry: PolicyRegistry = createDefaultRegistry(ACCESS_GATE_PACKAGE_ID[NETWORK])
|
|
13
13
|
|
|
14
14
|
let controller: SealController | null = null
|
|
15
15
|
|