@blu1606/create-walrus-app 1.0.0 → 2.0.0

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.
Files changed (101) hide show
  1. package/dist/generator/file-ops.d.ts +8 -0
  2. package/dist/generator/file-ops.js +20 -0
  3. package/dist/generator/index.js +37 -22
  4. package/dist/generator/layers.d.ts +15 -2
  5. package/dist/generator/layers.js +38 -47
  6. package/dist/generator/types.d.ts +9 -1
  7. package/dist/index.js +1 -2
  8. package/dist/post-install/git.d.ts +8 -0
  9. package/dist/post-install/git.js +2 -0
  10. package/dist/post-install/index.d.ts +0 -1
  11. package/dist/post-install/index.js +2 -15
  12. package/dist/post-install/messages.js +1 -1
  13. package/package.json +3 -3
  14. package/{templates/base → presets/react-mysten-gallery}/.env.example +31 -31
  15. package/presets/react-mysten-gallery/.gitkeep +4 -0
  16. package/{templates/gallery → presets/react-mysten-gallery}/README.md +25 -22
  17. package/presets/react-mysten-gallery/package.json +34 -0
  18. package/presets/react-mysten-gallery/src/App.tsx +23 -0
  19. package/presets/react-mysten-gallery/src/components/features/file-card.tsx +89 -0
  20. package/{templates/gallery/src/components/GalleryGrid.tsx → presets/react-mysten-gallery/src/components/features/gallery-grid.tsx} +5 -5
  21. package/presets/react-mysten-gallery/src/components/features/upload-modal.tsx +69 -0
  22. package/{templates/react/src/components/WalletConnect.tsx → presets/react-mysten-gallery/src/components/features/wallet-connect.tsx} +1 -1
  23. package/presets/react-mysten-gallery/src/components/layout/app-layout.tsx +21 -0
  24. package/{templates/react/src/hooks/useStorage.ts → presets/react-mysten-gallery/src/hooks/use-download.ts} +2 -18
  25. package/presets/react-mysten-gallery/src/hooks/use-upload.ts +49 -0
  26. package/{templates/react/src/hooks/useWallet.ts → presets/react-mysten-gallery/src/hooks/use-wallet.ts} +2 -7
  27. package/presets/react-mysten-gallery/src/index.css +384 -0
  28. package/presets/react-mysten-gallery/src/index.ts +17 -0
  29. package/presets/react-mysten-gallery/src/lib/walrus/adapter.ts +197 -0
  30. package/presets/react-mysten-gallery/src/lib/walrus/client.ts +87 -0
  31. package/presets/react-mysten-gallery/src/lib/walrus/index.ts +4 -0
  32. package/presets/react-mysten-gallery/src/lib/walrus/types.ts +101 -0
  33. package/{templates/react → presets/react-mysten-gallery}/src/main.tsx +0 -1
  34. package/{templates/react → presets/react-mysten-gallery}/src/providers/WalletProvider.tsx +16 -1
  35. package/{templates/base → presets/react-mysten-gallery}/src/utils/env.ts +41 -41
  36. package/{templates/gallery → presets/react-mysten-gallery}/src/utils/index-manager.ts +2 -2
  37. package/presets/react-mysten-gallery/src/utils/mime-type.ts +97 -0
  38. package/presets/react-mysten-gallery/src/utils/preview-generator.ts +134 -0
  39. package/{templates/react → presets/react-mysten-gallery}/tsconfig.json +20 -8
  40. package/presets/react-mysten-simple-upload/.env.example +31 -0
  41. package/presets/react-mysten-simple-upload/.gitkeep +4 -0
  42. package/presets/react-mysten-simple-upload/index.html +13 -0
  43. package/{templates/react → presets/react-mysten-simple-upload}/package.json +13 -11
  44. package/presets/react-mysten-simple-upload/src/App.tsx +27 -0
  45. package/presets/react-mysten-simple-upload/src/components/features/file-preview.tsx +73 -0
  46. package/{templates/simple-upload/src/components/UploadForm.tsx → presets/react-mysten-simple-upload/src/components/features/upload-form.tsx} +15 -5
  47. package/presets/react-mysten-simple-upload/src/components/features/wallet-connect.tsx +21 -0
  48. package/presets/react-mysten-simple-upload/src/components/layout/app-layout.tsx +21 -0
  49. package/presets/react-mysten-simple-upload/src/hooks/use-download.ts +24 -0
  50. package/presets/react-mysten-simple-upload/src/hooks/use-upload.ts +49 -0
  51. package/presets/react-mysten-simple-upload/src/hooks/use-wallet.ts +11 -0
  52. package/presets/react-mysten-simple-upload/src/index.css +252 -0
  53. package/presets/react-mysten-simple-upload/src/index.ts +16 -0
  54. package/presets/react-mysten-simple-upload/src/lib/walrus/adapter.ts +197 -0
  55. package/presets/react-mysten-simple-upload/src/lib/walrus/client.ts +87 -0
  56. package/presets/react-mysten-simple-upload/src/lib/walrus/index.ts +4 -0
  57. package/{templates/base/src/adapters/storage.ts → presets/react-mysten-simple-upload/src/lib/walrus/types.ts} +83 -58
  58. package/presets/react-mysten-simple-upload/src/main.tsx +16 -0
  59. package/presets/react-mysten-simple-upload/src/providers/QueryProvider.tsx +18 -0
  60. package/presets/react-mysten-simple-upload/src/providers/WalletProvider.tsx +52 -0
  61. package/presets/react-mysten-simple-upload/src/utils/env.ts +41 -0
  62. package/presets/react-mysten-simple-upload/src/utils/mime-type.ts +97 -0
  63. package/presets/react-mysten-simple-upload/tsconfig.json +39 -0
  64. package/presets/react-mysten-simple-upload/tsconfig.node.json +10 -0
  65. package/presets/react-mysten-simple-upload/vite.config.ts +19 -0
  66. package/templates/base/README.md +0 -54
  67. package/templates/base/package.json +0 -19
  68. package/templates/base/src/types/index.ts +0 -9
  69. package/templates/base/src/types/walrus.ts +0 -22
  70. package/templates/base/src/utils/format.ts +0 -29
  71. package/templates/base/tsconfig.json +0 -19
  72. package/templates/gallery/package.json +0 -6
  73. package/templates/gallery/src/App.tsx +0 -21
  74. package/templates/gallery/src/components/FileCard.tsx +0 -27
  75. package/templates/gallery/src/components/UploadModal.tsx +0 -45
  76. package/templates/gallery/src/styles.css +0 -58
  77. package/templates/gallery/src/types/gallery.ts +0 -13
  78. package/templates/react/.eslintrc.json +0 -26
  79. package/templates/react/README.md +0 -80
  80. package/templates/react/src/App.tsx +0 -14
  81. package/templates/react/src/components/Layout.tsx +0 -21
  82. package/templates/react/src/dapp-kit.css +0 -1
  83. package/templates/react/src/index.css +0 -50
  84. package/templates/react/src/index.ts +0 -10
  85. package/templates/sdk-mysten/README.md +0 -65
  86. package/templates/sdk-mysten/package.json +0 -14
  87. package/templates/sdk-mysten/src/adapter.ts +0 -80
  88. package/templates/sdk-mysten/src/client.ts +0 -45
  89. package/templates/sdk-mysten/src/config.ts +0 -33
  90. package/templates/sdk-mysten/src/index.ts +0 -11
  91. package/templates/sdk-mysten/src/types.ts +0 -19
  92. package/templates/sdk-mysten/test/adapter.test.ts +0 -20
  93. package/templates/simple-upload/package.json +0 -6
  94. package/templates/simple-upload/src/App.tsx +0 -27
  95. package/templates/simple-upload/src/components/FilePreview.tsx +0 -40
  96. package/templates/simple-upload/src/styles.css +0 -33
  97. /package/{templates/react → presets/react-mysten-gallery}/index.html +0 -0
  98. /package/{templates/react → presets/react-mysten-gallery}/src/providers/QueryProvider.tsx +0 -0
  99. /package/{templates/react → presets/react-mysten-gallery}/tsconfig.node.json +0 -0
  100. /package/{templates/react → presets/react-mysten-gallery}/vite.config.ts +0 -0
  101. /package/{templates/simple-upload → presets/react-mysten-simple-upload}/README.md +0 -0
@@ -0,0 +1,134 @@
1
+ import { storageAdapter } from '../lib/walrus/adapter.js';
2
+ import { loadEnv } from './env.js';
3
+
4
+ // Walrus aggregator URLs for direct HTTP access
5
+ const AGGREGATOR_URLS = {
6
+ testnet: 'https://aggregator.walrus-testnet.walrus.space',
7
+ mainnet: 'https://aggregator.walrus.space',
8
+ };
9
+
10
+ /**
11
+ * Get the aggregator URL based on current network configuration
12
+ */
13
+ function getAggregatorUrl(): string {
14
+ const env = loadEnv();
15
+ // Use environment override if available
16
+ if (env.walrusAggregator) {
17
+ return env.walrusAggregator;
18
+ }
19
+ // Default to testnet
20
+ const network = env.walrusNetwork === 'mainnet' ? 'mainnet' : 'testnet';
21
+ return AGGREGATOR_URLS[network];
22
+ }
23
+
24
+ /**
25
+ * Generate a preview URL by fetching directly from Walrus aggregator HTTP API
26
+ * This is faster than using the SDK as it makes a direct HTTP request
27
+ *
28
+ * @param blobId - The Walrus blob ID
29
+ * @param contentType - MIME type of the content (e.g., 'image/jpeg')
30
+ * @returns Object URL that can be used in <img> src
31
+ */
32
+ async function generatePreviewUrlDirect(
33
+ blobId: string,
34
+ contentType: string
35
+ ): Promise<string> {
36
+ const aggregatorUrl = getAggregatorUrl();
37
+
38
+ // Fetch blob data directly from aggregator
39
+ // Using the /v1/blobs/{blobId} endpoint which returns the full blob
40
+ const response = await fetch(`${aggregatorUrl}/v1/blobs/${blobId}`, {
41
+ headers: {
42
+ 'Accept': 'application/octet-stream',
43
+ },
44
+ });
45
+
46
+ if (!response.ok) {
47
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
48
+ }
49
+
50
+ // Get blob data and create object URL
51
+ const blobData = await response.blob();
52
+
53
+ // Create a new blob with correct content type
54
+ const typedBlob = new Blob([blobData], { type: contentType });
55
+ const objectUrl = URL.createObjectURL(typedBlob);
56
+
57
+ return objectUrl;
58
+ }
59
+
60
+ /**
61
+ * Fallback: Generate preview using SDK download method
62
+ */
63
+ async function generatePreviewUrlFallback(
64
+ blobId: string,
65
+ contentType: string
66
+ ): Promise<string> {
67
+ // Download blob data as bytes via SDK
68
+ const bytes = await storageAdapter.download(blobId);
69
+
70
+ if (!(bytes instanceof Uint8Array)) {
71
+ throw new Error('Expected Uint8Array from download');
72
+ }
73
+
74
+ // Create a Blob from the bytes with correct content type
75
+ const buffer = new ArrayBuffer(bytes.length);
76
+ const view = new Uint8Array(buffer);
77
+ view.set(bytes);
78
+ const blob = new Blob([buffer], { type: contentType });
79
+
80
+ // Create and return object URL
81
+ const objectUrl = URL.createObjectURL(blob);
82
+
83
+ return objectUrl;
84
+ }
85
+
86
+ /**
87
+ * Generate a preview URL from blob ID
88
+ * Tries direct HTTP fetch first (faster), falls back to SDK if it fails
89
+ *
90
+ * @param blobId - The Walrus blob ID
91
+ * @param contentType - MIME type of the content (e.g., 'image/jpeg')
92
+ * @returns Object URL that can be used in <img> src
93
+ */
94
+ export async function generatePreviewUrl(
95
+ blobId: string,
96
+ contentType: string
97
+ ): Promise<string> {
98
+ try {
99
+ // Try direct HTTP fetch first (faster)
100
+ console.log(`[Preview] Attempting direct fetch for ${blobId.slice(0, 16)}...`);
101
+ const url = await generatePreviewUrlDirect(blobId, contentType);
102
+ console.log(`[Preview] Direct fetch successful`);
103
+ return url;
104
+ } catch (directError) {
105
+ console.warn(`[Preview] Direct fetch failed, falling back to SDK:`, directError);
106
+
107
+ try {
108
+ // Fallback to SDK method
109
+ const url = await generatePreviewUrlFallback(blobId, contentType);
110
+ console.log(`[Preview] SDK fallback successful`);
111
+ return url;
112
+ } catch (fallbackError) {
113
+ console.error(`[Preview] Both methods failed for ${blobId}:`, fallbackError);
114
+ throw fallbackError;
115
+ }
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Check if content type is an image that can be displayed
121
+ */
122
+ export function isImageType(contentType: string): boolean {
123
+ return contentType.startsWith('image/');
124
+ }
125
+
126
+ /**
127
+ * Revoke object URL to free memory
128
+ * Call this when the component unmounts or the URL is no longer needed
129
+ */
130
+ export function revokePreviewUrl(url: string): void {
131
+ if (url.startsWith('blob:')) {
132
+ URL.revokeObjectURL(url);
133
+ }
134
+ }
@@ -2,26 +2,38 @@
2
2
  "compilerOptions": {
3
3
  "target": "ES2020",
4
4
  "useDefineForClassFields": true,
5
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
5
+ "lib": [
6
+ "ES2020",
7
+ "DOM",
8
+ "DOM.Iterable"
9
+ ],
6
10
  "module": "ESNext",
7
11
  "skipLibCheck": true,
8
-
9
12
  "moduleResolution": "bundler",
10
13
  "allowImportingTsExtensions": true,
11
14
  "resolveJsonModule": true,
12
15
  "isolatedModules": true,
13
16
  "noEmit": true,
14
17
  "jsx": "react-jsx",
15
-
18
+ "types": [
19
+ "vite/client"
20
+ ],
16
21
  "strict": true,
17
22
  "noUnusedLocals": true,
18
23
  "noUnusedParameters": true,
19
24
  "noFallthroughCasesInSwitch": true,
20
-
21
25
  "paths": {
22
- "@/*": ["./src/*"]
26
+ "@/*": [
27
+ "./src/*"
28
+ ]
23
29
  }
24
30
  },
25
- "include": ["src"],
26
- "references": [{ "path": "./tsconfig.node.json" }]
27
- }
31
+ "include": [
32
+ "src"
33
+ ],
34
+ "references": [
35
+ {
36
+ "path": "./tsconfig.node.json"
37
+ }
38
+ ]
39
+ }
@@ -0,0 +1,31 @@
1
+ ## ==============================================
2
+ ## Walrus Application - Environment Configuration
3
+ ## ==============================================
4
+
5
+ ## WALRUS NETWORK SETTINGS
6
+ ## Network: testnet | mainnet | devnet
7
+ VITE_WALRUS_NETWORK=testnet
8
+
9
+ ## Walrus Aggregator URL (for downloads)
10
+ VITE_WALRUS_AGGREGATOR=https://aggregator.walrus-testnet.walrus.space
11
+
12
+ ## Walrus Publisher URL (for uploads)
13
+ VITE_WALRUS_PUBLISHER=https://publisher.walrus-testnet.walrus.space
14
+
15
+ ## SUI BLOCKCHAIN SETTINGS
16
+ ## Sui Network: testnet | mainnet | devnet
17
+ VITE_SUI_NETWORK=testnet
18
+
19
+ ## Sui RPC URL (for wallet interactions)
20
+ VITE_SUI_RPC=https://fullnode.testnet.sui.io:443
21
+
22
+ ## OPTIONAL FEATURES
23
+ ## Blockberry Analytics API Key (leave empty to disable)
24
+ VITE_BLOCKBERRY_KEY=
25
+
26
+ ## ==============================================
27
+ ## PREREQUISITES
28
+ ## ==============================================
29
+ ## 1. Install Sui Wallet browser extension
30
+ ## 2. Get testnet SUI from faucet: https://faucet.testnet.sui.io/
31
+ ## 3. Copy this file to .env and fill in any optional values
@@ -0,0 +1,4 @@
1
+ # Preset: react-mysten-simple-upload
2
+
3
+ Structure created based on 2025/2026 React standards.
4
+ Populate with code from layer merging or manual upload.
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Walrus App</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
@@ -2,31 +2,33 @@
2
2
  "name": "{{projectName}}",
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
+ "description": "Walrus application scaffolded with create-walrus-app",
5
6
  "type": "module",
6
7
  "scripts": {
7
- "dev": "vite",
8
8
  "build": "tsc && vite build",
9
- "preview": "vite preview",
9
+ "dev": "vite",
10
10
  "lint": "eslint . --ext .ts,.tsx",
11
+ "preview": "vite preview",
11
12
  "type-check": "tsc --noEmit"
12
13
  },
13
14
  "dependencies": {
14
- "react": "^18.2.0",
15
- "react-dom": "^18.2.0",
16
- "@tanstack/react-query": "^5.17.0",
17
15
  "@mysten/dapp-kit": "^0.14.0",
18
- "@mysten/sui": "^1.10.0"
16
+ "@mysten/sui": "^1.10.0",
17
+ "@mysten/walrus": "^0.9.0",
18
+ "@tanstack/react-query": "^5.17.0",
19
+ "react": "^18.2.0",
20
+ "react-dom": "^18.2.0"
19
21
  },
20
22
  "devDependencies": {
21
23
  "@types/react": "^18.2.48",
22
24
  "@types/react-dom": "^18.2.18",
23
- "@vitejs/plugin-react": "^4.2.1",
24
- "vite": "^5.0.11",
25
- "typescript": "^5.3.3",
26
- "eslint": "^8.56.0",
27
25
  "@typescript-eslint/eslint-plugin": "^6.19.0",
28
26
  "@typescript-eslint/parser": "^6.19.0",
27
+ "@vitejs/plugin-react": "^4.2.1",
28
+ "eslint": "^8.56.0",
29
29
  "eslint-plugin-react": "^7.33.2",
30
- "eslint-plugin-react-hooks": "^4.6.0"
30
+ "eslint-plugin-react-hooks": "^4.6.0",
31
+ "typescript": "^5.3.3",
32
+ "vite": "^5.0.11"
31
33
  }
32
34
  }
@@ -0,0 +1,27 @@
1
+ import { AppLayout } from './components/layout/app-layout.js';
2
+ import { UploadForm } from './components/features/upload-form.js';
3
+ import { FilePreview } from './components/features/file-preview.js';
4
+ import './index.css';
5
+
6
+ function App() {
7
+ return (
8
+ <AppLayout>
9
+ <div className="simple-upload-app">
10
+ <h2><span className="text-accent">📤</span> Simple Upload</h2>
11
+ <p className="text-secondary">Upload a file to <span className="text-accent">Walrus</span> and download it by <span className="text-accent">Blob ID</span></p>
12
+
13
+ <section className="upload-section">
14
+ <h3>Upload File</h3>
15
+ <UploadForm />
16
+ </section>
17
+
18
+ <section className="download-section">
19
+ <h3>Download File</h3>
20
+ <FilePreview />
21
+ </section>
22
+ </div>
23
+ </AppLayout>
24
+ );
25
+ }
26
+
27
+ export default App;
@@ -0,0 +1,73 @@
1
+ import { useState } from 'react';
2
+ import { useDownload, useMetadata } from '../../hooks/use-download.js';
3
+
4
+ export function FilePreview() {
5
+ const [blobId, setBlobId] = useState('');
6
+ const { data, isLoading, error } = useDownload(blobId);
7
+ const { data: metadata } = useMetadata(blobId);
8
+
9
+ const handleDownload = () => {
10
+ if (!data) return;
11
+
12
+ // Use original filename from metadata if available
13
+ let filename = metadata?.fileName || `walrus-${blobId.slice(0, 8)}`;
14
+
15
+ // If metadata doesn't have filename, auto-detect from content type
16
+ if (!metadata?.fileName) {
17
+ const contentType = metadata?.contentType || 'application/octet-stream';
18
+
19
+ if (contentType.startsWith('image/')) {
20
+ filename += `.${contentType.split('/')[1]}`;
21
+ } else if (contentType.startsWith('text/')) {
22
+ filename += '.txt';
23
+ } else if (contentType.includes('json')) {
24
+ filename += '.json';
25
+ } else {
26
+ filename += '.bin';
27
+ }
28
+ }
29
+
30
+ const contentType = metadata?.contentType || 'application/octet-stream';
31
+ const blob = new Blob([data as Uint8Array], { type: contentType });
32
+ const url = URL.createObjectURL(blob);
33
+ const a = document.createElement('a');
34
+ a.href = url;
35
+ a.download = filename;
36
+ a.click();
37
+ URL.revokeObjectURL(url);
38
+ };
39
+
40
+ // Auto-detect if data is text or JSON for preview
41
+ const canPreview = data && (typeof data === 'string' || data instanceof Uint8Array);
42
+ const preview = canPreview && typeof data === 'string'
43
+ ? data.slice(0, 200) // Show first 200 chars for text
44
+ : null;
45
+
46
+ return (
47
+ <div className="file-preview">
48
+ <input
49
+ type="text"
50
+ placeholder="Enter Blob ID"
51
+ value={blobId}
52
+ onChange={(e) => setBlobId(e.target.value)}
53
+ />
54
+
55
+ {isLoading && <p className="text-secondary">Loading...</p>}
56
+ {error && <p className="error">Error: {error.message}</p>}
57
+
58
+ {data && (
59
+ <div className="preview-content icon-list">
60
+ <p className="text-success">✓ Blob found <span className="text-secondary">({data.byteLength || data.length} bytes)</span></p>
61
+ {metadata?.fileName && <p className="text-secondary">File: <span className="text-accent">{metadata.fileName}</span></p>}
62
+ {metadata?.contentType && <p className="text-secondary">Type: <span className="text-accent">{metadata.contentType}</span></p>}
63
+ {preview && (
64
+ <pre>
65
+ {preview}...
66
+ </pre>
67
+ )}
68
+ <button onClick={handleDownload}>Download File</button>
69
+ </div>
70
+ )}
71
+ </div>
72
+ );
73
+ }
@@ -1,9 +1,11 @@
1
1
  import { useState } from 'react';
2
- import { useUpload } from '../hooks/useStorage.js';
2
+ import { useUpload } from '../../hooks/use-upload.js';
3
+ import { useWallet } from '../../hooks/use-wallet.js';
3
4
 
4
5
  export function UploadForm() {
5
6
  const [selectedFile, setSelectedFile] = useState<File | null>(null);
6
7
  const upload = useUpload();
8
+ const { isConnected } = useWallet();
7
9
 
8
10
  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
9
11
  const file = e.target.files?.[0];
@@ -33,16 +35,24 @@ export function UploadForm() {
33
35
 
34
36
  {selectedFile && (
35
37
  <div className="file-info">
36
- <p>Selected: {selectedFile.name}</p>
37
- <p>Size: {(selectedFile.size / 1024).toFixed(2)} KB</p>
38
+ <p className="text-secondary">Selected: <span className="text-accent">{selectedFile.name}</span></p>
39
+ <p className="text-secondary">Size: <span className="text-accent">{(selectedFile.size / 1024).toFixed(2)} KB</span></p>
38
40
  </div>
39
41
  )}
40
42
 
43
+ {!isConnected && (
44
+ <p className="warning">⚠️ Please connect your wallet to upload files</p>
45
+ )}
46
+
41
47
  <button
42
48
  onClick={handleUpload}
43
- disabled={!selectedFile || upload.isPending}
49
+ disabled={!selectedFile || !isConnected || upload.isPending}
44
50
  >
45
- {upload.isPending ? 'Uploading...' : 'Upload to Walrus'}
51
+ {!isConnected
52
+ ? 'Connect Wallet First'
53
+ : upload.isPending
54
+ ? 'Uploading...'
55
+ : 'Upload to Walrus'}
46
56
  </button>
47
57
 
48
58
  {upload.isError && <p className="error">Error: {upload.error.message}</p>}
@@ -0,0 +1,21 @@
1
+ import { ConnectButton } from '@mysten/dapp-kit';
2
+ import { useWallet } from '../../hooks/use-wallet.js';
3
+
4
+ export function WalletConnect() {
5
+ const { isConnected, address } = useWallet();
6
+
7
+ return (
8
+ <div className="wallet-connect">
9
+ {isConnected ? (
10
+ <div className="wallet-info">
11
+ <span>
12
+ Connected: {address?.slice(0, 6)}...{address?.slice(-4)}
13
+ </span>
14
+ </div>
15
+ ) : (
16
+ <p>Please connect your wallet</p>
17
+ )}
18
+ <ConnectButton />
19
+ </div>
20
+ );
21
+ }
@@ -0,0 +1,21 @@
1
+ import { ReactNode } from 'react';
2
+ import { WalletConnect } from '../features/wallet-connect.js';
3
+
4
+ interface LayoutProps {
5
+ children: ReactNode;
6
+ }
7
+
8
+ export function AppLayout({ children }: LayoutProps) {
9
+ return (
10
+ <div className="app-layout">
11
+ <header className="app-header">
12
+ <h1><span className="text-secondary">🌊</span> <span className="text-accent">Walrus</span> App</h1>
13
+ <WalletConnect />
14
+ </header>
15
+ <main className="app-main">{children}</main>
16
+ <footer className="app-footer">
17
+ <p className="text-secondary">Powered by <span className="text-accent">Walrus</span> & <span style={{ color: 'var(--walrus-accent-blue)' }}>Sui</span></p>
18
+ </footer>
19
+ </div>
20
+ );
21
+ }
@@ -0,0 +1,24 @@
1
+ import { useQuery } from '@tanstack/react-query';
2
+ import { storageAdapter } from '../lib/walrus/index.js';
3
+
4
+ export function useDownload(blobId: string | null) {
5
+ return useQuery({
6
+ queryKey: ['blob', blobId],
7
+ queryFn: async () => {
8
+ if (!blobId) throw new Error('No blob ID provided');
9
+ return await storageAdapter.download(blobId);
10
+ },
11
+ enabled: !!blobId,
12
+ });
13
+ }
14
+
15
+ export function useMetadata(blobId: string | null) {
16
+ return useQuery({
17
+ queryKey: ['metadata', blobId],
18
+ queryFn: async () => {
19
+ if (!blobId) throw new Error('No blob ID provided');
20
+ return await storageAdapter.getMetadata(blobId);
21
+ },
22
+ enabled: !!blobId,
23
+ });
24
+ }
@@ -0,0 +1,49 @@
1
+ import { useMutation } from '@tanstack/react-query';
2
+ import { storageAdapter } from '../lib/walrus/index.js';
3
+ import type { UploadOptions, SignAndExecuteTransactionArgs } from '../lib/walrus/types.js';
4
+ import { useCurrentAccount, useSignAndExecuteTransaction, useSuiClient } from '@mysten/dapp-kit';
5
+
6
+ export function useUpload() {
7
+ const currentAccount = useCurrentAccount();
8
+ const { mutate: signAndExecute } = useSignAndExecuteTransaction();
9
+ const suiClient = useSuiClient();
10
+
11
+ return useMutation({
12
+ mutationFn: async ({
13
+ file,
14
+ options,
15
+ }: {
16
+ file: File;
17
+ options?: UploadOptions;
18
+ }) => {
19
+ if (!currentAccount) {
20
+ throw new Error('Wallet not connected. Please connect your wallet to upload files.');
21
+ }
22
+
23
+ // Wrap signAndExecute to return Promise
24
+ const signTransaction = (args: SignAndExecuteTransactionArgs) => {
25
+ return new Promise<{ digest: string }>((resolve, reject) => {
26
+ signAndExecute(
27
+ {
28
+ transaction: args.transaction,
29
+ },
30
+ {
31
+ onSuccess: (result) => resolve({ digest: result.digest }),
32
+ onError: (error) => reject(error),
33
+ }
34
+ );
35
+ });
36
+ };
37
+
38
+ const blobId = await storageAdapter.upload(file, {
39
+ ...options,
40
+ client: suiClient,
41
+ signer: {
42
+ address: currentAccount.address,
43
+ signAndExecuteTransaction: signTransaction,
44
+ },
45
+ });
46
+ return { blobId, file };
47
+ },
48
+ });
49
+ }
@@ -0,0 +1,11 @@
1
+ import { useCurrentAccount } from '@mysten/dapp-kit';
2
+
3
+ export function useWallet() {
4
+ const currentAccount = useCurrentAccount();
5
+
6
+ return {
7
+ isConnected: !!currentAccount,
8
+ address: currentAccount?.address,
9
+ account: currentAccount,
10
+ };
11
+ }