@meddleware/seal-ui 0.0.31 → 0.0.33

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 CHANGED
@@ -3,6 +3,18 @@
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.33] - 2026-10-09
7
+
8
+ ### Changed
9
+
10
+ - Manifest check validates the policy type and params against the network's registry (seal-client 0.0.19 `parseSealedManifest(text, registry)`); depends on access-gate-client ^0.0.8, seal-client ^0.0.19, ui ^0.1.31, design-tokens ^0.1.9, eslint-config ^0.0.2
11
+
12
+ ## [0.0.32] - 2026-10-08
13
+
14
+ ### Changed
15
+
16
+ - Operator-only discovery by default (opt-in for other publishers), refuse non-linked gates when sealing, publisher result handling; seal-client 0.0.16, access-gate-client 0.0.6, walrus-client 0.0.26
17
+
6
18
  ## [0.0.31] - 2026-10-03
7
19
 
8
20
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meddleware/seal-ui",
3
- "version": "0.0.31",
3
+ "version": "0.0.33",
4
4
  "homepage": "https://docs.meddleware.co.uk/blockchain/sui/sealed-storage/",
5
5
  "description": "Standalone Vue 3 SPA for Sealed Storage — client-side Seal encryption + access-gated, decentralised Walrus storage on Sui.",
6
6
  "author": "MeddleWare <dev@meddleware.co.uk>",
@@ -44,12 +44,12 @@
44
44
  "access": "public"
45
45
  },
46
46
  "dependencies": {
47
- "@meddleware/access-gate-client": "^0.0.4",
48
- "@meddleware/design-tokens": "^0.1.8",
49
- "@meddleware/seal-client": "^0.0.15",
50
- "@meddleware/ui": "^0.1.30",
51
- "@meddleware/walrus-client": "^0.0.25",
52
- "@mysten/sui": "^2.33.1",
47
+ "@meddleware/access-gate-client": "^0.0.8",
48
+ "@meddleware/design-tokens": "^0.1.9",
49
+ "@meddleware/seal-client": "^0.0.19",
50
+ "@meddleware/ui": "^0.1.31",
51
+ "@meddleware/walrus-client": "^0.0.26",
52
+ "@mysten/sui": "^2.33.2",
53
53
  "@mysten/wallet-standard": "^0.21.0",
54
54
  "vue": "^3.5.43"
55
55
  },
@@ -57,6 +57,7 @@
57
57
  "@meddleware/wallet-adapter": ">=0.0.12 <0.2.0"
58
58
  },
59
59
  "devDependencies": {
60
+ "@meddleware/eslint-config": "^0.0.2",
60
61
  "@meddleware/wallet-adapter": "^0.0.13",
61
62
  "@types/node": "~24.12.2",
62
63
  "@vitejs/plugin-vue": "^6.0.9",
@@ -242,7 +242,7 @@ const decValues = ref<Record<string, string | boolean>>({})
242
242
  // AND enforce that it targets the network this app is built for — a manifest sealed on another
243
243
  // network references a package + committee that don't exist here and can never decrypt. The
244
244
  // `decManifestError` is surfaced next to the input so a rejection explains itself.
245
- const decCheck = computed(() => checkManifestForNetwork(decManifestText.value, network.value))
245
+ const decCheck = computed(() => checkManifestForNetwork(decManifestText.value, network.value, registry.value))
246
246
  const decManifest = computed<SealedManifest | null>(() => decCheck.value.manifest)
247
247
  const decManifestError = computed<string | null>(() => decCheck.value.error)
248
248
  const decProvider = computed(() =>
@@ -394,6 +394,8 @@ const unlockGateId = ref('')
394
394
  const discovered = ref<SealedContentPointer[]>([])
395
395
  const unlockNftId = ref('')
396
396
  const unlockSoulbound = ref(false)
397
+ // Seal gives confidentiality, not authenticity: by default only the gate operator's pointers are listed.
398
+ const includeOthers = ref(false)
397
399
 
398
400
  async function performDiscover(): Promise<void> {
399
401
  errorMsg.value = null
@@ -405,7 +407,7 @@ async function performDiscover(): Promise<void> {
405
407
  busy.value = true
406
408
  try {
407
409
  status.value = 'Searching for sealed content…'
408
- discovered.value = await discoverSealedContent(unlockGateId.value)
410
+ discovered.value = await discoverSealedContent(unlockGateId.value, { includeOthers: includeOthers.value })
409
411
  status.value = discovered.value.length
410
412
  ? `Found ${discovered.value.length} item(s).`
411
413
  : 'No sealed content published for this gate.'
@@ -621,6 +623,10 @@ async function performUnlock(item: SealedContentPointer): Promise<void> {
621
623
  <button v-if="!unlockGateManual" type="button" class="link link--spaced" @click="unlockGateManual = true; unlockGateId = ''">Enter ID manually</button>
622
624
  <button v-else type="button" class="link link--spaced" @click="unlockGateManual = false; unlockGateId = ''">← Back to picker</button>
623
625
  </template>
626
+ <label class="field checkbox">
627
+ <input type="checkbox" v-model="includeOthers" />
628
+ <span>Also list content published by other addresses (unverified: anyone can publish to a gate)</span>
629
+ </label>
624
630
  <button type="button" class="primary" :disabled="busy || !unlockGateId" @click="performDiscover">
625
631
  {{ busy ? 'Working…' : 'Find sealed content' }}
626
632
  </button>
@@ -642,6 +648,7 @@ async function performUnlock(item: SealedContentPointer): Promise<void> {
642
648
 
643
649
  <article v-for="item in discovered" :key="item.contentId" class="card discovered">
644
650
  <h3 class="discovered__title">{{ item.label || '(untitled)' }}</h3>
651
+ <p v-if="includeOthers" class="muted muted--sm">Published by {{ item.publisher }}: decrypting proves it was sealed to this gate, not who wrote it.</p>
645
652
  <p class="muted discovered__blob">blob {{ item.blobId }}</p>
646
653
  <button type="button" class="primary" :disabled="busy || !account || !unlockNftId" @click="performUnlock(item)">
647
654
  Unlock &amp; download
@@ -1,4 +1,4 @@
1
- import { parseSealedManifest, type SealedManifest } from '@meddleware/seal-client'
1
+ import { parseSealedManifest, type PolicyRegistry, type SealedManifest } from '@meddleware/seal-client'
2
2
 
3
3
  /**
4
4
  * Result of validating a pasted/loaded manifest: either the parsed manifest, or a human-readable
@@ -12,8 +12,10 @@ export type ManifestCheck =
12
12
  * Validate a manifest string for use on `network`. Rejects a malformed shape (via the shared
13
13
  * `parseSealedManifest` schema guard) and a manifest sealed for a different network — the latter
14
14
  * references a policy package + key-server committee that don't exist here and can never decrypt.
15
+ * With the network's policy `registry`, the policy type must be registered and its params must parse,
16
+ * so a bad manifest fails here instead of at transaction build time.
15
17
  */
16
- export function checkManifestForNetwork(text: string, network: string): ManifestCheck {
18
+ export function checkManifestForNetwork(text: string, network: string, registry?: PolicyRegistry): ManifestCheck {
17
19
  if (!text.trim()) return { manifest: null, error: null }
18
20
  try {
19
21
  const manifest = parseSealedManifest(text)
@@ -23,7 +25,7 @@ export function checkManifestForNetwork(text: string, network: string): Manifest
23
25
  error: `This manifest is for '${manifest.network}', but this app is on '${network}'. It cannot be decrypted here.`,
24
26
  }
25
27
  }
26
- return { manifest, error: null }
28
+ return { manifest: registry ? parseSealedManifest(manifest, registry) : manifest, error: null }
27
29
  } catch (e) {
28
30
  return { manifest: null, error: e instanceof Error ? e.message : 'Invalid manifest.' }
29
31
  }
package/src/seal.ts CHANGED
@@ -44,6 +44,8 @@ export async function getSealController(): Promise<SealController> {
44
44
  policyConfigId: cfg.seal.policyConfigId,
45
45
  threshold: cfg.threshold,
46
46
  serverConfigs: cfg.servers,
47
+ // Sealing to a gate of another access_gate package can never be unlocked: refuse it.
48
+ accessGateOriginalId: cfg.accessGateOriginalId || undefined,
47
49
  },
48
50
  registryFor(cfg),
49
51
  )
@@ -1,22 +1,37 @@
1
1
  // Discover on-chain SealedContent pointers for a gate with seal-client's `listSealedContent`
2
2
  // (exact event type at the deployment's original id; BCS-decoded). Reads the read-indexer when
3
3
  // VITE_INDEXER_URL is set — display data only, falling back to the full node.
4
- import { listSealedContent, type SealedContentPointer } from '@meddleware/seal-client'
4
+ import { gateOperators, listSealedContent, type SealedContentPointer } from '@meddleware/seal-client'
5
5
  import { getSuiClient } from './wallet.js'
6
6
  import { activeConfig, INDEXER_URL } from './config.js'
7
7
 
8
8
  /** Most pointers listed for one gate. */
9
9
  export const MAX_DISCOVERED = 200
10
10
 
11
- /** Published pointers for `gateId` on the active network, newest first. */
12
- export async function discoverSealedContent(gateId: string): Promise<SealedContentPointer[]> {
11
+ /**
12
+ * Published pointers for `gateId` on the active network, newest first.
13
+ *
14
+ * Seal gives confidentiality, not authenticity: anyone can seal content to a gate and publish a
15
+ * pointer for it, and a pass holder decrypts it successfully. So by default only the gate
16
+ * operator's pointers are listed (`gateOperators`); `includeOthers` lists every publisher's, for the
17
+ * view to label as unverified.
18
+ */
19
+ export async function discoverSealedContent(
20
+ gateId: string,
21
+ opts: { includeOthers?: boolean } = {},
22
+ ): Promise<SealedContentPointer[]> {
13
23
  const cfg = activeConfig.value
14
24
  if (!cfg.seal) throw new Error(cfg.problem ?? `Sealed Storage is not available on ${cfg.network}.`)
15
- const page = await listSealedContent(getSuiClient(), {
25
+ const client = getSuiClient()
26
+ const publishers = opts.includeOthers
27
+ ? undefined
28
+ : await gateOperators(client as never, gateId, cfg.accessGateOriginalId)
29
+ const page = await listSealedContent(client, {
16
30
  originalId: cfg.seal.originalId,
17
31
  gateId,
18
32
  limit: MAX_DISCOVERED,
19
33
  indexer: INDEXER_URL ? { url: INDEXER_URL, network: cfg.network } : undefined,
34
+ ...(publishers ? { publishers } : {}),
20
35
  })
21
36
  return page.pointers
22
37
  }
package/src/walrus.ts CHANGED
@@ -16,12 +16,18 @@ export async function storeBlob(bytes: Uint8Array, opts: { sendObjectTo: string
16
16
  `set VITE_WALRUS_PUBLISHER_${cfg.network.toUpperCase()} to an operator-run publisher.`,
17
17
  )
18
18
  }
19
- return storeBlobViaPublisher(bytes, {
19
+ const stored = await storeBlobViaPublisher(bytes, {
20
20
  publisher: cfg.walrusPublisher,
21
21
  epochs: WALRUS_EPOCHS,
22
22
  sendObjectTo: opts.sendObjectTo,
23
23
  maxBytes: WALRUS_MAX_UPLOAD_BYTES,
24
24
  })
25
+ // Ciphertext carries a random Seal identity, so it cannot already be stored; if a publisher says it
26
+ // is, the user would own no Blob object to extend, so refuse rather than hand back a dead pointer.
27
+ if (stored.kind !== 'newlyCreated') {
28
+ throw new Error('The publisher reported this blob as already stored, so no Blob object was created for you. Try again.')
29
+ }
30
+ return stored.blobId
25
31
  }
26
32
 
27
33
  /** Read the ciphertext back, with the aggregator's strict consistency check. */