@meddleware/walrus-ui 0.1.24 → 0.1.25

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/walrus-ui",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
4
4
  "description": "Standalone Vue 3 SPA for uploading blobs to Walrus decentralised storage and managing owned blobs — wallet-connected, NFT-gated relay support.",
5
5
  "author": "Meddleware <dev@meddleware.co.uk>",
6
6
  "license": "0BSD",
@@ -45,7 +45,7 @@
45
45
  "@meddleware/ui": "^0.1.10",
46
46
  "@meddleware/wallet-adapter": "^0.0.5",
47
47
  "@meddleware/walrus-client": "^0.0.9",
48
- "@meddleware/walrus-relay": "^0.1.9",
48
+ "@meddleware/walrus-relay": "^0.1.10",
49
49
  "@mysten/sui": "^2.30.0",
50
50
  "@mysten/wallet-standard": "^0.20.0",
51
51
  "@mysten/walrus": "~1.2.24",
@@ -19,6 +19,7 @@ import { useWallet, getSuiClient } from '../wallet.js'
19
19
  import { fetchChallenge, buildAccessProof } from '@meddleware/nft-gate-client'
20
20
  import { NETWORK, relayHosts, accessGate, uploadRelayMaxTipMist, walruscanBlobUrl } from '../config.js'
21
21
  import { runBlobUpload } from '../upload-flow.js'
22
+ import { getCertifyRetry } from '@meddleware/walrus-relay'
22
23
  import {
23
24
  consumeStorageKey,
24
25
  isRedeemedConflict,
@@ -101,22 +102,29 @@ async function performUpload(
101
102
  })
102
103
 
103
104
  const runOnce = async (authToken: string | undefined): Promise<UploadResult> => {
104
- const r = await runBlobUpload({
105
- bytes,
106
- network: NETWORK,
107
- relayHost: opts.relayHost,
108
- address,
109
- wasmUrl: walrusWasmUrl,
110
- maxTipMist: uploadRelayMaxTipMist(),
111
- epochs: MAX_SINGLE_RESERVATION_EPOCHS,
112
- executor,
113
- suiClient: getSuiClient(),
114
- authToken,
115
- onStatus: opts.onStatus,
116
- })
117
- // Success → clear the consume layer (the use is now genuinely spent for an upload).
118
- if (consumeKey) storage.removeItem(consumeKey)
119
- return r
105
+ try {
106
+ const r = await runBlobUpload({
107
+ bytes,
108
+ network: NETWORK,
109
+ relayHost: opts.relayHost,
110
+ address,
111
+ wasmUrl: walrusWasmUrl,
112
+ maxTipMist: uploadRelayMaxTipMist(),
113
+ epochs: MAX_SINGLE_RESERVATION_EPOCHS,
114
+ executor,
115
+ suiClient: getSuiClient(),
116
+ authToken,
117
+ onStatus: opts.onStatus,
118
+ })
119
+ // Success → clear the consume layer (the use is now genuinely spent for an upload).
120
+ if (consumeKey) storage.removeItem(consumeKey)
121
+ return r
122
+ } catch (e) {
123
+ // A certify-only failure means the upload already landed (relay access was used), so clear the
124
+ // consume too — the certify retry is a plain Sui tx and must not trigger a fresh NFT consume.
125
+ if (getCertifyRetry(e) && consumeKey) storage.removeItem(consumeKey)
126
+ throw e
127
+ }
120
128
  }
121
129
 
122
130
  try {
@@ -12,6 +12,7 @@
12
12
  // discovery) hands the relay an old tx with a non-matching nonce. Each attempt re-encodes (free) and
13
13
  // registers fresh so the tip+nonce the relay verifies is always recent.
14
14
  import type { UploadResult, UploadProgress } from '@meddleware/walrus-relay'
15
+ import { attachCertifyRetry } from '@meddleware/walrus-relay'
15
16
 
16
17
  /** Minimal transaction executor — the structural subset App.vue's wallet executor already provides. */
17
18
  export interface UploadExecutor {
@@ -104,13 +105,27 @@ export async function runBlobUpload(deps: RunBlobUploadDeps): Promise<UploadResu
104
105
  deps.onStatus({ step: 'upload', detail: 'Uploading to the relay…' })
105
106
  await flow.upload({ digest: reg.digest, deletable: false })
106
107
 
107
- deps.onStatus({ step: 'certify', detail: 'Certifying (approve in wallet)…' })
108
- const certTx = flow.certify()
109
- certTx.setSenderIfNotSet(deps.address)
110
- await certTx.build({ client: deps.suiClient })
111
- const cert = await deps.executor.signAndExecute(certTx)
112
- await deps.executor.waitForTransaction(cert.digest)
108
+ // Certify is a plain owner tx built from the storage-node certificate the live `flow` now holds
109
+ // (no relay, no tip). If it fails (e.g. the user rejects the prompt) the blob is already registered
110
+ // + stored + paid, so we never want to redo the upload — we expose this closure to retry certify
111
+ // alone. Re-calling `flow.certify()` just rebuilds the same tx from the in-memory certificate.
112
+ const runCertify = async (): Promise<UploadResult> => {
113
+ deps.onStatus({ step: 'certify', detail: 'Certifying (approve in wallet)…' })
114
+ const certTx = flow.certify()
115
+ certTx.setSenderIfNotSet(deps.address)
116
+ await certTx.build({ client: deps.suiClient })
117
+ const cert = await deps.executor.signAndExecute(certTx)
118
+ await deps.executor.waitForTransaction(cert.digest)
119
+ const blob = await flow.getBlob()
120
+ return { blobId: blob.blobId, url: walrusBlobUrl(deps.network, blob.blobId), digest: cert.digest }
121
+ }
113
122
 
114
- const blob = await flow.getBlob()
115
- return { blobId: blob.blobId, url: walrusBlobUrl(deps.network, blob.blobId), digest: cert.digest }
123
+ try {
124
+ return await runCertify()
125
+ } catch (e) {
126
+ // Upload landed but certify did not: attach a retry so the UI can offer a "Certify" button
127
+ // rather than discarding the paid upload. The closure keeps the live flow (and its certificate).
128
+ attachCertifyRetry<UploadResult>(e, runCertify)
129
+ throw e
130
+ }
116
131
  }