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 +52 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.js +23 -0
- package/dist/errors.d.ts +20 -0
- package/dist/errors.js +23 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +51 -0
- package/dist/placeholder.d.ts +1 -0
- package/dist/placeholder.js +1 -0
- package/dist/upload.d.ts +21 -0
- package/dist/upload.js +96 -0
- package/package.json +43 -0
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
|
package/dist/config.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/index.d.ts
ADDED
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 {};
|
package/dist/upload.d.ts
ADDED
|
@@ -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
|
+
}
|