@blu1606/create-walrus-app 0.1.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 (119) hide show
  1. package/dist/__tests__/helpers/adapter-compliance.d.ts +2 -0
  2. package/dist/__tests__/helpers/adapter-compliance.js +47 -0
  3. package/dist/__tests__/helpers/fixtures.d.ts +21 -0
  4. package/dist/__tests__/helpers/fixtures.js +30 -0
  5. package/dist/__tests__/helpers/fs-helpers.d.ts +12 -0
  6. package/dist/__tests__/helpers/fs-helpers.js +35 -0
  7. package/dist/__tests__/helpers/index.d.ts +4 -0
  8. package/dist/__tests__/helpers/index.js +4 -0
  9. package/dist/__tests__/helpers/test-hooks.d.ts +3 -0
  10. package/dist/__tests__/helpers/test-hooks.js +18 -0
  11. package/dist/context.d.ts +2 -0
  12. package/dist/context.js +43 -0
  13. package/dist/context.test.d.ts +1 -0
  14. package/dist/context.test.js +98 -0
  15. package/dist/generator/file-ops.d.ts +12 -0
  16. package/dist/generator/file-ops.js +40 -0
  17. package/dist/generator/index.d.ts +2 -0
  18. package/dist/generator/index.js +75 -0
  19. package/dist/generator/index.test.d.ts +1 -0
  20. package/dist/generator/index.test.js +143 -0
  21. package/dist/generator/layers.d.ts +3 -0
  22. package/dist/generator/layers.js +59 -0
  23. package/dist/generator/layers.test.d.ts +1 -0
  24. package/dist/generator/layers.test.js +92 -0
  25. package/dist/generator/merge.d.ts +14 -0
  26. package/dist/generator/merge.js +62 -0
  27. package/dist/generator/merge.test.d.ts +1 -0
  28. package/dist/generator/merge.test.js +79 -0
  29. package/dist/generator/transform.d.ts +21 -0
  30. package/dist/generator/transform.js +52 -0
  31. package/dist/generator/transform.test.d.ts +1 -0
  32. package/dist/generator/transform.test.js +51 -0
  33. package/dist/generator/types.d.ts +18 -0
  34. package/dist/generator/types.js +1 -0
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.js +106 -0
  37. package/dist/matrix.d.ts +31 -0
  38. package/dist/matrix.js +31 -0
  39. package/dist/matrix.test.d.ts +1 -0
  40. package/dist/matrix.test.js +70 -0
  41. package/dist/post-install/git.d.ts +12 -0
  42. package/dist/post-install/git.js +94 -0
  43. package/dist/post-install/index.d.ts +16 -0
  44. package/dist/post-install/index.js +56 -0
  45. package/dist/post-install/messages.d.ts +9 -0
  46. package/dist/post-install/messages.js +49 -0
  47. package/dist/post-install/package-manager.d.ts +14 -0
  48. package/dist/post-install/package-manager.js +57 -0
  49. package/dist/post-install/validator.d.ts +14 -0
  50. package/dist/post-install/validator.js +114 -0
  51. package/dist/prompts.d.ts +2 -0
  52. package/dist/prompts.js +115 -0
  53. package/dist/test-base.d.ts +1 -0
  54. package/dist/test-base.js +42 -0
  55. package/dist/types.d.ts +19 -0
  56. package/dist/types.js +1 -0
  57. package/dist/types.test.d.ts +1 -0
  58. package/dist/types.test.js +65 -0
  59. package/dist/utils/detect-pm.d.ts +2 -0
  60. package/dist/utils/detect-pm.js +10 -0
  61. package/dist/utils/detect-pm.test.d.ts +1 -0
  62. package/dist/utils/detect-pm.test.js +52 -0
  63. package/dist/utils/logger.d.ts +6 -0
  64. package/dist/utils/logger.js +7 -0
  65. package/dist/validator.d.ts +3 -0
  66. package/dist/validator.js +48 -0
  67. package/dist/validator.test.d.ts +1 -0
  68. package/dist/validator.test.js +96 -0
  69. package/package.json +68 -0
  70. package/templates/base/.env.example +31 -0
  71. package/templates/base/README.md +54 -0
  72. package/templates/base/package.json +19 -0
  73. package/templates/base/src/adapters/storage.ts +58 -0
  74. package/templates/base/src/types/index.ts +9 -0
  75. package/templates/base/src/types/walrus.ts +22 -0
  76. package/templates/base/src/utils/env.ts +41 -0
  77. package/templates/base/src/utils/format.ts +29 -0
  78. package/templates/base/tsconfig.json +19 -0
  79. package/templates/gallery/README.md +44 -0
  80. package/templates/gallery/package.json +6 -0
  81. package/templates/gallery/src/App.tsx +21 -0
  82. package/templates/gallery/src/components/FileCard.tsx +27 -0
  83. package/templates/gallery/src/components/GalleryGrid.tsx +30 -0
  84. package/templates/gallery/src/components/UploadModal.tsx +45 -0
  85. package/templates/gallery/src/styles.css +58 -0
  86. package/templates/gallery/src/types/gallery.ts +13 -0
  87. package/templates/gallery/src/utils/index-manager.ts +37 -0
  88. package/templates/react/.eslintrc.json +26 -0
  89. package/templates/react/README.md +80 -0
  90. package/templates/react/index.html +13 -0
  91. package/templates/react/package.json +32 -0
  92. package/templates/react/src/App.tsx +14 -0
  93. package/templates/react/src/components/Layout.tsx +21 -0
  94. package/templates/react/src/components/WalletConnect.tsx +21 -0
  95. package/templates/react/src/dapp-kit.css +1 -0
  96. package/templates/react/src/hooks/useStorage.ts +40 -0
  97. package/templates/react/src/hooks/useWallet.ts +16 -0
  98. package/templates/react/src/index.css +50 -0
  99. package/templates/react/src/index.ts +10 -0
  100. package/templates/react/src/main.tsx +17 -0
  101. package/templates/react/src/providers/QueryProvider.tsx +18 -0
  102. package/templates/react/src/providers/WalletProvider.tsx +37 -0
  103. package/templates/react/tsconfig.json +27 -0
  104. package/templates/react/tsconfig.node.json +10 -0
  105. package/templates/react/vite.config.ts +19 -0
  106. package/templates/sdk-mysten/README.md +65 -0
  107. package/templates/sdk-mysten/package.json +14 -0
  108. package/templates/sdk-mysten/src/adapter.ts +80 -0
  109. package/templates/sdk-mysten/src/client.ts +45 -0
  110. package/templates/sdk-mysten/src/config.ts +33 -0
  111. package/templates/sdk-mysten/src/index.ts +11 -0
  112. package/templates/sdk-mysten/src/types.ts +19 -0
  113. package/templates/sdk-mysten/test/adapter.test.ts +20 -0
  114. package/templates/simple-upload/README.md +24 -0
  115. package/templates/simple-upload/package.json +6 -0
  116. package/templates/simple-upload/src/App.tsx +27 -0
  117. package/templates/simple-upload/src/components/FilePreview.tsx +40 -0
  118. package/templates/simple-upload/src/components/UploadForm.tsx +51 -0
  119. package/templates/simple-upload/src/styles.css +33 -0
@@ -0,0 +1,50 @@
1
+ :root {
2
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3
+ line-height: 1.5;
4
+ font-weight: 400;
5
+
6
+ color-scheme: light dark;
7
+ color: rgba(255, 255, 255, 0.87);
8
+ background-color: #242424;
9
+
10
+ font-synthesis: none;
11
+ text-rendering: optimizeLegibility;
12
+ -webkit-font-smoothing: antialiased;
13
+ -moz-osx-font-smoothing: grayscale;
14
+ }
15
+
16
+ body {
17
+ margin: 0;
18
+ display: flex;
19
+ place-items: center;
20
+ min-width: 320px;
21
+ min-height: 100vh;
22
+ }
23
+
24
+ #root {
25
+ max-width: 1280px;
26
+ margin: 0 auto;
27
+ padding: 2rem;
28
+ text-align: center;
29
+ }
30
+
31
+ button {
32
+ border-radius: 8px;
33
+ border: 1px solid transparent;
34
+ padding: 0.6em 1.2em;
35
+ font-size: 1em;
36
+ font-weight: 500;
37
+ font-family: inherit;
38
+ background-color: #1a1a1a;
39
+ cursor: pointer;
40
+ transition: border-color 0.25s;
41
+ }
42
+
43
+ button:hover {
44
+ border-color: #646cff;
45
+ }
46
+
47
+ button:focus,
48
+ button:focus-visible {
49
+ outline: 4px auto -webkit-focus-ring-color;
50
+ }
@@ -0,0 +1,10 @@
1
+ // Re-export storage adapter from SDK layer for use case templates
2
+ export { storageAdapter } from '../../sdk-mysten/src/index.js';
3
+
4
+ // Re-export base adapter types
5
+ export type {
6
+ StorageAdapter,
7
+ BlobMetadata,
8
+ UploadOptions,
9
+ DownloadOptions,
10
+ } from '../../base/src/adapters/storage.js';
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import { QueryProvider } from './providers/QueryProvider.js';
4
+ import { WalletProvider } from './providers/WalletProvider.js';
5
+ import App from './App.js';
6
+ import './dapp-kit.css';
7
+ import './index.css';
8
+
9
+ ReactDOM.createRoot(document.getElementById('root')!).render(
10
+ <React.StrictMode>
11
+ <QueryProvider>
12
+ <WalletProvider>
13
+ <App />
14
+ </WalletProvider>
15
+ </QueryProvider>
16
+ </React.StrictMode>
17
+ );
@@ -0,0 +1,18 @@
1
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2
+ import { ReactNode } from 'react';
3
+
4
+ const queryClient = new QueryClient({
5
+ defaultOptions: {
6
+ queries: {
7
+ refetchOnWindowFocus: false,
8
+ retry: 1,
9
+ staleTime: 5 * 60 * 1000,
10
+ },
11
+ },
12
+ });
13
+
14
+ export function QueryProvider({ children }: { children: ReactNode }) {
15
+ return (
16
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
17
+ );
18
+ }
@@ -0,0 +1,37 @@
1
+ import {
2
+ createNetworkConfig,
3
+ SuiClientProvider,
4
+ WalletProvider as SuiWalletProvider,
5
+ } from '@mysten/dapp-kit';
6
+ import { getFullnodeUrl } from '@mysten/sui/client';
7
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
8
+ import { ReactNode } from 'react';
9
+ import { loadEnv } from '../utils/env.js';
10
+
11
+ const env = loadEnv();
12
+
13
+ const validatedNetwork =
14
+ env.suiNetwork === 'mainnet' || env.suiNetwork === 'testnet'
15
+ ? env.suiNetwork
16
+ : 'testnet';
17
+
18
+ const { networkConfig } = createNetworkConfig({
19
+ [validatedNetwork]: {
20
+ url: env.suiRpc || getFullnodeUrl(validatedNetwork),
21
+ },
22
+ });
23
+
24
+ const walletQueryClient = new QueryClient();
25
+
26
+ export function WalletProvider({ children }: { children: ReactNode }) {
27
+ return (
28
+ <QueryClientProvider client={walletQueryClient}>
29
+ <SuiClientProvider
30
+ networks={networkConfig}
31
+ defaultNetwork={validatedNetwork}
32
+ >
33
+ <SuiWalletProvider>{children}</SuiWalletProvider>
34
+ </SuiClientProvider>
35
+ </QueryClientProvider>
36
+ );
37
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+
9
+ "moduleResolution": "bundler",
10
+ "allowImportingTsExtensions": true,
11
+ "resolveJsonModule": true,
12
+ "isolatedModules": true,
13
+ "noEmit": true,
14
+ "jsx": "react-jsx",
15
+
16
+ "strict": true,
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "noFallthroughCasesInSwitch": true,
20
+
21
+ "paths": {
22
+ "@/*": ["./src/*"]
23
+ }
24
+ },
25
+ "include": ["src"],
26
+ "references": [{ "path": "./tsconfig.node.json" }]
27
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "allowSyntheticDefaultImports": true
8
+ },
9
+ "include": ["vite.config.ts"]
10
+ }
@@ -0,0 +1,19 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: {
7
+ port: 3000,
8
+ open: true,
9
+ },
10
+ build: {
11
+ target: 'esnext',
12
+ outDir: 'dist',
13
+ },
14
+ resolve: {
15
+ alias: {
16
+ '@': '/src',
17
+ },
18
+ },
19
+ });
@@ -0,0 +1,65 @@
1
+ # Mysten Walrus SDK Layer
2
+
3
+ Official [Mysten Labs](https://mystenlabs.com/) SDK implementation for Walrus storage.
4
+
5
+ ## Features
6
+
7
+ ✅ **Relay Upload** - Browser-optimized uploads via relay nodes
8
+ ✅ **Direct Download** - Fast blob retrieval
9
+ ✅ **Metadata Queries** - Size, type, creation date
10
+ ✅ **Network Support** - Testnet, Mainnet, Devnet
11
+ ✅ **Type Safety** - Full TypeScript support
12
+
13
+ ## Usage
14
+
15
+ ```typescript
16
+ import { storageAdapter } from './sdk-mysten';
17
+
18
+ // Upload file
19
+ const blobId = await storageAdapter.upload(fileData, { epochs: 1 });
20
+
21
+ // Download file
22
+ const data = await storageAdapter.download(blobId);
23
+
24
+ // Get metadata
25
+ const metadata = await storageAdapter.getMetadata(blobId);
26
+ console.log(`Blob size: ${metadata.size} bytes`);
27
+ ```
28
+
29
+ ## Configuration
30
+
31
+ Set environment variables:
32
+
33
+ ```bash
34
+ VITE_WALRUS_NETWORK=testnet
35
+ VITE_WALRUS_PUBLISHER=https://publisher.walrus-testnet.walrus.space
36
+ VITE_WALRUS_AGGREGATOR=https://aggregator.walrus-testnet.walrus.space
37
+ VITE_SUI_RPC=https://fullnode.testnet.sui.io:443
38
+ ```
39
+
40
+ ## API Reference
41
+
42
+ ### `storageAdapter`
43
+
44
+ Singleton instance implementing `StorageAdapter` interface.
45
+
46
+ ### `getWalrusClient()`
47
+
48
+ Get WalrusClient singleton (lazy initialization).
49
+
50
+ ### `getNetworkConfig(network)`
51
+
52
+ Get network-specific configuration.
53
+
54
+ ## Network Defaults
55
+
56
+ | Network | Publisher | Aggregator |
57
+ | ------- | ----------------------------------------------- | ------------------------------------------------ |
58
+ | testnet | `https://publisher.walrus-testnet.walrus.space` | `https://aggregator.walrus-testnet.walrus.space` |
59
+ | mainnet | `https://publisher.walrus.space` | `https://aggregator.walrus.space` |
60
+
61
+ ## Resources
62
+
63
+ - [Walrus SDK Docs](https://sdk.mystenlabs.com/walrus)
64
+ - [Walrus Documentation](https://docs.walrus.site)
65
+ - [npm: @mysten/walrus](https://www.npmjs.com/package/@mysten/walrus)
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "description": "Mysten Walrus SDK layer for walrus-starter-kit",
7
+ "dependencies": {
8
+ "@mysten/walrus": "^1.0.0",
9
+ "@mysten/sui": "^1.10.0"
10
+ },
11
+ "peerDependencies": {
12
+ "typescript": "^5.3.0"
13
+ }
14
+ }
@@ -0,0 +1,80 @@
1
+ import type {
2
+ StorageAdapter,
3
+ BlobMetadata,
4
+ UploadOptions,
5
+ DownloadOptions,
6
+ } from '../../base/src/adapters/storage.js';
7
+ import { getWalrusClient } from './client.js';
8
+
9
+ export class MystenStorageAdapter implements StorageAdapter {
10
+ async upload(
11
+ data: File | Uint8Array,
12
+ options?: UploadOptions
13
+ ): Promise<string> {
14
+ const client = getWalrusClient();
15
+
16
+ const bytes =
17
+ data instanceof File ? new Uint8Array(await data.arrayBuffer()) : data;
18
+
19
+ try {
20
+ const result = await client.writeBlobToUploadRelay(bytes, {
21
+ nEpochs: options?.epochs || 1,
22
+ });
23
+
24
+ const blobId = result.newlyCreated.blobObject.blobId;
25
+
26
+ return blobId;
27
+ } catch (error) {
28
+ throw new Error(
29
+ `Upload failed: ${error instanceof Error ? error.message : 'Unknown error'}`
30
+ );
31
+ }
32
+ }
33
+
34
+ async download(
35
+ blobId: string,
36
+ options?: DownloadOptions
37
+ ): Promise<Uint8Array> {
38
+ const client = getWalrusClient();
39
+
40
+ try {
41
+ const data = await client.readBlob(blobId);
42
+
43
+ return data;
44
+ } catch (error) {
45
+ throw new Error(
46
+ `Download failed for blob ${blobId}: ${error instanceof Error ? error.message : 'Unknown error'}`
47
+ );
48
+ }
49
+ }
50
+
51
+ async getMetadata(blobId: string): Promise<BlobMetadata> {
52
+ const client = getWalrusClient();
53
+
54
+ try {
55
+ const metadata = await client.getBlobMetadata(blobId);
56
+
57
+ return {
58
+ blobId,
59
+ size: metadata.size,
60
+ contentType: metadata.contentType,
61
+ createdAt: metadata.createdAt || Date.now(),
62
+ };
63
+ } catch (error) {
64
+ throw new Error(
65
+ `Failed to get metadata for blob ${blobId}: ${error instanceof Error ? error.message : 'Unknown error'}`
66
+ );
67
+ }
68
+ }
69
+
70
+ async exists(blobId: string): Promise<boolean> {
71
+ try {
72
+ await this.getMetadata(blobId);
73
+ return true;
74
+ } catch {
75
+ return false;
76
+ }
77
+ }
78
+ }
79
+
80
+ export const storageAdapter = new MystenStorageAdapter();
@@ -0,0 +1,45 @@
1
+ import { WalrusClient } from '@mysten/walrus';
2
+ import { SuiClient, getFullnodeUrl } from '@mysten/sui/client';
3
+ import { loadEnv } from '../../base/src/utils/env.js';
4
+ import { getNetworkConfig } from './config.js';
5
+ import type { WalrusNetwork } from '../../base/src/types/walrus.js';
6
+
7
+ let walrusClient: WalrusClient | null = null;
8
+
9
+ export function getWalrusClient(): WalrusClient {
10
+ if (walrusClient) {
11
+ return walrusClient;
12
+ }
13
+
14
+ const env = loadEnv();
15
+
16
+ // Validate network value before casting
17
+ const allowedNetworks: WalrusNetwork[] = ['testnet', 'mainnet', 'devnet'];
18
+ if (!allowedNetworks.includes(env.walrusNetwork as WalrusNetwork)) {
19
+ throw new Error(
20
+ `Invalid WALRUS_NETWORK: ${env.walrusNetwork}. Must be one of: ${allowedNetworks.join(', ')}`
21
+ );
22
+ }
23
+ const network = env.walrusNetwork as WalrusNetwork;
24
+ const config = getNetworkConfig(network);
25
+
26
+ const suiClient = new SuiClient({
27
+ url:
28
+ env.suiRpc ||
29
+ config.suiRpcUrl ||
30
+ getFullnodeUrl(network === 'testnet' ? 'testnet' : 'mainnet'),
31
+ });
32
+
33
+ walrusClient = new WalrusClient({
34
+ network,
35
+ suiClient,
36
+ ...(env.walrusPublisher && { publisherUrl: env.walrusPublisher }),
37
+ ...(env.walrusAggregator && { aggregatorUrl: env.walrusAggregator }),
38
+ });
39
+
40
+ return walrusClient;
41
+ }
42
+
43
+ export function resetWalrusClient(): void {
44
+ walrusClient = null;
45
+ }
@@ -0,0 +1,33 @@
1
+ import type { WalrusNetwork } from '../../base/src/types/walrus.js';
2
+
3
+ export interface MystenWalrusConfig {
4
+ network: WalrusNetwork;
5
+ publisherUrl?: string;
6
+ aggregatorUrl?: string;
7
+ suiRpcUrl?: string;
8
+ }
9
+
10
+ export const NETWORK_CONFIGS: Record<WalrusNetwork, MystenWalrusConfig> = {
11
+ testnet: {
12
+ network: 'testnet',
13
+ publisherUrl: 'https://publisher.walrus-testnet.walrus.space',
14
+ aggregatorUrl: 'https://aggregator.walrus-testnet.walrus.space',
15
+ suiRpcUrl: 'https://fullnode.testnet.sui.io:443',
16
+ },
17
+ mainnet: {
18
+ network: 'mainnet',
19
+ publisherUrl: 'https://publisher.walrus.space',
20
+ aggregatorUrl: 'https://aggregator.walrus.space',
21
+ suiRpcUrl: 'https://fullnode.mainnet.sui.io:443',
22
+ },
23
+ devnet: {
24
+ network: 'devnet',
25
+ publisherUrl: 'http://localhost:8080',
26
+ aggregatorUrl: 'http://localhost:8081',
27
+ suiRpcUrl: 'http://localhost:9000',
28
+ },
29
+ };
30
+
31
+ export function getNetworkConfig(network: WalrusNetwork): MystenWalrusConfig {
32
+ return NETWORK_CONFIGS[network];
33
+ }
@@ -0,0 +1,11 @@
1
+ export { getWalrusClient, resetWalrusClient } from './client.js';
2
+ export { MystenStorageAdapter, storageAdapter } from './adapter.js';
3
+ export { getNetworkConfig, NETWORK_CONFIGS } from './config.js';
4
+ export type { MystenUploadResult, MystenBlobMetadata } from './types.js';
5
+
6
+ export type {
7
+ StorageAdapter,
8
+ BlobMetadata,
9
+ UploadOptions,
10
+ DownloadOptions,
11
+ } from '../../base/src/adapters/storage.js';
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Mysten-specific type extensions
3
+ */
4
+
5
+ export interface MystenUploadResult {
6
+ newlyCreated: {
7
+ blobObject: {
8
+ blobId: string;
9
+ size: number;
10
+ };
11
+ };
12
+ }
13
+
14
+ export interface MystenBlobMetadata {
15
+ size: number;
16
+ encodingType: string;
17
+ contentType?: string;
18
+ createdAt?: number;
19
+ }
@@ -0,0 +1,20 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { MystenStorageAdapter } from '../src/adapter.js';
3
+
4
+ describe('MystenStorageAdapter', () => {
5
+ it('should implement StorageAdapter interface', () => {
6
+ const adapter = new MystenStorageAdapter();
7
+
8
+ expect(adapter).toHaveProperty('upload');
9
+ expect(adapter).toHaveProperty('download');
10
+ expect(adapter).toHaveProperty('getMetadata');
11
+ expect(adapter).toHaveProperty('exists');
12
+ });
13
+
14
+ it('should handle upload errors gracefully', async () => {
15
+ const adapter = new MystenStorageAdapter();
16
+ const invalidData = new Uint8Array(0);
17
+
18
+ await expect(adapter.upload(invalidData)).rejects.toThrow('Upload failed');
19
+ });
20
+ });
@@ -0,0 +1,24 @@
1
+ # {{projectName}}
2
+
3
+ This is a Simple Upload Walrus application.
4
+
5
+ ## Features
6
+
7
+ - Upload any file to Walrus
8
+ - Get Blob ID after upload
9
+ - Download file by Blob ID
10
+ - File size display
11
+
12
+ ## Usage
13
+
14
+ 1. Click "Choose File" and select a file
15
+ 2. Click "Upload to Walrus"
16
+ 3. Copy the Blob ID from the success message
17
+ 4. Paste Blob ID in the download section
18
+ 5. Click "Download File"
19
+
20
+ ## Code Structure
21
+
22
+ - `UploadForm.tsx` - File upload UI
23
+ - `FilePreview.tsx` - Download UI
24
+ - `App.tsx` - Main app layout
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "dependencies": {}
6
+ }
@@ -0,0 +1,27 @@
1
+ import { Layout } from '../../react/src/components/Layout.js';
2
+ import { UploadForm } from './components/UploadForm.js';
3
+ import { FilePreview } from './components/FilePreview.js';
4
+ import './styles.css';
5
+
6
+ function App() {
7
+ return (
8
+ <Layout>
9
+ <div className="simple-upload-app">
10
+ <h2>📤 Simple Upload</h2>
11
+ <p>Upload a file to Walrus and download it by Blob ID</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
+ </Layout>
24
+ );
25
+ }
26
+
27
+ export default App;
@@ -0,0 +1,40 @@
1
+ import { useState } from 'react';
2
+ import { useDownload } from '../../../react/src/hooks/useStorage.js';
3
+
4
+ export function FilePreview() {
5
+ const [blobId, setBlobId] = useState('');
6
+ const { data, isLoading, error } = useDownload(blobId);
7
+
8
+ const handleDownload = () => {
9
+ if (!data) return;
10
+
11
+ const blob = new Blob([data]);
12
+ const url = URL.createObjectURL(blob);
13
+ const a = document.createElement('a');
14
+ a.href = url;
15
+ a.download = `walrus-${blobId.slice(0, 8)}.bin`;
16
+ a.click();
17
+ URL.revokeObjectURL(url);
18
+ };
19
+
20
+ return (
21
+ <div className="file-preview">
22
+ <input
23
+ type="text"
24
+ placeholder="Enter Blob ID"
25
+ value={blobId}
26
+ onChange={(e) => setBlobId(e.target.value)}
27
+ />
28
+
29
+ {isLoading && <p>Loading...</p>}
30
+ {error && <p className="error">Error: {error.message}</p>}
31
+
32
+ {data && (
33
+ <div className="preview-content">
34
+ <p>✓ Blob found ({data.byteLength} bytes)</p>
35
+ <button onClick={handleDownload}>Download File</button>
36
+ </div>
37
+ )}
38
+ </div>
39
+ );
40
+ }
@@ -0,0 +1,51 @@
1
+ import { useState } from 'react';
2
+ import { useUpload } from '../../../react/src/hooks/useStorage.js';
3
+
4
+ export function UploadForm() {
5
+ const [selectedFile, setSelectedFile] = useState<File | null>(null);
6
+ const upload = useUpload();
7
+
8
+ const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
9
+ const file = e.target.files?.[0];
10
+ if (file) setSelectedFile(file);
11
+ };
12
+
13
+ const handleUpload = async () => {
14
+ if (!selectedFile) return;
15
+
16
+ upload.mutate(
17
+ { file: selectedFile, options: { epochs: 1 } },
18
+ {
19
+ onSuccess: (data) => {
20
+ alert(`Upload successful! Blob ID: ${data.blobId}`);
21
+ },
22
+ }
23
+ );
24
+ };
25
+
26
+ return (
27
+ <div className="upload-form">
28
+ <input
29
+ type="file"
30
+ onChange={handleFileChange}
31
+ disabled={upload.isPending}
32
+ />
33
+
34
+ {selectedFile && (
35
+ <div className="file-info">
36
+ <p>Selected: {selectedFile.name}</p>
37
+ <p>Size: {(selectedFile.size / 1024).toFixed(2)} KB</p>
38
+ </div>
39
+ )}
40
+
41
+ <button
42
+ onClick={handleUpload}
43
+ disabled={!selectedFile || upload.isPending}
44
+ >
45
+ {upload.isPending ? 'Uploading...' : 'Upload to Walrus'}
46
+ </button>
47
+
48
+ {upload.isError && <p className="error">Error: {upload.error.message}</p>}
49
+ </div>
50
+ );
51
+ }
@@ -0,0 +1,33 @@
1
+ .simple-upload-app {
2
+ max-width: 800px;
3
+ margin: 0 auto;
4
+ }
5
+
6
+ section {
7
+ margin: 2rem 0;
8
+ padding: 1.5rem;
9
+ border: 1px solid #333;
10
+ border-radius: 8px;
11
+ }
12
+
13
+ .upload-form,
14
+ .file-preview {
15
+ display: flex;
16
+ flex-direction: column;
17
+ gap: 1rem;
18
+ }
19
+
20
+ .file-info {
21
+ background: #1a1a1a;
22
+ padding: 1rem;
23
+ border-radius: 4px;
24
+ }
25
+
26
+ .error {
27
+ color: #ff4444;
28
+ }
29
+
30
+ button:disabled {
31
+ opacity: 0.5;
32
+ cursor: not-allowed;
33
+ }