x402storage 1.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.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # x402store
2
+
3
+ Upload files to permanent IPFS storage from the command line. Pay-per-use with x402 payment protocol.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # From npm (when published)
9
+ npm install -g x402store-cli
10
+
11
+ # From source
12
+ git clone https://github.com/user/x402store-cli
13
+ cd x402store-cli
14
+ npm install
15
+ npm run build
16
+ npm link
17
+ ```
18
+
19
+ ## Setup
20
+
21
+ Export your Base wallet private key:
22
+
23
+ ```bash
24
+ export X402_PRIVATE_KEY=0x...
25
+ ```
26
+
27
+ Need a wallet? Create one at https://x402.storage
28
+
29
+ ## Usage
30
+
31
+ ```bash
32
+ # Upload a file
33
+ x402store myfile.txt
34
+
35
+ # Output on success:
36
+ # Success! File stored permanently.
37
+ # URL: https://x402.storage/bafybeig...
38
+ # CID: bafybeig...
39
+ ```
40
+
41
+ ## Pricing
42
+
43
+ $0.01 per upload (paid in USDC on Base).
44
+
45
+ ## Requirements
46
+
47
+ - Node.js 18+
48
+ - Base wallet with USDC balance
49
+
50
+ ## License
51
+
52
+ MIT
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Environment validation and configuration for x402store CLI
3
+ */
4
+ /**
5
+ * Validates that required environment variables are set.
6
+ * Exits with code 1 and helpful error message if X402_PRIVATE_KEY is missing.
7
+ */
8
+ export declare function validateEnvironment(): void;
9
+ /**
10
+ * Gets the private key from environment.
11
+ * Should only be called after validateEnvironment().
12
+ */
13
+ export declare function getPrivateKey(): `0x${string}`;
package/dist/config.js ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Environment validation and configuration for x402store CLI
3
+ */
4
+ /**
5
+ * Validates that required environment variables are set.
6
+ * Exits with code 1 and helpful error message if X402_PRIVATE_KEY is missing.
7
+ */
8
+ export function validateEnvironment() {
9
+ if (!process.env.X402_PRIVATE_KEY) {
10
+ console.error('Error: X402_PRIVATE_KEY not set.\n\n' +
11
+ 'Export your Base wallet private key:\n' +
12
+ ' export X402_PRIVATE_KEY=0x...\n\n' +
13
+ 'Need a wallet? Create one at https://x402.storage');
14
+ process.exit(1);
15
+ }
16
+ }
17
+ /**
18
+ * Gets the private key from environment.
19
+ * Should only be called after validateEnvironment().
20
+ */
21
+ export function getPrivateKey() {
22
+ return process.env.X402_PRIVATE_KEY;
23
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Custom error classes for x402store CLI
3
+ */
4
+ /**
5
+ * Error thrown when the specified file does not exist
6
+ */
7
+ export declare class FileNotFoundError extends Error {
8
+ constructor(filePath: string);
9
+ }
10
+ /**
11
+ * Error types for upload failures
12
+ */
13
+ export type UploadErrorType = 'network_error' | 'payment_failed' | 'upload_failed' | 'insufficient_balance';
14
+ /**
15
+ * Error thrown when upload fails
16
+ */
17
+ export declare class UploadError extends Error {
18
+ readonly type: UploadErrorType;
19
+ constructor(message: string, type: UploadErrorType);
20
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Custom error classes for x402store CLI
3
+ */
4
+ /**
5
+ * Error thrown when the specified file does not exist
6
+ */
7
+ export class FileNotFoundError extends Error {
8
+ constructor(filePath) {
9
+ super(`File not found: ${filePath}`);
10
+ this.name = 'FileNotFoundError';
11
+ }
12
+ }
13
+ /**
14
+ * Error thrown when upload fails
15
+ */
16
+ export class UploadError extends Error {
17
+ type;
18
+ constructor(message, type) {
19
+ super(message);
20
+ this.name = 'UploadError';
21
+ this.type = type;
22
+ }
23
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { resolve } from 'node:path';
4
+ import { validateEnvironment, getPrivateKey } from './config.js';
5
+ import { uploadFile } from './upload.js';
6
+ import { FileNotFoundError, UploadError } from './errors.js';
7
+ // Validate environment at startup - fail fast with clear error
8
+ validateEnvironment();
9
+ const program = new Command();
10
+ program
11
+ .name('x402')
12
+ .description('Upload files to x402.storage with permanent IPFS storage')
13
+ .version('1.0.0')
14
+ .argument('<file>', 'file path to upload')
15
+ .action(async (filePath) => {
16
+ try {
17
+ // Resolve to absolute path (handles relative paths)
18
+ const resolvedPath = resolve(filePath);
19
+ const privateKey = getPrivateKey();
20
+ const result = await uploadFile(resolvedPath, privateKey);
21
+ console.log('Success! File stored permanently.');
22
+ console.log(`URL: ${result.gateway}`);
23
+ console.log(`CID: ${result.cid}`);
24
+ process.exit(0);
25
+ }
26
+ catch (error) {
27
+ if (error instanceof FileNotFoundError) {
28
+ console.error(`Error: File not found: ${filePath}`);
29
+ process.exit(1);
30
+ }
31
+ if (error instanceof UploadError) {
32
+ if (error.type === 'insufficient_balance') {
33
+ console.error('Error: Insufficient balance.\n');
34
+ console.error('Fund your wallet at https://x402.storage or transfer USDC on Base.');
35
+ process.exit(1);
36
+ }
37
+ if (error.type === 'network_error') {
38
+ console.error('Error: Network error. Check your connection and try again.');
39
+ process.exit(1);
40
+ }
41
+ // payment_failed or upload_failed
42
+ console.error(`Error: Upload failed: ${error.message}`);
43
+ process.exit(1);
44
+ }
45
+ // Unknown error
46
+ const message = error instanceof Error ? error.message : 'Unknown error occurred';
47
+ console.error(`Error: ${message}`);
48
+ process.exit(1);
49
+ }
50
+ });
51
+ program.parse();
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Upload logic with x402 payment integration
3
+ */
4
+ /**
5
+ * Response from the upload API
6
+ */
7
+ interface UploadResponse {
8
+ cid: string;
9
+ gateway: string;
10
+ }
11
+ /**
12
+ * Uploads a file to api.x402.storage with automatic payment handling
13
+ *
14
+ * @param filePath - Path to the file to upload
15
+ * @param privateKey - Private key for x402 payment signing
16
+ * @returns Object containing CID and gateway URL
17
+ * @throws FileNotFoundError if file doesn't exist
18
+ * @throws UploadError on network, payment, or upload failures
19
+ */
20
+ export declare function uploadFile(filePath: string, privateKey: string): Promise<UploadResponse>;
21
+ export {};
package/dist/upload.js ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Upload logic with x402 payment integration
3
+ */
4
+ import { x402Client } from '@x402/core/client';
5
+ import { x402HTTPClient } from '@x402/core/http';
6
+ import { ExactEvmScheme } from '@x402/evm/exact/client';
7
+ import { wrapFetchWithPayment } from '@x402/fetch';
8
+ import { privateKeyToAccount } from 'viem/accounts';
9
+ import { openAsBlob } from 'node:fs';
10
+ import { stat } from 'node:fs/promises';
11
+ import { basename } from 'node:path';
12
+ import { FileNotFoundError, UploadError } from './errors.js';
13
+ /**
14
+ * Creates an x402 payment client with the given private key
15
+ */
16
+ function createPaymentClient(privateKey) {
17
+ const signer = privateKeyToAccount(privateKey);
18
+ const coreClient = new x402Client().register('eip155:*', new ExactEvmScheme(signer));
19
+ return new x402HTTPClient(coreClient);
20
+ }
21
+ /**
22
+ * Uploads a file to api.x402.storage with automatic payment handling
23
+ *
24
+ * @param filePath - Path to the file to upload
25
+ * @param privateKey - Private key for x402 payment signing
26
+ * @returns Object containing CID and gateway URL
27
+ * @throws FileNotFoundError if file doesn't exist
28
+ * @throws UploadError on network, payment, or upload failures
29
+ */
30
+ export async function uploadFile(filePath, privateKey) {
31
+ // Check file exists
32
+ try {
33
+ await stat(filePath);
34
+ }
35
+ catch {
36
+ throw new FileNotFoundError(filePath);
37
+ }
38
+ // Create payment client and wrap fetch
39
+ const client = createPaymentClient(privateKey);
40
+ const fetchWithPayment = wrapFetchWithPayment(fetch, client);
41
+ // Read file as blob
42
+ const blob = await openAsBlob(filePath);
43
+ const fileName = basename(filePath);
44
+ // Create FormData with file
45
+ const formData = new FormData();
46
+ formData.append('file', blob, fileName);
47
+ // Upload to api.x402.storage (root endpoint, not /store)
48
+ let response;
49
+ try {
50
+ response = await fetchWithPayment('https://api.x402.storage', {
51
+ method: 'POST',
52
+ body: formData,
53
+ // Do NOT set Content-Type header - let Node.js set boundary
54
+ });
55
+ }
56
+ catch (error) {
57
+ // Network or payment errors during fetch
58
+ const message = error instanceof Error ? error.message : 'Unknown network error';
59
+ // Check for payment-related errors
60
+ if (message.toLowerCase().includes('insufficient')) {
61
+ throw new UploadError(message, 'insufficient_balance');
62
+ }
63
+ if (message.toLowerCase().includes('payment') ||
64
+ message.toLowerCase().includes('402')) {
65
+ throw new UploadError(message, 'payment_failed');
66
+ }
67
+ throw new UploadError(message, 'network_error');
68
+ }
69
+ // Handle non-OK responses
70
+ if (!response.ok) {
71
+ let errorMessage = `Upload failed: ${response.status} ${response.statusText}`;
72
+ try {
73
+ const errorBody = (await response.json());
74
+ if (errorBody.error) {
75
+ errorMessage = errorBody.error;
76
+ }
77
+ }
78
+ catch {
79
+ // Ignore JSON parse errors, use status message
80
+ }
81
+ // Determine error type from status code
82
+ if (response.status === 402) {
83
+ throw new UploadError(errorMessage, 'payment_failed');
84
+ }
85
+ if (response.status >= 500) {
86
+ throw new UploadError(errorMessage, 'network_error');
87
+ }
88
+ throw new UploadError(errorMessage, 'upload_failed');
89
+ }
90
+ // Parse successful response
91
+ const result = (await response.json());
92
+ return {
93
+ cid: result.cid,
94
+ gateway: result.gateway,
95
+ };
96
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "x402storage",
3
+ "version": "1.0.0",
4
+ "description": "Upload files to x402.storage from the command line",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/rawgroundbeef/x402.storage"
10
+ },
11
+ "bin": {
12
+ "x402": "./dist/index.js"
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "keywords": [
22
+ "x402",
23
+ "ipfs",
24
+ "storage",
25
+ "cli",
26
+ "upload",
27
+ "permanent"
28
+ ],
29
+ "engines": {
30
+ "node": ">=18.0.0"
31
+ },
32
+ "dependencies": {
33
+ "@x402/core": "^2.2.0",
34
+ "@x402/evm": "latest",
35
+ "@x402/fetch": "latest",
36
+ "commander": "^12.0.0",
37
+ "viem": "^2.21.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^20.0.0",
41
+ "typescript": "^5.7.0"
42
+ }
43
+ }