@net-protocol/cli 0.1.7 → 0.1.9
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 +95 -0
- package/dist/cli/index.mjs +322 -8
- package/dist/cli/index.mjs.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -364,6 +364,8 @@ Profile operations for managing your Net Protocol profile.
|
|
|
364
364
|
- `profile set-picture` - Set your profile picture URL
|
|
365
365
|
- `profile set-x-username` - Set your X (Twitter) username
|
|
366
366
|
- `profile set-bio` - Set your profile bio
|
|
367
|
+
- `profile set-canvas` - Set your profile canvas (HTML content)
|
|
368
|
+
- `profile get-canvas` - Get profile canvas for an address
|
|
367
369
|
|
|
368
370
|
##### Profile Get
|
|
369
371
|
|
|
@@ -513,6 +515,99 @@ netp profile set-bio \
|
|
|
513
515
|
--encode-only
|
|
514
516
|
```
|
|
515
517
|
|
|
518
|
+
##### Profile Set Canvas
|
|
519
|
+
|
|
520
|
+
Set your profile canvas (HTML content, max 60KB). Canvas content is stored using ChunkedStorage with gzip compression.
|
|
521
|
+
|
|
522
|
+
```bash
|
|
523
|
+
netp profile set-canvas \
|
|
524
|
+
--file <path> | --content <html> \
|
|
525
|
+
[--private-key <0x...>] \
|
|
526
|
+
[--chain-id <8453|1|...>] \
|
|
527
|
+
[--rpc-url <custom-rpc>] \
|
|
528
|
+
[--encode-only]
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
**Profile Set Canvas Arguments:**
|
|
532
|
+
|
|
533
|
+
- `--file` (optional): Path to file containing canvas content (HTML, images, etc.)
|
|
534
|
+
- `--content` (optional): HTML content for canvas (inline). Must provide either `--file` or `--content`, but not both.
|
|
535
|
+
- `--private-key` (optional): Private key. Can also be set via `NET_PRIVATE_KEY` environment variable
|
|
536
|
+
- `--chain-id` (optional): Chain ID. Can also be set via `NET_CHAIN_ID` environment variable
|
|
537
|
+
- `--rpc-url` (optional): Custom RPC URL. Can also be set via `NET_RPC_URL` environment variable
|
|
538
|
+
- `--encode-only` (optional): Output transaction data as JSON instead of executing
|
|
539
|
+
|
|
540
|
+
**Notes:**
|
|
541
|
+
- Maximum canvas size is 60KB
|
|
542
|
+
- Binary files (images) are automatically converted to data URIs
|
|
543
|
+
- Content is compressed using gzip before storage
|
|
544
|
+
|
|
545
|
+
**Example:**
|
|
546
|
+
|
|
547
|
+
```bash
|
|
548
|
+
# Set canvas from file
|
|
549
|
+
netp profile set-canvas \
|
|
550
|
+
--file ./my-canvas.html \
|
|
551
|
+
--chain-id 8453
|
|
552
|
+
|
|
553
|
+
# Set canvas from inline content
|
|
554
|
+
netp profile set-canvas \
|
|
555
|
+
--content "<html><body><h1>My Profile</h1></body></html>" \
|
|
556
|
+
--chain-id 8453
|
|
557
|
+
|
|
558
|
+
# Encode-only
|
|
559
|
+
netp profile set-canvas \
|
|
560
|
+
--file ./my-canvas.html \
|
|
561
|
+
--chain-id 8453 \
|
|
562
|
+
--encode-only
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
##### Profile Get Canvas
|
|
566
|
+
|
|
567
|
+
Get profile canvas for an address.
|
|
568
|
+
|
|
569
|
+
```bash
|
|
570
|
+
netp profile get-canvas \
|
|
571
|
+
--address <wallet-address> \
|
|
572
|
+
[--output <path>] \
|
|
573
|
+
[--chain-id <8453|1|...>] \
|
|
574
|
+
[--rpc-url <custom-rpc>] \
|
|
575
|
+
[--json]
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
**Profile Get Canvas Arguments:**
|
|
579
|
+
|
|
580
|
+
- `--address` (required): Wallet address to get canvas for
|
|
581
|
+
- `--output` (optional): Write canvas content to file instead of stdout
|
|
582
|
+
- `--chain-id` (optional): Chain ID. Can also be set via `NET_CHAIN_ID` environment variable
|
|
583
|
+
- `--rpc-url` (optional): Custom RPC URL. Can also be set via `NET_RPC_URL` environment variable
|
|
584
|
+
- `--json` (optional): Output in JSON format (includes metadata like size and type)
|
|
585
|
+
|
|
586
|
+
**Notes:**
|
|
587
|
+
- Binary content (data URIs) is automatically converted to binary files when using `--output`
|
|
588
|
+
- JSON output includes: canvas content, filename, size, and whether it's a data URI
|
|
589
|
+
|
|
590
|
+
**Example:**
|
|
591
|
+
|
|
592
|
+
```bash
|
|
593
|
+
# Output to stdout
|
|
594
|
+
netp profile get-canvas \
|
|
595
|
+
--address 0x1234567890abcdef1234567890abcdef12345678 \
|
|
596
|
+
--chain-id 8453
|
|
597
|
+
|
|
598
|
+
# Save to file
|
|
599
|
+
netp profile get-canvas \
|
|
600
|
+
--address 0x1234567890abcdef1234567890abcdef12345678 \
|
|
601
|
+
--output ./canvas.html \
|
|
602
|
+
--chain-id 8453
|
|
603
|
+
|
|
604
|
+
# JSON output
|
|
605
|
+
netp profile get-canvas \
|
|
606
|
+
--address 0x1234567890abcdef1234567890abcdef12345678 \
|
|
607
|
+
--chain-id 8453 \
|
|
608
|
+
--json
|
|
609
|
+
```
|
|
610
|
+
|
|
516
611
|
#### Info Command
|
|
517
612
|
|
|
518
613
|
Show contract info and stats.
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { ProxyAgent, setGlobalDispatcher } from 'undici';
|
|
2
3
|
import 'dotenv/config';
|
|
3
4
|
import { Command } from 'commander';
|
|
4
5
|
import { createRequire } from 'module';
|
|
5
6
|
import chalk4 from 'chalk';
|
|
6
|
-
import * as
|
|
7
|
+
import * as fs2 from 'fs';
|
|
7
8
|
import { readFileSync } from 'fs';
|
|
8
|
-
import { StorageClient, detectFileTypeFromBase64, base64ToDataUri, shouldSuggestXmlStorage, getStorageKeyBytes, encodeStorageKeyForUrl, STORAGE_CONTRACT as STORAGE_CONTRACT$1
|
|
9
|
+
import { StorageClient, detectFileTypeFromBase64, base64ToDataUri, shouldSuggestXmlStorage, getStorageKeyBytes, encodeStorageKeyForUrl, chunkDataForStorage, CHUNKED_STORAGE_CONTRACT, STORAGE_CONTRACT as STORAGE_CONTRACT$1 } from '@net-protocol/storage';
|
|
9
10
|
import { stringToHex, createWalletClient, http, hexToString, parseEther, encodeFunctionData, publicActions, defineChain } from 'viem';
|
|
10
11
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
11
|
-
import { getNetContract, getChainName, getPublicClient, getChainRpcUrls, NetClient } from '@net-protocol/core';
|
|
12
|
+
import { getNetContract, getChainName, getPublicClient, getChainRpcUrls, NetClient, toBytes32 } from '@net-protocol/core';
|
|
12
13
|
import { createRelayX402Client, createRelaySession, checkBackendWalletBalance, fundBackendWallet, batchTransactions, submitTransactionsViaRelay, waitForConfirmations, retryFailedTransactions as retryFailedTransactions$1 } from '@net-protocol/relay';
|
|
13
14
|
import { isNetrSupportedChain, NetrClient } from '@net-protocol/netr';
|
|
14
|
-
import { PROFILE_PICTURE_STORAGE_KEY, PROFILE_METADATA_STORAGE_KEY, parseProfileMetadata, isValidUrl, getProfilePictureStorageArgs, STORAGE_CONTRACT, isValidXUsername, getXUsernameStorageArgs, isValidBio, getProfileMetadataStorageArgs } from '@net-protocol/profiles';
|
|
15
|
+
import { PROFILE_PICTURE_STORAGE_KEY, PROFILE_METADATA_STORAGE_KEY, parseProfileMetadata, PROFILE_CANVAS_STORAGE_KEY, isValidUrl, getProfilePictureStorageArgs, STORAGE_CONTRACT, isValidXUsername, getXUsernameStorageArgs, isValidBio, getProfileMetadataStorageArgs } from '@net-protocol/profiles';
|
|
15
16
|
import { base } from 'viem/chains';
|
|
17
|
+
import * as path from 'path';
|
|
16
18
|
|
|
17
19
|
function getRequiredChainId(optionValue) {
|
|
18
20
|
const chainId = optionValue || (process.env.NET_CHAIN_ID ? parseInt(process.env.NET_CHAIN_ID, 10) : void 0);
|
|
@@ -1191,7 +1193,7 @@ async function encodeStorageUpload(options) {
|
|
|
1191
1193
|
} else {
|
|
1192
1194
|
operatorAddress = "0x0000000000000000000000000000000000000000";
|
|
1193
1195
|
}
|
|
1194
|
-
const fileContent =
|
|
1196
|
+
const fileContent = fs2.readFileSync(options.filePath, "utf-8");
|
|
1195
1197
|
const fileSize = Buffer.byteLength(fileContent, "utf-8");
|
|
1196
1198
|
const client = new StorageClient({
|
|
1197
1199
|
chainId: readOnlyOptions.chainId
|
|
@@ -1475,8 +1477,8 @@ function registerStorageCommand(program2) {
|
|
|
1475
1477
|
console.log(chalk4.blue(`\u{1F4C1} Reading file: ${options.file}`));
|
|
1476
1478
|
console.log(chalk4.blue(`\u{1F517} Using relay API: ${options.apiUrl}`));
|
|
1477
1479
|
const result = await uploadFileWithRelay(uploadRelayOptions);
|
|
1478
|
-
const { privateKeyToAccount:
|
|
1479
|
-
const userAccount =
|
|
1480
|
+
const { privateKeyToAccount: privateKeyToAccount10 } = await import('viem/accounts');
|
|
1481
|
+
const userAccount = privateKeyToAccount10(commonOptions.privateKey);
|
|
1480
1482
|
const storageUrl = generateStorageUrl(
|
|
1481
1483
|
userAccount.address,
|
|
1482
1484
|
commonOptions.chainId,
|
|
@@ -2274,7 +2276,24 @@ async function executeProfileGet(options) {
|
|
|
2274
2276
|
throw error;
|
|
2275
2277
|
}
|
|
2276
2278
|
}
|
|
2277
|
-
|
|
2279
|
+
let canvasSize;
|
|
2280
|
+
let canvasIsDataUri = false;
|
|
2281
|
+
try {
|
|
2282
|
+
const canvasResult = await client.readChunkedStorage({
|
|
2283
|
+
key: PROFILE_CANVAS_STORAGE_KEY,
|
|
2284
|
+
operator: options.address
|
|
2285
|
+
});
|
|
2286
|
+
if (canvasResult.data) {
|
|
2287
|
+
canvasSize = canvasResult.data.length;
|
|
2288
|
+
canvasIsDataUri = canvasResult.data.startsWith("data:");
|
|
2289
|
+
}
|
|
2290
|
+
} catch (error) {
|
|
2291
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2292
|
+
if (errorMessage !== "ChunkedStorage metadata not found" && !errorMessage.includes("not found")) {
|
|
2293
|
+
throw error;
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
const hasProfile = profilePicture || xUsername || bio || canvasSize;
|
|
2278
2297
|
if (options.json) {
|
|
2279
2298
|
const output = {
|
|
2280
2299
|
address: options.address,
|
|
@@ -2282,6 +2301,7 @@ async function executeProfileGet(options) {
|
|
|
2282
2301
|
profilePicture: profilePicture || null,
|
|
2283
2302
|
xUsername: xUsername || null,
|
|
2284
2303
|
bio: bio || null,
|
|
2304
|
+
canvas: canvasSize ? { size: canvasSize, isDataUri: canvasIsDataUri } : null,
|
|
2285
2305
|
hasProfile
|
|
2286
2306
|
};
|
|
2287
2307
|
console.log(JSON.stringify(output, null, 2));
|
|
@@ -2299,6 +2319,9 @@ async function executeProfileGet(options) {
|
|
|
2299
2319
|
console.log(
|
|
2300
2320
|
` ${chalk4.cyan("Bio:")} ${bio || chalk4.gray("(not set)")}`
|
|
2301
2321
|
);
|
|
2322
|
+
console.log(
|
|
2323
|
+
` ${chalk4.cyan("Canvas:")} ${canvasSize ? `${canvasSize} bytes${canvasIsDataUri ? " (data URI)" : ""}` : chalk4.gray("(not set)")}`
|
|
2324
|
+
);
|
|
2302
2325
|
if (!hasProfile) {
|
|
2303
2326
|
console.log(chalk4.yellow("\n No profile data found for this address."));
|
|
2304
2327
|
}
|
|
@@ -2532,6 +2555,251 @@ Bio updated successfully!
|
|
|
2532
2555
|
);
|
|
2533
2556
|
}
|
|
2534
2557
|
}
|
|
2558
|
+
var MAX_CANVAS_SIZE = 60 * 1024;
|
|
2559
|
+
var CANVAS_FILENAME = "profile-compressed.html";
|
|
2560
|
+
function isBinaryContent(buffer) {
|
|
2561
|
+
const sampleSize = Math.min(buffer.length, 8192);
|
|
2562
|
+
for (let i = 0; i < sampleSize; i++) {
|
|
2563
|
+
const byte = buffer[i];
|
|
2564
|
+
if (byte === 0) return true;
|
|
2565
|
+
if (byte < 32 && byte !== 9 && byte !== 10 && byte !== 13) return true;
|
|
2566
|
+
}
|
|
2567
|
+
return false;
|
|
2568
|
+
}
|
|
2569
|
+
function getMimeType(filePath) {
|
|
2570
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
2571
|
+
const mimeTypes = {
|
|
2572
|
+
".png": "image/png",
|
|
2573
|
+
".jpg": "image/jpeg",
|
|
2574
|
+
".jpeg": "image/jpeg",
|
|
2575
|
+
".gif": "image/gif",
|
|
2576
|
+
".webp": "image/webp",
|
|
2577
|
+
".svg": "image/svg+xml",
|
|
2578
|
+
".pdf": "application/pdf",
|
|
2579
|
+
".html": "text/html",
|
|
2580
|
+
".htm": "text/html",
|
|
2581
|
+
".css": "text/css",
|
|
2582
|
+
".js": "application/javascript",
|
|
2583
|
+
".json": "application/json",
|
|
2584
|
+
".txt": "text/plain"
|
|
2585
|
+
};
|
|
2586
|
+
return mimeTypes[ext] || "application/octet-stream";
|
|
2587
|
+
}
|
|
2588
|
+
function bufferToDataUri(buffer, mimeType) {
|
|
2589
|
+
const base64 = buffer.toString("base64");
|
|
2590
|
+
return `data:${mimeType};base64,${base64}`;
|
|
2591
|
+
}
|
|
2592
|
+
async function executeProfileSetCanvas(options) {
|
|
2593
|
+
if (!options.file && !options.content) {
|
|
2594
|
+
exitWithError(
|
|
2595
|
+
"Must provide either --file or --content to set canvas content."
|
|
2596
|
+
);
|
|
2597
|
+
}
|
|
2598
|
+
if (options.file && options.content) {
|
|
2599
|
+
exitWithError("Cannot provide both --file and --content. Choose one.");
|
|
2600
|
+
}
|
|
2601
|
+
let canvasContent;
|
|
2602
|
+
if (options.file) {
|
|
2603
|
+
const filePath = path.resolve(options.file);
|
|
2604
|
+
if (!fs2.existsSync(filePath)) {
|
|
2605
|
+
exitWithError(`File not found: ${filePath}`);
|
|
2606
|
+
}
|
|
2607
|
+
const buffer = fs2.readFileSync(filePath);
|
|
2608
|
+
if (buffer.length > MAX_CANVAS_SIZE) {
|
|
2609
|
+
exitWithError(
|
|
2610
|
+
`File too large: ${buffer.length} bytes exceeds maximum of ${MAX_CANVAS_SIZE} bytes (60KB).`
|
|
2611
|
+
);
|
|
2612
|
+
}
|
|
2613
|
+
if (isBinaryContent(buffer)) {
|
|
2614
|
+
const mimeType = getMimeType(filePath);
|
|
2615
|
+
canvasContent = bufferToDataUri(buffer, mimeType);
|
|
2616
|
+
} else {
|
|
2617
|
+
canvasContent = buffer.toString("utf-8");
|
|
2618
|
+
}
|
|
2619
|
+
} else {
|
|
2620
|
+
canvasContent = options.content;
|
|
2621
|
+
const contentSize = Buffer.byteLength(canvasContent, "utf-8");
|
|
2622
|
+
if (contentSize > MAX_CANVAS_SIZE) {
|
|
2623
|
+
exitWithError(
|
|
2624
|
+
`Content too large: ${contentSize} bytes exceeds maximum of ${MAX_CANVAS_SIZE} bytes (60KB).`
|
|
2625
|
+
);
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
const bytesKey = toBytes32(PROFILE_CANVAS_STORAGE_KEY);
|
|
2629
|
+
const chunks = chunkDataForStorage(canvasContent);
|
|
2630
|
+
if (options.encodeOnly) {
|
|
2631
|
+
const readOnlyOptions = parseReadOnlyOptions({
|
|
2632
|
+
chainId: options.chainId,
|
|
2633
|
+
rpcUrl: options.rpcUrl
|
|
2634
|
+
});
|
|
2635
|
+
const encoded = encodeTransaction(
|
|
2636
|
+
{
|
|
2637
|
+
to: CHUNKED_STORAGE_CONTRACT.address,
|
|
2638
|
+
abi: CHUNKED_STORAGE_CONTRACT.abi,
|
|
2639
|
+
functionName: "put",
|
|
2640
|
+
args: [bytesKey, CANVAS_FILENAME, chunks]
|
|
2641
|
+
},
|
|
2642
|
+
readOnlyOptions.chainId
|
|
2643
|
+
);
|
|
2644
|
+
console.log(JSON.stringify(encoded, null, 2));
|
|
2645
|
+
return;
|
|
2646
|
+
}
|
|
2647
|
+
const commonOptions = parseCommonOptions(
|
|
2648
|
+
{
|
|
2649
|
+
privateKey: options.privateKey,
|
|
2650
|
+
chainId: options.chainId,
|
|
2651
|
+
rpcUrl: options.rpcUrl
|
|
2652
|
+
},
|
|
2653
|
+
true
|
|
2654
|
+
// supports --encode-only
|
|
2655
|
+
);
|
|
2656
|
+
try {
|
|
2657
|
+
const account = privateKeyToAccount(commonOptions.privateKey);
|
|
2658
|
+
const rpcUrls = getChainRpcUrls({
|
|
2659
|
+
chainId: commonOptions.chainId,
|
|
2660
|
+
rpcUrl: commonOptions.rpcUrl
|
|
2661
|
+
});
|
|
2662
|
+
const client = createWalletClient({
|
|
2663
|
+
account,
|
|
2664
|
+
chain: base,
|
|
2665
|
+
// TODO: Support other chains
|
|
2666
|
+
transport: http(rpcUrls[0])
|
|
2667
|
+
}).extend(publicActions);
|
|
2668
|
+
console.log(chalk4.blue(`Setting profile canvas...`));
|
|
2669
|
+
console.log(
|
|
2670
|
+
chalk4.gray(` Content size: ${Buffer.byteLength(canvasContent)} bytes`)
|
|
2671
|
+
);
|
|
2672
|
+
console.log(chalk4.gray(` Chunks: ${chunks.length}`));
|
|
2673
|
+
console.log(chalk4.gray(` Address: ${account.address}`));
|
|
2674
|
+
const hash = await client.writeContract({
|
|
2675
|
+
address: CHUNKED_STORAGE_CONTRACT.address,
|
|
2676
|
+
abi: CHUNKED_STORAGE_CONTRACT.abi,
|
|
2677
|
+
functionName: "put",
|
|
2678
|
+
args: [bytesKey, CANVAS_FILENAME, chunks]
|
|
2679
|
+
});
|
|
2680
|
+
console.log(chalk4.blue(`Waiting for confirmation...`));
|
|
2681
|
+
const receipt = await client.waitForTransactionReceipt({ hash });
|
|
2682
|
+
if (receipt.status === "success") {
|
|
2683
|
+
console.log(
|
|
2684
|
+
chalk4.green(
|
|
2685
|
+
`
|
|
2686
|
+
Canvas updated successfully!
|
|
2687
|
+
Transaction: ${hash}
|
|
2688
|
+
Content size: ${Buffer.byteLength(canvasContent)} bytes
|
|
2689
|
+
Chunks: ${chunks.length}`
|
|
2690
|
+
)
|
|
2691
|
+
);
|
|
2692
|
+
} else {
|
|
2693
|
+
exitWithError(`Transaction failed: ${hash}`);
|
|
2694
|
+
}
|
|
2695
|
+
} catch (error) {
|
|
2696
|
+
exitWithError(
|
|
2697
|
+
`Failed to set canvas: ${error instanceof Error ? error.message : String(error)}`
|
|
2698
|
+
);
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
function isDataUri(content) {
|
|
2702
|
+
return content.startsWith("data:");
|
|
2703
|
+
}
|
|
2704
|
+
function parseDataUri(dataUri) {
|
|
2705
|
+
const match = dataUri.match(/^data:([^;]+);base64,(.+)$/);
|
|
2706
|
+
if (!match) {
|
|
2707
|
+
throw new Error("Invalid data URI format");
|
|
2708
|
+
}
|
|
2709
|
+
const mimeType = match[1];
|
|
2710
|
+
const base64Data = match[2];
|
|
2711
|
+
const buffer = Buffer.from(base64Data, "base64");
|
|
2712
|
+
return { buffer, mimeType };
|
|
2713
|
+
}
|
|
2714
|
+
function getExtensionFromMimeType(mimeType) {
|
|
2715
|
+
const extensions = {
|
|
2716
|
+
"image/png": ".png",
|
|
2717
|
+
"image/jpeg": ".jpg",
|
|
2718
|
+
"image/gif": ".gif",
|
|
2719
|
+
"image/webp": ".webp",
|
|
2720
|
+
"image/svg+xml": ".svg",
|
|
2721
|
+
"application/pdf": ".pdf",
|
|
2722
|
+
"text/html": ".html",
|
|
2723
|
+
"text/css": ".css",
|
|
2724
|
+
"application/javascript": ".js",
|
|
2725
|
+
"application/json": ".json",
|
|
2726
|
+
"text/plain": ".txt",
|
|
2727
|
+
"application/octet-stream": ".bin"
|
|
2728
|
+
};
|
|
2729
|
+
return extensions[mimeType] || ".bin";
|
|
2730
|
+
}
|
|
2731
|
+
async function executeProfileGetCanvas(options) {
|
|
2732
|
+
const readOnlyOptions = parseReadOnlyOptions({
|
|
2733
|
+
chainId: options.chainId,
|
|
2734
|
+
rpcUrl: options.rpcUrl
|
|
2735
|
+
});
|
|
2736
|
+
const client = new StorageClient({
|
|
2737
|
+
chainId: readOnlyOptions.chainId,
|
|
2738
|
+
overrides: options.rpcUrl ? { rpcUrls: [options.rpcUrl] } : void 0
|
|
2739
|
+
});
|
|
2740
|
+
try {
|
|
2741
|
+
let canvasContent;
|
|
2742
|
+
let canvasText;
|
|
2743
|
+
try {
|
|
2744
|
+
const result = await client.readChunkedStorage({
|
|
2745
|
+
key: PROFILE_CANVAS_STORAGE_KEY,
|
|
2746
|
+
operator: options.address
|
|
2747
|
+
});
|
|
2748
|
+
if (result.data) {
|
|
2749
|
+
canvasContent = result.data;
|
|
2750
|
+
canvasText = result.text;
|
|
2751
|
+
}
|
|
2752
|
+
} catch (error) {
|
|
2753
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2754
|
+
if (errorMessage !== "ChunkedStorage metadata not found" && !errorMessage.includes("not found")) {
|
|
2755
|
+
throw error;
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
if (options.json) {
|
|
2759
|
+
const output = {
|
|
2760
|
+
address: options.address,
|
|
2761
|
+
chainId: readOnlyOptions.chainId,
|
|
2762
|
+
canvas: canvasContent || null,
|
|
2763
|
+
filename: canvasText || null,
|
|
2764
|
+
hasCanvas: !!canvasContent,
|
|
2765
|
+
isDataUri: canvasContent ? isDataUri(canvasContent) : false,
|
|
2766
|
+
contentLength: canvasContent ? canvasContent.length : 0
|
|
2767
|
+
};
|
|
2768
|
+
console.log(JSON.stringify(output, null, 2));
|
|
2769
|
+
return;
|
|
2770
|
+
}
|
|
2771
|
+
if (!canvasContent) {
|
|
2772
|
+
exitWithError(`No canvas found for address: ${options.address}`);
|
|
2773
|
+
}
|
|
2774
|
+
if (options.output) {
|
|
2775
|
+
const outputPath = path.resolve(options.output);
|
|
2776
|
+
if (isDataUri(canvasContent)) {
|
|
2777
|
+
const { buffer, mimeType } = parseDataUri(canvasContent);
|
|
2778
|
+
let finalPath = outputPath;
|
|
2779
|
+
if (!path.extname(outputPath)) {
|
|
2780
|
+
finalPath = outputPath + getExtensionFromMimeType(mimeType);
|
|
2781
|
+
}
|
|
2782
|
+
fs2.writeFileSync(finalPath, buffer);
|
|
2783
|
+
console.log(
|
|
2784
|
+
chalk4.green(`Canvas written to: ${finalPath} (${buffer.length} bytes)`)
|
|
2785
|
+
);
|
|
2786
|
+
} else {
|
|
2787
|
+
fs2.writeFileSync(outputPath, canvasContent, "utf-8");
|
|
2788
|
+
console.log(
|
|
2789
|
+
chalk4.green(
|
|
2790
|
+
`Canvas written to: ${outputPath} (${canvasContent.length} bytes)`
|
|
2791
|
+
)
|
|
2792
|
+
);
|
|
2793
|
+
}
|
|
2794
|
+
return;
|
|
2795
|
+
}
|
|
2796
|
+
console.log(canvasContent);
|
|
2797
|
+
} catch (error) {
|
|
2798
|
+
exitWithError(
|
|
2799
|
+
`Failed to read canvas: ${error instanceof Error ? error.message : String(error)}`
|
|
2800
|
+
);
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2535
2803
|
|
|
2536
2804
|
// src/commands/profile/index.ts
|
|
2537
2805
|
function registerProfileCommand(program2) {
|
|
@@ -2620,13 +2888,59 @@ function registerProfileCommand(program2) {
|
|
|
2620
2888
|
encodeOnly: options.encodeOnly
|
|
2621
2889
|
});
|
|
2622
2890
|
});
|
|
2891
|
+
const setCanvasCommand = new Command("set-canvas").description("Set your profile canvas (HTML content)").option("--file <path>", "Path to file containing canvas content").option("--content <html>", "HTML content for canvas (inline)").option(
|
|
2892
|
+
"--private-key <key>",
|
|
2893
|
+
"Private key (0x-prefixed hex, 66 characters). Can also be set via NET_PRIVATE_KEY env var"
|
|
2894
|
+
).option(
|
|
2895
|
+
"--chain-id <id>",
|
|
2896
|
+
"Chain ID. Can also be set via NET_CHAIN_ID env var",
|
|
2897
|
+
(value) => parseInt(value, 10)
|
|
2898
|
+
).option(
|
|
2899
|
+
"--rpc-url <url>",
|
|
2900
|
+
"Custom RPC URL. Can also be set via NET_RPC_URL env var"
|
|
2901
|
+
).option(
|
|
2902
|
+
"--encode-only",
|
|
2903
|
+
"Output transaction data as JSON instead of executing"
|
|
2904
|
+
).action(async (options) => {
|
|
2905
|
+
await executeProfileSetCanvas({
|
|
2906
|
+
file: options.file,
|
|
2907
|
+
content: options.content,
|
|
2908
|
+
privateKey: options.privateKey,
|
|
2909
|
+
chainId: options.chainId,
|
|
2910
|
+
rpcUrl: options.rpcUrl,
|
|
2911
|
+
encodeOnly: options.encodeOnly
|
|
2912
|
+
});
|
|
2913
|
+
});
|
|
2914
|
+
const getCanvasCommand = new Command("get-canvas").description("Get profile canvas for an address").requiredOption("--address <address>", "Wallet address to get canvas for").option("--output <path>", "Write canvas content to file instead of stdout").option(
|
|
2915
|
+
"--chain-id <id>",
|
|
2916
|
+
"Chain ID. Can also be set via NET_CHAIN_ID env var",
|
|
2917
|
+
(value) => parseInt(value, 10)
|
|
2918
|
+
).option(
|
|
2919
|
+
"--rpc-url <url>",
|
|
2920
|
+
"Custom RPC URL. Can also be set via NET_RPC_URL env var"
|
|
2921
|
+
).option("--json", "Output in JSON format").action(async (options) => {
|
|
2922
|
+
await executeProfileGetCanvas({
|
|
2923
|
+
address: options.address,
|
|
2924
|
+
output: options.output,
|
|
2925
|
+
chainId: options.chainId,
|
|
2926
|
+
rpcUrl: options.rpcUrl,
|
|
2927
|
+
json: options.json
|
|
2928
|
+
});
|
|
2929
|
+
});
|
|
2623
2930
|
profileCommand.addCommand(getCommand);
|
|
2624
2931
|
profileCommand.addCommand(setPictureCommand);
|
|
2625
2932
|
profileCommand.addCommand(setUsernameCommand);
|
|
2626
2933
|
profileCommand.addCommand(setBioCommand);
|
|
2934
|
+
profileCommand.addCommand(setCanvasCommand);
|
|
2935
|
+
profileCommand.addCommand(getCanvasCommand);
|
|
2627
2936
|
}
|
|
2628
2937
|
|
|
2629
2938
|
// src/cli/index.ts
|
|
2939
|
+
var proxyUrl = process.env.https_proxy || process.env.HTTPS_PROXY;
|
|
2940
|
+
if (proxyUrl) {
|
|
2941
|
+
const agent = new ProxyAgent(proxyUrl);
|
|
2942
|
+
setGlobalDispatcher(agent);
|
|
2943
|
+
}
|
|
2630
2944
|
var require2 = createRequire(import.meta.url);
|
|
2631
2945
|
var { version } = require2("../../package.json");
|
|
2632
2946
|
var program = new Command();
|