@meddleware/walrus-storage-ui 0.1.64 → 0.1.66
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 +22 -12
- package/src/App.vue +20 -41
- package/src/components/BlobGroupRows.vue +133 -0
- package/src/components/ExtendControl.vue +102 -0
- package/src/components/MyBlobs.vue +108 -345
- package/src/components/UploadResultCard.vue +35 -0
- package/src/components/WalrusStorageView.vue +94 -310
- package/src/composables/useExtendQuotes.ts +3 -3
- package/src/composables/useOwnedBlobs.ts +8 -9
- package/src/composables/useStorageUpload.ts +111 -0
- package/src/config.ts +3 -16
- package/src/e2e-data.ts +11 -0
- package/src/e2e.ts +46 -0
- package/src/main.ts +3 -1
- package/src/wallet.ts +5 -44
- package/src/walrus.ts +29 -0
- package/vite.config.ts +13 -7
package/src/config.ts
CHANGED
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
// purchase price and payees are read from the chain, never configured.
|
|
7
7
|
import { computed } from 'vue'
|
|
8
8
|
import type { WalrusNetwork, RelayGateConfig } from '@meddleware/walrus-storage-kit'
|
|
9
|
-
import { relayGateConfig } from '@meddleware/walrus-storage-kit'
|
|
9
|
+
import { PUBLIC_WALRUS_RELAY_HOSTS, relayGateConfig } from '@meddleware/walrus-storage-kit'
|
|
10
10
|
import { useNetwork } from '@meddleware/wallet-adapter'
|
|
11
11
|
import { withRuntimeEnv } from '@meddleware/ui'
|
|
12
|
+
import { errorMessage } from '@meddleware/web-utils'
|
|
12
13
|
|
|
13
14
|
/** Build-time env bag. Injectable on the env-reading helpers below purely so unit tests can exercise
|
|
14
15
|
* each branch without depending on Vite's (frozen) `import.meta.env` inlining. */
|
|
@@ -25,12 +26,6 @@ export const walrusNetwork = computed<WalrusNetwork | null>(() =>
|
|
|
25
26
|
network.value === 'testnet' || network.value === 'mainnet' ? network.value : null,
|
|
26
27
|
)
|
|
27
28
|
|
|
28
|
-
/** Public Mysten relays — the fallback when no operator relay is configured. */
|
|
29
|
-
export const PUBLIC_WALRUS_RELAY_HOSTS: Record<WalrusNetwork, string> = {
|
|
30
|
-
testnet: 'https://upload-relay.testnet.walrus.space',
|
|
31
|
-
mainnet: 'https://upload-relay.mainnet.walrus.space',
|
|
32
|
-
}
|
|
33
|
-
|
|
34
29
|
/** Operator relay host per network (env → public fallback). */
|
|
35
30
|
export const OPERATOR_RELAY_HOSTS: Record<WalrusNetwork, string> = {
|
|
36
31
|
testnet: env.VITE_WALRUS_RELAY_TESTNET || PUBLIC_WALRUS_RELAY_HOSTS.testnet,
|
|
@@ -42,14 +37,6 @@ export function relayHosts(network: WalrusNetwork): { operator: string; public:
|
|
|
42
37
|
return { operator: OPERATOR_RELAY_HOSTS[network], public: PUBLIC_WALRUS_RELAY_HOSTS[network] }
|
|
43
38
|
}
|
|
44
39
|
|
|
45
|
-
/**
|
|
46
|
-
* Walruscan explorer URL for a blob. A Walrus blob id is not a Sui object, so it links to the
|
|
47
|
-
* Walrus-native explorer rather than a Sui explorer (SuiVision handles Sui entities elsewhere).
|
|
48
|
-
*/
|
|
49
|
-
export function walruscanBlobUrl(network: WalrusNetwork, blobId: string): string {
|
|
50
|
-
return `https://walruscan.com/${network}/blob/${blobId}`
|
|
51
|
-
}
|
|
52
|
-
|
|
53
40
|
/**
|
|
54
41
|
* Default relay tip ceiling (MIST) when `VITE_UPLOAD_RELAY_MAX_TIP_MIST` is unset: 0.005 SUI, the
|
|
55
42
|
* workspace-wide ceiling (~8× the operator relay's worst-case tip at the 100 MiB edge cap).
|
|
@@ -83,7 +70,7 @@ export function accessGate(network: WalrusNetwork, envSource: EnvSource = env):
|
|
|
83
70
|
soulbound: envSource[`VITE_ACCESS_GATE_SOULBOUND_${NET}`] === 'true',
|
|
84
71
|
})
|
|
85
72
|
} catch (e) {
|
|
86
|
-
console.warn(`[walrus-storage-ui] access gate for ${network} disabled: ${
|
|
73
|
+
console.warn(`[walrus-storage-ui] access gate for ${network} disabled: ${errorMessage(e)}`)
|
|
87
74
|
return null
|
|
88
75
|
}
|
|
89
76
|
}
|
package/src/e2e-data.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// What the end-to-end build's prepared blob list is made of, apart from the code that installs it (`e2e.ts` pulls in the mock wallet, which
|
|
2
|
+
// a Playwright spec, running in Node, cannot load): the specs import these to look for the right rows.
|
|
3
|
+
export const E2E_EPOCH = 560
|
|
4
|
+
|
|
5
|
+
/** Blob ids as the aggregator spells them (URL-safe base64, 43 characters). */
|
|
6
|
+
export const E2E_BLOBS = {
|
|
7
|
+
duplicated: 'Qm9yZWRPZkR1cGxpY2F0ZXNfQm9yZWRPZkR1cGxpY2F0ZXM',
|
|
8
|
+
steady: 'U3RlYWR5U3RhdGVTdG9yYWdlX1N0ZWFkeVN0YXRlU3RvcmE',
|
|
9
|
+
pending: 'UGVuZGluZ0NlcnRpZmljYXRpb25fUGVuZGluZ0NlcnRpZmk',
|
|
10
|
+
maxed: 'TWF4aW11bUxpZmV0aW1lX01heGltdW1MaWZldGltZV9NYXg',
|
|
11
|
+
} as const
|
package/src/e2e.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// End-to-end build only (`vite build --mode e2e`; main.ts loads this when MODE is `e2e` and VITE_E2E is `1`, both static, so every other
|
|
2
|
+
// build drops it). A mock wallet is registered like an extension, and the owned-blob list is filled in advance (a session cache the app
|
|
3
|
+
// reads through `useOwnedBlobs`), so the connected screens can be shown and checked without a network or a funded key. The upload itself
|
|
4
|
+
// needs a relay and storage nodes: it is exercised by the localnet spec, not here.
|
|
5
|
+
import { MOCK_ADDRESS, installE2eWallet } from '@meddleware/wallet-adapter/testing'
|
|
6
|
+
import { browserStorage, pendingCertifyKey, savePendingCertify } from '@meddleware/walrus-storage-client/flow'
|
|
7
|
+
import type { OwnedBlob } from '@meddleware/walrus-storage-client'
|
|
8
|
+
import { useOwnedBlobs } from './composables/useOwnedBlobs.js'
|
|
9
|
+
import { E2E_BLOBS, E2E_EPOCH } from './e2e-data.js'
|
|
10
|
+
|
|
11
|
+
const hex = (byte: string) => `0x${byte.repeat(32)}`
|
|
12
|
+
|
|
13
|
+
const blob = (n: number, blobId: string, over: Partial<OwnedBlob>): OwnedBlob => ({
|
|
14
|
+
objectId: hex(n.toString(16).padStart(2, '0')),
|
|
15
|
+
blobId,
|
|
16
|
+
size: 48 * 1024,
|
|
17
|
+
startEpoch: 540,
|
|
18
|
+
endEpoch: 600,
|
|
19
|
+
certified: true,
|
|
20
|
+
deletable: false,
|
|
21
|
+
...over,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
export function installE2e(): void {
|
|
25
|
+
installE2eWallet()
|
|
26
|
+
const owned = [
|
|
27
|
+
// Re-uploads of the same content collapse into one row with two copies; the nearer one is about to expire.
|
|
28
|
+
blob(1, E2E_BLOBS.duplicated, { endEpoch: 565 }),
|
|
29
|
+
blob(2, E2E_BLOBS.duplicated, { endEpoch: 566, size: 48 * 1024 }),
|
|
30
|
+
blob(3, E2E_BLOBS.steady, { endEpoch: 600, size: 3 * 1024 * 1024 }),
|
|
31
|
+
// Uploaded but not certified, with the certificate still stored: Certify is offered.
|
|
32
|
+
blob(4, E2E_BLOBS.pending, { endEpoch: 580, certified: false, deletable: true }),
|
|
33
|
+
// Already as far ahead as the chain allows: nothing to extend.
|
|
34
|
+
blob(5, E2E_BLOBS.maxed, { endEpoch: 613, size: 512 }),
|
|
35
|
+
]
|
|
36
|
+
savePendingCertify(browserStorage(), pendingCertifyKey('testnet', MOCK_ADDRESS), {
|
|
37
|
+
blobId: E2E_BLOBS.pending,
|
|
38
|
+
blobObjectId: owned[3].objectId,
|
|
39
|
+
certificate: 'ZTJlLWNlcnRpZmljYXRl',
|
|
40
|
+
deletable: true,
|
|
41
|
+
})
|
|
42
|
+
const state = useOwnedBlobs()
|
|
43
|
+
state.blobs.value = owned.sort((a, b) => a.endEpoch - b.endEpoch)
|
|
44
|
+
state.currentEpoch.value = E2E_EPOCH
|
|
45
|
+
state.loadedFor.value = `testnet|${MOCK_ADDRESS}`
|
|
46
|
+
}
|
package/src/main.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import '@meddleware/design-tokens/tokens.css'
|
|
2
2
|
import '@meddleware/design-tokens/seasons.css'
|
|
3
|
-
import '@meddleware/ui/
|
|
3
|
+
import '@meddleware/ui/reset.css'
|
|
4
4
|
import { createApp } from 'vue'
|
|
5
5
|
import { loadRuntimeConfig, useSeason } from '@meddleware/ui'
|
|
6
6
|
import { useNetwork } from '@meddleware/wallet-adapter'
|
|
@@ -15,6 +15,8 @@ useSeason()
|
|
|
15
15
|
// Machine-zone endpoints (the indexer, the Walrus relays) come from /runtime-config.json when the deployment
|
|
16
16
|
// serves one (ADR-0005); load it before any module that reads them is imported.
|
|
17
17
|
void loadRuntimeConfig().then(async () => {
|
|
18
|
+
// End-to-end builds only: a mock wallet and a stub chain (`src/e2e.ts`). Both conditions are static, so no other build contains it.
|
|
19
|
+
if (import.meta.env.MODE === 'e2e' && import.meta.env.VITE_E2E === '1') (await import('./e2e.js')).installE2e()
|
|
18
20
|
const { default: App } = await import('./App.vue')
|
|
19
21
|
createApp(App).mount('#app')
|
|
20
22
|
})
|
package/src/wallet.ts
CHANGED
|
@@ -1,47 +1,8 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
// module singleton, the wallet connection is shared with any other tool view rendered
|
|
6
|
-
// in the same window (e.g. the dashboard).
|
|
7
|
-
import {
|
|
8
|
-
useWallet as useWalletBase,
|
|
9
|
-
getSuiClient as getSuiClientBase,
|
|
10
|
-
buildExecutor as buildExecutorBase,
|
|
11
|
-
useNetwork,
|
|
12
|
-
} from '@meddleware/wallet-adapter'
|
|
1
|
+
// walrus-storage-ui's wallet: the shared @meddleware/wallet-adapter singleton, bound to the selected network. Walrus needs
|
|
2
|
+
// `sui:signTransaction` (register, certify, extend), so only wallets that can sign transactions are offered. Because the adapter is a
|
|
3
|
+
// module singleton, the connection is shared with any other tool view in the same window (e.g. the dashboard).
|
|
4
|
+
import { createToolWallet } from '@meddleware/wallet-adapter'
|
|
13
5
|
import type { Executor } from '@meddleware/wallet-adapter'
|
|
14
6
|
|
|
15
7
|
export type { Executor }
|
|
16
|
-
|
|
17
|
-
const { network, rpcUrl } = useNetwork()
|
|
18
|
-
|
|
19
|
-
/** Memoised Sui gRPC client for the currently selected network. */
|
|
20
|
-
export function getSuiClient() {
|
|
21
|
-
return getSuiClientBase(network.value, rpcUrl.value)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Build a transaction executor bound to the connected wallet and current network RPC URL. */
|
|
25
|
-
export function buildExecutor(): Promise<Executor> {
|
|
26
|
-
return buildExecutorBase(network.value, rpcUrl.value)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Wallet composable bound to walrus-storage-ui's network config. Delegates to the shared adapter
|
|
31
|
-
* singleton; walrus needs `sui:signTransaction` (upload certify) so it's requested for discovery.
|
|
32
|
-
*/
|
|
33
|
-
export function useWallet() {
|
|
34
|
-
const base = useWalletBase({ requiredFeatures: ['sui:signTransaction'] })
|
|
35
|
-
return {
|
|
36
|
-
wallets: base.wallets,
|
|
37
|
-
currentWallet: base.currentWallet,
|
|
38
|
-
account: base.account,
|
|
39
|
-
connecting: base.connecting,
|
|
40
|
-
error: base.error,
|
|
41
|
-
connect: base.connect,
|
|
42
|
-
disconnect: base.disconnect,
|
|
43
|
-
signPersonalMessage: base.signPersonalMessage,
|
|
44
|
-
getSuiClient,
|
|
45
|
-
buildExecutor,
|
|
46
|
-
}
|
|
47
|
-
}
|
|
8
|
+
export const { useWallet, getSuiClient, buildExecutor } = createToolWallet({ requiredFeatures: ['sui:signTransaction'] })
|
package/src/walrus.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// The Walrus client, loaded and created on first use. `@mysten/walrus` and its wasm are heavy, so nothing here is imported at module
|
|
2
|
+
// top level: the first caller pays for the dynamic import, everyone after shares it. One client per network (a client holds no
|
|
3
|
+
// wallet state), so the upload, the owned-blob list and the extend quotes all read through the same one.
|
|
4
|
+
import walrusWasmUrl from '@mysten/walrus-wasm/web/walrus_wasm_bg.wasm?url'
|
|
5
|
+
import type { WalrusNetwork } from '@meddleware/walrus-storage-kit'
|
|
6
|
+
|
|
7
|
+
type WalrusLib = typeof import('@meddleware/walrus-storage-client')
|
|
8
|
+
type WalrusClient = ReturnType<WalrusLib['createWalrusClient']>
|
|
9
|
+
|
|
10
|
+
let lib: Promise<WalrusLib> | null = null
|
|
11
|
+
const clients = new Map<WalrusNetwork, WalrusClient>()
|
|
12
|
+
|
|
13
|
+
/** The `@meddleware/walrus-storage-client` root, imported once. */
|
|
14
|
+
export function walrusLib(): Promise<WalrusLib> {
|
|
15
|
+
return (lib ??= import('@meddleware/walrus-storage-client'))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The shared Walrus client for `network`. */
|
|
19
|
+
export async function walrusClient(network: WalrusNetwork): Promise<WalrusClient> {
|
|
20
|
+
const { createWalrusClient } = await walrusLib()
|
|
21
|
+
let client = clients.get(network)
|
|
22
|
+
if (!client) {
|
|
23
|
+
client = createWalrusClient({ network, wasmUrl: walrusWasmUrl })
|
|
24
|
+
clients.set(network, client)
|
|
25
|
+
}
|
|
26
|
+
return client
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { walrusWasmUrl }
|
package/vite.config.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { defineConfig } from 'vite'
|
|
2
2
|
import vue from '@vitejs/plugin-vue'
|
|
3
|
+
import { assertE2eOnlyInE2eMode, themeInit, viteTarget } from '@meddleware/frontend-config/vite'
|
|
3
4
|
|
|
4
5
|
// design-tokens and ui resolve from node_modules (published packages).
|
|
5
6
|
// @mysten/walrus is excluded from dep-optimization so its wasm `?url` import resolves.
|
|
6
|
-
export default defineConfig({
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
export default defineConfig(({ mode }) => {
|
|
8
|
+
// The end-to-end build (`--mode e2e`) includes a mock wallet; nothing else may.
|
|
9
|
+
assertE2eOnlyInE2eMode(mode)
|
|
10
|
+
return {
|
|
11
|
+
build: { target: viteTarget() },
|
|
12
|
+
plugins: [vue(), themeInit({ default: 'dark' })],
|
|
13
|
+
optimizeDeps: {
|
|
14
|
+
// @meddleware/wallet-adapter ships TS + .vue source and holds the shared wallet singleton; if it
|
|
15
|
+
// were pre-bundled, its .vue files (served raw) would load a second copy of the singleton in dev.
|
|
16
|
+
exclude: ['@mysten/walrus', '@mysten/walrus-wasm', '@meddleware/wallet-adapter'],
|
|
17
|
+
},
|
|
18
|
+
}
|
|
13
19
|
})
|