@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.
- package/dist/__tests__/helpers/adapter-compliance.d.ts +2 -0
- package/dist/__tests__/helpers/adapter-compliance.js +47 -0
- package/dist/__tests__/helpers/fixtures.d.ts +21 -0
- package/dist/__tests__/helpers/fixtures.js +30 -0
- package/dist/__tests__/helpers/fs-helpers.d.ts +12 -0
- package/dist/__tests__/helpers/fs-helpers.js +35 -0
- package/dist/__tests__/helpers/index.d.ts +4 -0
- package/dist/__tests__/helpers/index.js +4 -0
- package/dist/__tests__/helpers/test-hooks.d.ts +3 -0
- package/dist/__tests__/helpers/test-hooks.js +18 -0
- package/dist/context.d.ts +2 -0
- package/dist/context.js +43 -0
- package/dist/context.test.d.ts +1 -0
- package/dist/context.test.js +98 -0
- package/dist/generator/file-ops.d.ts +12 -0
- package/dist/generator/file-ops.js +40 -0
- package/dist/generator/index.d.ts +2 -0
- package/dist/generator/index.js +75 -0
- package/dist/generator/index.test.d.ts +1 -0
- package/dist/generator/index.test.js +143 -0
- package/dist/generator/layers.d.ts +3 -0
- package/dist/generator/layers.js +59 -0
- package/dist/generator/layers.test.d.ts +1 -0
- package/dist/generator/layers.test.js +92 -0
- package/dist/generator/merge.d.ts +14 -0
- package/dist/generator/merge.js +62 -0
- package/dist/generator/merge.test.d.ts +1 -0
- package/dist/generator/merge.test.js +79 -0
- package/dist/generator/transform.d.ts +21 -0
- package/dist/generator/transform.js +52 -0
- package/dist/generator/transform.test.d.ts +1 -0
- package/dist/generator/transform.test.js +51 -0
- package/dist/generator/types.d.ts +18 -0
- package/dist/generator/types.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +106 -0
- package/dist/matrix.d.ts +31 -0
- package/dist/matrix.js +31 -0
- package/dist/matrix.test.d.ts +1 -0
- package/dist/matrix.test.js +70 -0
- package/dist/post-install/git.d.ts +12 -0
- package/dist/post-install/git.js +94 -0
- package/dist/post-install/index.d.ts +16 -0
- package/dist/post-install/index.js +56 -0
- package/dist/post-install/messages.d.ts +9 -0
- package/dist/post-install/messages.js +49 -0
- package/dist/post-install/package-manager.d.ts +14 -0
- package/dist/post-install/package-manager.js +57 -0
- package/dist/post-install/validator.d.ts +14 -0
- package/dist/post-install/validator.js +114 -0
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.js +115 -0
- package/dist/test-base.d.ts +1 -0
- package/dist/test-base.js +42 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.js +1 -0
- package/dist/types.test.d.ts +1 -0
- package/dist/types.test.js +65 -0
- package/dist/utils/detect-pm.d.ts +2 -0
- package/dist/utils/detect-pm.js +10 -0
- package/dist/utils/detect-pm.test.d.ts +1 -0
- package/dist/utils/detect-pm.test.js +52 -0
- package/dist/utils/logger.d.ts +6 -0
- package/dist/utils/logger.js +7 -0
- package/dist/validator.d.ts +3 -0
- package/dist/validator.js +48 -0
- package/dist/validator.test.d.ts +1 -0
- package/dist/validator.test.js +96 -0
- package/package.json +68 -0
- package/templates/base/.env.example +31 -0
- package/templates/base/README.md +54 -0
- package/templates/base/package.json +19 -0
- package/templates/base/src/adapters/storage.ts +58 -0
- package/templates/base/src/types/index.ts +9 -0
- package/templates/base/src/types/walrus.ts +22 -0
- package/templates/base/src/utils/env.ts +41 -0
- package/templates/base/src/utils/format.ts +29 -0
- package/templates/base/tsconfig.json +19 -0
- package/templates/gallery/README.md +44 -0
- package/templates/gallery/package.json +6 -0
- package/templates/gallery/src/App.tsx +21 -0
- package/templates/gallery/src/components/FileCard.tsx +27 -0
- package/templates/gallery/src/components/GalleryGrid.tsx +30 -0
- package/templates/gallery/src/components/UploadModal.tsx +45 -0
- package/templates/gallery/src/styles.css +58 -0
- package/templates/gallery/src/types/gallery.ts +13 -0
- package/templates/gallery/src/utils/index-manager.ts +37 -0
- package/templates/react/.eslintrc.json +26 -0
- package/templates/react/README.md +80 -0
- package/templates/react/index.html +13 -0
- package/templates/react/package.json +32 -0
- package/templates/react/src/App.tsx +14 -0
- package/templates/react/src/components/Layout.tsx +21 -0
- package/templates/react/src/components/WalletConnect.tsx +21 -0
- package/templates/react/src/dapp-kit.css +1 -0
- package/templates/react/src/hooks/useStorage.ts +40 -0
- package/templates/react/src/hooks/useWallet.ts +16 -0
- package/templates/react/src/index.css +50 -0
- package/templates/react/src/index.ts +10 -0
- package/templates/react/src/main.tsx +17 -0
- package/templates/react/src/providers/QueryProvider.tsx +18 -0
- package/templates/react/src/providers/WalletProvider.tsx +37 -0
- package/templates/react/tsconfig.json +27 -0
- package/templates/react/tsconfig.node.json +10 -0
- package/templates/react/vite.config.ts +19 -0
- package/templates/sdk-mysten/README.md +65 -0
- package/templates/sdk-mysten/package.json +14 -0
- package/templates/sdk-mysten/src/adapter.ts +80 -0
- package/templates/sdk-mysten/src/client.ts +45 -0
- package/templates/sdk-mysten/src/config.ts +33 -0
- package/templates/sdk-mysten/src/index.ts +11 -0
- package/templates/sdk-mysten/src/types.ts +19 -0
- package/templates/sdk-mysten/test/adapter.test.ts +20 -0
- package/templates/simple-upload/README.md +24 -0
- package/templates/simple-upload/package.json +6 -0
- package/templates/simple-upload/src/App.tsx +27 -0
- package/templates/simple-upload/src/components/FilePreview.tsx +40 -0
- package/templates/simple-upload/src/components/UploadForm.tsx +51 -0
- 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,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,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
|
+
}
|