@meddleware/seal-ui 0.0.10 → 0.0.11

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meddleware/seal-ui",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "Standalone Vue 3 SPA for Sealed Storage — client-side Seal encryption + access-gated, decentralised Walrus storage on Sui.",
5
5
  "author": "MeddleWare <meddleware@proton.me>",
6
6
  "license": "0BSD",
@@ -38,10 +38,9 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@meddleware/design-tokens": "^0.1.2",
41
- "@meddleware/seal-client": "^0.0.2",
41
+ "@meddleware/seal-client": "^0.0.3",
42
42
  "@meddleware/ui": "^0.1.11",
43
43
  "@meddleware/wallet-adapter": "^0.0.5",
44
- "@mysten/seal": "^1.4.7",
45
44
  "@mysten/sui": "^2.30.0",
46
45
  "@mysten/wallet-standard": "^0.20.0",
47
46
  "vue": "^3.5.40"
@@ -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
- <label
312
- v-for="f in encProvider.describe().encryptFields"
313
- :key="f.name"
314
- class="field"
315
- :class="{ checkbox: f.kind === 'boolean' }"
316
- >
317
- <span>{{ f.label }}<template v-if="f.required"> *</template></span>
318
- <input v-if="f.kind === 'datetime'" type="datetime-local" v-model="encValues[f.name]" />
319
- <input v-else-if="f.kind === 'boolean'" type="checkbox" v-model="encValues[f.name]" />
320
- <input v-else type="text" v-model="encValues[f.name]" :placeholder="f.help" />
321
- </label>
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
- <input type="text" v-model="unlockGateId" placeholder="0x… gate object id" />
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