@meddleware/walrus-ui 0.1.20 → 0.1.22
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 +2 -2
- package/src/components/WalrusView.vue +29 -10
- package/src/upload-flow.ts +21 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meddleware/walrus-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
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",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@meddleware/nft-gate-client": "^0.0.6",
|
|
45
45
|
"@meddleware/ui": "^0.1.10",
|
|
46
46
|
"@meddleware/wallet-adapter": "^0.0.5",
|
|
47
|
-
"@meddleware/walrus-client": "^0.0.
|
|
47
|
+
"@meddleware/walrus-client": "^0.0.9",
|
|
48
48
|
"@meddleware/walrus-relay": "^0.1.8",
|
|
49
49
|
"@mysten/sui": "^2.30.0",
|
|
50
50
|
"@mysten/wallet-standard": "^0.20.0",
|
|
@@ -61,10 +61,26 @@ async function onPurchase(): Promise<void> {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
// On-chain resume discovery: given the encoded blobId, find an already-registered, uncertified blob
|
|
65
|
+
// owned by `owner` and return its register digest, so an interrupted upload can resume WITHOUT a
|
|
66
|
+
// local pointer (robust to cache-clear / new device / incognito). Best-effort: any failure falls
|
|
67
|
+
// through to a fresh register. The walrus client is loaded lazily to keep it out of the eager graph.
|
|
68
|
+
async function discoverRegistration(owner: string, blobId: string): Promise<string | undefined> {
|
|
69
|
+
try {
|
|
70
|
+
const { createWalrusClient, findUncertifiedRegisteredBlob } = await import('@meddleware/walrus-client')
|
|
71
|
+
const walrusClient = createWalrusClient({ network: NETWORK, wasmUrl: walrusWasmUrl })
|
|
72
|
+
const found = await findUncertifiedRegisteredBlob(getSuiClient(), walrusClient, owner, blobId)
|
|
73
|
+
return found?.registerDigest
|
|
74
|
+
} catch {
|
|
75
|
+
return undefined
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
64
79
|
// Wire the shared WalrusUpload widget to the extracted upload orchestration + the wallet. The
|
|
65
80
|
// register/upload/certify sequence lives in src/upload-flow.ts (unit-tested); this closure gathers
|
|
66
81
|
// the wallet-bound inputs and manages two resume layers so an interrupted upload wastes nothing —
|
|
67
|
-
// both persisted in localStorage
|
|
82
|
+
// both persisted in localStorage (fast path) and rediscoverable on-chain (fallback), so they
|
|
83
|
+
// survive a page reload — and even a cache-clear — once the same file is re-selected:
|
|
68
84
|
// 1. Single-use consume: the relay treats the permanent on-chain `consumeDigest` as the one-time
|
|
69
85
|
// redemption token, so a use is only spent when an upload succeeds. The digest is persisted and
|
|
70
86
|
// reused across retries/reload (re-signing a fresh challenge is free); cleared on success.
|
|
@@ -122,6 +138,9 @@ async function performUpload(
|
|
|
122
138
|
authToken,
|
|
123
139
|
onStatus: opts.onStatus,
|
|
124
140
|
resumeRegisterDigest,
|
|
141
|
+
// On-chain fallback when the local pointer is missing (cache-clear / new device): find an
|
|
142
|
+
// already-registered, uncertified blob for this content and resume from it — gas-free.
|
|
143
|
+
discoverRegisterDigest: (blobId) => discoverRegistration(address, blobId),
|
|
125
144
|
onRegistered: (digest) => saveRegisterResume(storage, regKey, key, digest),
|
|
126
145
|
})
|
|
127
146
|
// Success → clear both resume layers (the use is now genuinely spent for an upload).
|
|
@@ -214,15 +233,15 @@ function onSettled(): void {
|
|
|
214
233
|
|
|
215
234
|
<!-- Access held (or ungated relay): show the upload form. -->
|
|
216
235
|
<template v-else>
|
|
217
|
-
<!-- Gated relays spend a
|
|
236
|
+
<!-- Gated relays spend a credit before the file is stored — make the "attempt, not a
|
|
218
237
|
guarantee" nature explicit, while reassuring that attempts resume. -->
|
|
219
|
-
<UiNotice v-if="gateState.gateConfigured" type="info" class="
|
|
220
|
-
Uploading spends <strong>one
|
|
221
|
-
the file is stored — it pays for an upload <em>attempt</em>, not a guaranteed
|
|
222
|
-
Your attempt resumes automatically, even after a page reload if you re-select
|
|
223
|
-
file, so a
|
|
224
|
-
you abandon the upload entirely, cancel a required wallet approval, or
|
|
225
|
-
that the reserved storage lapses.
|
|
238
|
+
<UiNotice v-if="gateState.gateConfigured" type="info" class="credit-notice">
|
|
239
|
+
Uploading spends <strong>one credit</strong> from your access NFT (an on-chain step)
|
|
240
|
+
before the file is stored — it pays for an upload <em>attempt</em>, not a guaranteed
|
|
241
|
+
upload. Your attempt resumes automatically, even after a page reload if you re-select
|
|
242
|
+
the same file, so a credit is normally not lost. A credit is spent without a completed
|
|
243
|
+
upload only if you abandon the upload entirely, cancel a required wallet approval, or
|
|
244
|
+
wait long enough that the reserved storage lapses.
|
|
226
245
|
</UiNotice>
|
|
227
246
|
|
|
228
247
|
<WalrusUpload
|
|
@@ -313,7 +332,7 @@ function onSettled(): void {
|
|
|
313
332
|
text-align: center;
|
|
314
333
|
}
|
|
315
334
|
|
|
316
|
-
.
|
|
335
|
+
.credit-notice {
|
|
317
336
|
margin: 1rem 0;
|
|
318
337
|
font-size: 0.85rem;
|
|
319
338
|
line-height: 1.5;
|
package/src/upload-flow.ts
CHANGED
|
@@ -25,7 +25,9 @@ interface BuiltTx {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export interface BlobUploadFlow {
|
|
28
|
-
encode()
|
|
28
|
+
// The SDK `encode()` returns a `WriteBlobStepEncoded`; we use its deterministic `blobId` to look
|
|
29
|
+
// up an existing on-chain registration for the same content (resume discovery).
|
|
30
|
+
encode(): Promise<{ blobId: string }>
|
|
29
31
|
register(opts: { owner: string; epochs: number; deletable: boolean }): BuiltTx
|
|
30
32
|
// `digest` = the register transaction digest. When resuming without a prior `register()` call on
|
|
31
33
|
// this flow instance, the SDK accepts the digest + `deletable` to upload against the already
|
|
@@ -65,6 +67,13 @@ export interface RunBlobUploadDeps {
|
|
|
65
67
|
* WAL/gas), even across a reload once the same file is re-selected. Omit for a fresh upload.
|
|
66
68
|
*/
|
|
67
69
|
resumeRegisterDigest?: string
|
|
70
|
+
/**
|
|
71
|
+
* On-chain resume fallback: called with the encoded `blobId` when no `resumeRegisterDigest` was
|
|
72
|
+
* supplied. Returns the register digest of an already-registered, uncertified on-chain blob for
|
|
73
|
+
* this content (or `undefined`). This makes resume robust to a lost local pointer (cache-clear /
|
|
74
|
+
* new device) — the registration is discovered on-chain rather than remembered client-side.
|
|
75
|
+
*/
|
|
76
|
+
discoverRegisterDigest?: (blobId: string) => Promise<string | undefined>
|
|
68
77
|
/**
|
|
69
78
|
* Called with the register transaction digest immediately after a fresh register succeeds, so the
|
|
70
79
|
* caller can PERSIST it (e.g. to localStorage) and resume the upload after a failure/reload
|
|
@@ -97,16 +106,19 @@ export async function runBlobUpload(deps: RunBlobUploadDeps): Promise<UploadResu
|
|
|
97
106
|
const flow = createBlobUploadFlow(client, deps.bytes)
|
|
98
107
|
|
|
99
108
|
// Encoding is deterministic from the content and costs no gas, so it always runs — including on a
|
|
100
|
-
// resume, where it re-derives the slivers for the re-selected file
|
|
109
|
+
// resume, where it re-derives the slivers for the re-selected file (and yields the blobId used
|
|
110
|
+
// for on-chain resume discovery).
|
|
101
111
|
deps.onStatus('Encoding…')
|
|
102
|
-
await flow.encode()
|
|
112
|
+
const { blobId } = await flow.encode()
|
|
113
|
+
|
|
114
|
+
// Resolve a resume point: a caller-provided digest (localStorage fast path) or, failing that, an
|
|
115
|
+
// on-chain lookup by blobId (robust to a lost local pointer). Either skips the register tx.
|
|
116
|
+
let registerDigest = deps.resumeRegisterDigest
|
|
117
|
+
if (registerDigest === undefined && deps.discoverRegisterDigest) {
|
|
118
|
+
registerDigest = (await deps.discoverRegisterDigest(blobId)) ?? undefined
|
|
119
|
+
}
|
|
103
120
|
|
|
104
|
-
|
|
105
|
-
if (deps.resumeRegisterDigest) {
|
|
106
|
-
// Resume: the blob was registered in a prior attempt. Skip the register transaction (no new
|
|
107
|
-
// WAL/gas) and upload against the existing on-chain registration.
|
|
108
|
-
registerDigest = deps.resumeRegisterDigest
|
|
109
|
-
} else {
|
|
121
|
+
if (registerDigest === undefined) {
|
|
110
122
|
deps.onStatus('Registering blob (approve in wallet)…')
|
|
111
123
|
const regTx = flow.register({ owner: deps.address, epochs: deps.epochs, deletable: false })
|
|
112
124
|
regTx.setSenderIfNotSet(deps.address)
|