@net-protocol/cli 0.1.13 → 0.1.15
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 +38 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.mjs +2353 -125
- package/dist/cli/index.mjs.map +1 -1
- package/dist/feed/index.d.ts +58 -0
- package/dist/feed/index.mjs +1357 -0
- package/dist/feed/index.mjs.map +1 -0
- package/dist/profile/index.d.ts +8 -0
- package/dist/profile/index.mjs +970 -0
- package/dist/profile/index.mjs.map +1 -0
- package/package.json +7 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -4,19 +4,23 @@ import 'dotenv/config';
|
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import { createRequire } from 'module';
|
|
6
6
|
import chalk4 from 'chalk';
|
|
7
|
-
import * as
|
|
7
|
+
import * as fs4 from 'fs';
|
|
8
8
|
import { readFileSync } from 'fs';
|
|
9
9
|
import { StorageClient, detectFileTypeFromBase64, base64ToDataUri, shouldSuggestXmlStorage, getStorageKeyBytes, encodeStorageKeyForUrl, chunkDataForStorage, CHUNKED_STORAGE_CONTRACT, STORAGE_CONTRACT as STORAGE_CONTRACT$1 } from '@net-protocol/storage';
|
|
10
10
|
import { stringToHex, createWalletClient, http, hexToString, parseEther, encodeFunctionData, publicActions, defineChain } from 'viem';
|
|
11
11
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
12
|
-
import { getNetContract, getChainName, getPublicClient, getChainRpcUrls, NetClient, toBytes32 } from '@net-protocol/core';
|
|
12
|
+
import { getNetContract, getChainName, getPublicClient, getChainRpcUrls, NetClient, toBytes32, NULL_ADDRESS } from '@net-protocol/core';
|
|
13
13
|
import { createRelayX402Client, createRelaySession, checkBackendWalletBalance, fundBackendWallet, batchTransactions, submitTransactionsViaRelay, waitForConfirmations, retryFailedTransactions as retryFailedTransactions$1 } from '@net-protocol/relay';
|
|
14
|
+
import { FeedRegistryClient, FeedClient } from '@net-protocol/feeds';
|
|
14
15
|
import { isNetrSupportedChain, NetrClient } from '@net-protocol/netr';
|
|
15
16
|
import { PROFILE_PICTURE_STORAGE_KEY, PROFILE_METADATA_STORAGE_KEY, parseProfileMetadata, PROFILE_CANVAS_STORAGE_KEY, isValidUrl, getProfilePictureStorageArgs, STORAGE_CONTRACT, isValidXUsername, getProfileMetadataStorageArgs, isValidBio, isValidTokenAddress } from '@net-protocol/profiles';
|
|
16
17
|
import { base } from 'viem/chains';
|
|
17
18
|
import * as path from 'path';
|
|
18
19
|
import { BazaarClient } from '@net-protocol/bazaar';
|
|
20
|
+
import * as os from 'os';
|
|
21
|
+
import * as readline from 'readline';
|
|
19
22
|
|
|
23
|
+
var DEFAULT_CHAIN_ID = 8453;
|
|
20
24
|
function getRequiredChainId(optionValue) {
|
|
21
25
|
const chainId = optionValue || (process.env.NET_CHAIN_ID ? parseInt(process.env.NET_CHAIN_ID, 10) : void 0);
|
|
22
26
|
if (!chainId) {
|
|
@@ -29,9 +33,22 @@ function getRequiredChainId(optionValue) {
|
|
|
29
33
|
}
|
|
30
34
|
return chainId;
|
|
31
35
|
}
|
|
36
|
+
function getChainIdWithDefault(optionValue) {
|
|
37
|
+
if (optionValue) {
|
|
38
|
+
return optionValue;
|
|
39
|
+
}
|
|
40
|
+
const envChainId = process.env.BOTCHAN_CHAIN_ID || process.env.NET_CHAIN_ID;
|
|
41
|
+
if (envChainId) {
|
|
42
|
+
return parseInt(envChainId, 10);
|
|
43
|
+
}
|
|
44
|
+
return DEFAULT_CHAIN_ID;
|
|
45
|
+
}
|
|
32
46
|
function getRpcUrl(optionValue) {
|
|
33
47
|
return optionValue || process.env.NET_RPC_URL;
|
|
34
48
|
}
|
|
49
|
+
function getRpcUrlWithBotchanFallback(optionValue) {
|
|
50
|
+
return optionValue || process.env.BOTCHAN_RPC_URL || process.env.NET_RPC_URL;
|
|
51
|
+
}
|
|
35
52
|
function parseCommonOptions(options, supportsEncodeOnly = false) {
|
|
36
53
|
const privateKey = options.privateKey || process.env.NET_PRIVATE_KEY || process.env.PRIVATE_KEY;
|
|
37
54
|
if (!privateKey) {
|
|
@@ -70,6 +87,44 @@ function parseReadOnlyOptions(options) {
|
|
|
70
87
|
rpcUrl: getRpcUrl(options.rpcUrl)
|
|
71
88
|
};
|
|
72
89
|
}
|
|
90
|
+
function parseReadOnlyOptionsWithDefault(options) {
|
|
91
|
+
return {
|
|
92
|
+
chainId: getChainIdWithDefault(options.chainId),
|
|
93
|
+
rpcUrl: getRpcUrlWithBotchanFallback(options.rpcUrl)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function parseCommonOptionsWithDefault(options, supportsEncodeOnly = false) {
|
|
97
|
+
const privateKey = options.privateKey || process.env.BOTCHAN_PRIVATE_KEY || process.env.NET_PRIVATE_KEY || process.env.PRIVATE_KEY;
|
|
98
|
+
if (!privateKey) {
|
|
99
|
+
const encodeOnlyHint = supportsEncodeOnly ? ", or use --encode-only to output transaction data without submitting" : "";
|
|
100
|
+
console.error(
|
|
101
|
+
chalk4.red(
|
|
102
|
+
`Error: Private key is required. Provide via --private-key flag or NET_PRIVATE_KEY/BOTCHAN_PRIVATE_KEY environment variable${encodeOnlyHint}`
|
|
103
|
+
)
|
|
104
|
+
);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
if (!privateKey.startsWith("0x") || privateKey.length !== 66) {
|
|
108
|
+
console.error(
|
|
109
|
+
chalk4.red(
|
|
110
|
+
"Error: Invalid private key format (must be 0x-prefixed, 66 characters)"
|
|
111
|
+
)
|
|
112
|
+
);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
if (options.privateKey) {
|
|
116
|
+
console.warn(
|
|
117
|
+
chalk4.yellow(
|
|
118
|
+
"Warning: Private key provided via command line. Consider using NET_PRIVATE_KEY environment variable instead."
|
|
119
|
+
)
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
privateKey,
|
|
124
|
+
chainId: getChainIdWithDefault(options.chainId),
|
|
125
|
+
rpcUrl: getRpcUrlWithBotchanFallback(options.rpcUrl)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
73
128
|
async function checkNormalStorageExists(params) {
|
|
74
129
|
const { storageClient, storageKey, operatorAddress, expectedContent } = params;
|
|
75
130
|
const existing = await storageClient.get({
|
|
@@ -1194,7 +1249,7 @@ async function encodeStorageUpload(options) {
|
|
|
1194
1249
|
} else {
|
|
1195
1250
|
operatorAddress = "0x0000000000000000000000000000000000000000";
|
|
1196
1251
|
}
|
|
1197
|
-
const fileContent =
|
|
1252
|
+
const fileContent = fs4.readFileSync(options.filePath, "utf-8");
|
|
1198
1253
|
const fileSize = Buffer.byteLength(fileContent, "utf-8");
|
|
1199
1254
|
const client = new StorageClient({
|
|
1200
1255
|
chainId: readOnlyOptions.chainId
|
|
@@ -1478,8 +1533,8 @@ function registerStorageCommand(program2) {
|
|
|
1478
1533
|
console.log(chalk4.blue(`\u{1F4C1} Reading file: ${options.file}`));
|
|
1479
1534
|
console.log(chalk4.blue(`\u{1F517} Using relay API: ${options.apiUrl}`));
|
|
1480
1535
|
const result = await uploadFileWithRelay(uploadRelayOptions);
|
|
1481
|
-
const { privateKeyToAccount:
|
|
1482
|
-
const userAccount =
|
|
1536
|
+
const { privateKeyToAccount: privateKeyToAccount24 } = await import('viem/accounts');
|
|
1537
|
+
const userAccount = privateKeyToAccount24(commonOptions.privateKey);
|
|
1483
1538
|
const storageUrl = generateStorageUrl(
|
|
1484
1539
|
userAccount.address,
|
|
1485
1540
|
commonOptions.chainId,
|
|
@@ -1549,6 +1604,18 @@ function registerStorageCommand(program2) {
|
|
|
1549
1604
|
storageCommand.addCommand(uploadRelayCommand);
|
|
1550
1605
|
storageCommand.addCommand(readCommand);
|
|
1551
1606
|
}
|
|
1607
|
+
function createFeedClient(options) {
|
|
1608
|
+
return new FeedClient({
|
|
1609
|
+
chainId: options.chainId,
|
|
1610
|
+
overrides: options.rpcUrl ? { rpcUrls: [options.rpcUrl] } : void 0
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
function createFeedRegistryClient(options) {
|
|
1614
|
+
return new FeedRegistryClient({
|
|
1615
|
+
chainId: options.chainId,
|
|
1616
|
+
overrides: options.rpcUrl ? { rpcUrls: [options.rpcUrl] } : void 0
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1552
1619
|
function createNetClient(options) {
|
|
1553
1620
|
return new NetClient({
|
|
1554
1621
|
chainId: options.chainId,
|
|
@@ -2755,10 +2822,10 @@ async function executeProfileSetCanvas(options) {
|
|
|
2755
2822
|
let canvasContent;
|
|
2756
2823
|
if (options.file) {
|
|
2757
2824
|
const filePath = path.resolve(options.file);
|
|
2758
|
-
if (!
|
|
2825
|
+
if (!fs4.existsSync(filePath)) {
|
|
2759
2826
|
exitWithError(`File not found: ${filePath}`);
|
|
2760
2827
|
}
|
|
2761
|
-
const buffer =
|
|
2828
|
+
const buffer = fs4.readFileSync(filePath);
|
|
2762
2829
|
if (buffer.length > MAX_CANVAS_SIZE) {
|
|
2763
2830
|
exitWithError(
|
|
2764
2831
|
`File too large: ${buffer.length} bytes exceeds maximum of ${MAX_CANVAS_SIZE} bytes (60KB).`
|
|
@@ -2933,12 +3000,12 @@ async function executeProfileGetCanvas(options) {
|
|
|
2933
3000
|
if (!path.extname(outputPath)) {
|
|
2934
3001
|
finalPath = outputPath + getExtensionFromMimeType(mimeType);
|
|
2935
3002
|
}
|
|
2936
|
-
|
|
3003
|
+
fs4.writeFileSync(finalPath, buffer);
|
|
2937
3004
|
console.log(
|
|
2938
3005
|
chalk4.green(`Canvas written to: ${finalPath} (${buffer.length} bytes)`)
|
|
2939
3006
|
);
|
|
2940
3007
|
} else {
|
|
2941
|
-
|
|
3008
|
+
fs4.writeFileSync(outputPath, canvasContent, "utf-8");
|
|
2942
3009
|
console.log(
|
|
2943
3010
|
chalk4.green(
|
|
2944
3011
|
`Canvas written to: ${outputPath} (${canvasContent.length} bytes)`
|
|
@@ -4025,133 +4092,2293 @@ Owned Tokens (${tokenIds.length}):
|
|
|
4025
4092
|
);
|
|
4026
4093
|
}
|
|
4027
4094
|
}
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
"Chain ID. Can also be set via NET_CHAIN_ID env var",
|
|
4033
|
-
(value) => parseInt(value, 10)
|
|
4034
|
-
];
|
|
4035
|
-
var rpcUrlOption = [
|
|
4036
|
-
"--rpc-url <url>",
|
|
4037
|
-
"Custom RPC URL. Can also be set via NET_RPC_URL env var"
|
|
4038
|
-
];
|
|
4039
|
-
var privateKeyOption = [
|
|
4040
|
-
"--private-key <key>",
|
|
4041
|
-
"Private key (0x-prefixed hex). Can also be set via NET_PRIVATE_KEY env var"
|
|
4042
|
-
];
|
|
4043
|
-
function registerBazaarCommand(program2) {
|
|
4044
|
-
const bazaarCommand = program2.command("bazaar").description("Bazaar NFT trading operations");
|
|
4045
|
-
const listListingsCommand = new Command("list-listings").description("List active NFT listings").option("--nft-address <address>", "NFT contract address (optional for cross-collection)").option(...chainIdOption).option(...rpcUrlOption).option("--json", "Output in JSON format").action(async (options) => {
|
|
4046
|
-
await executeListListings({
|
|
4047
|
-
nftAddress: options.nftAddress,
|
|
4048
|
-
chainId: options.chainId,
|
|
4049
|
-
rpcUrl: options.rpcUrl,
|
|
4050
|
-
json: options.json
|
|
4051
|
-
});
|
|
4052
|
-
});
|
|
4053
|
-
const listOffersCommand = new Command("list-offers").description("List active collection offers").requiredOption("--nft-address <address>", "NFT contract address").option(...chainIdOption).option(...rpcUrlOption).option("--json", "Output in JSON format").action(async (options) => {
|
|
4054
|
-
await executeListOffers({
|
|
4055
|
-
nftAddress: options.nftAddress,
|
|
4056
|
-
chainId: options.chainId,
|
|
4057
|
-
rpcUrl: options.rpcUrl,
|
|
4058
|
-
json: options.json
|
|
4059
|
-
});
|
|
4095
|
+
async function executeListErc20Listings(options) {
|
|
4096
|
+
const readOnlyOptions = parseReadOnlyOptions({
|
|
4097
|
+
chainId: options.chainId,
|
|
4098
|
+
rpcUrl: options.rpcUrl
|
|
4060
4099
|
});
|
|
4061
|
-
const
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
chainId: options.chainId,
|
|
4065
|
-
rpcUrl: options.rpcUrl,
|
|
4066
|
-
json: options.json
|
|
4067
|
-
});
|
|
4100
|
+
const bazaarClient = new BazaarClient({
|
|
4101
|
+
chainId: readOnlyOptions.chainId,
|
|
4102
|
+
rpcUrl: readOnlyOptions.rpcUrl
|
|
4068
4103
|
});
|
|
4069
|
-
|
|
4070
|
-
await
|
|
4071
|
-
|
|
4072
|
-
tokenId: options.tokenId,
|
|
4073
|
-
price: options.price,
|
|
4074
|
-
targetFulfiller: options.targetFulfiller,
|
|
4075
|
-
offerer: options.offerer,
|
|
4076
|
-
privateKey: options.privateKey,
|
|
4077
|
-
chainId: options.chainId,
|
|
4078
|
-
rpcUrl: options.rpcUrl
|
|
4104
|
+
try {
|
|
4105
|
+
const listings = await bazaarClient.getErc20Listings({
|
|
4106
|
+
tokenAddress: options.tokenAddress
|
|
4079
4107
|
});
|
|
4108
|
+
if (options.json) {
|
|
4109
|
+
const output = listings.map((l) => ({
|
|
4110
|
+
orderHash: l.orderHash,
|
|
4111
|
+
maker: l.maker,
|
|
4112
|
+
tokenAddress: l.tokenAddress,
|
|
4113
|
+
tokenAmount: l.tokenAmount.toString(),
|
|
4114
|
+
price: l.price,
|
|
4115
|
+
priceWei: l.priceWei.toString(),
|
|
4116
|
+
pricePerToken: l.pricePerToken,
|
|
4117
|
+
pricePerTokenWei: l.pricePerTokenWei.toString(),
|
|
4118
|
+
currency: l.currency,
|
|
4119
|
+
expirationDate: l.expirationDate,
|
|
4120
|
+
orderStatus: l.orderStatus
|
|
4121
|
+
}));
|
|
4122
|
+
console.log(JSON.stringify(output, null, 2));
|
|
4123
|
+
return;
|
|
4124
|
+
}
|
|
4125
|
+
if (listings.length === 0) {
|
|
4126
|
+
console.log(chalk4.yellow("No active ERC-20 listings found"));
|
|
4127
|
+
return;
|
|
4128
|
+
}
|
|
4129
|
+
console.log(chalk4.white.bold(`
|
|
4130
|
+
ERC-20 Listings (${listings.length}):
|
|
4131
|
+
`));
|
|
4132
|
+
for (const listing of listings) {
|
|
4133
|
+
const expiry = new Date(listing.expirationDate * 1e3).toLocaleString();
|
|
4134
|
+
console.log(` ${chalk4.cyan("Order Hash:")} ${listing.orderHash}`);
|
|
4135
|
+
console.log(` ${chalk4.cyan("Seller:")} ${listing.maker}`);
|
|
4136
|
+
console.log(` ${chalk4.cyan("Token:")} ${listing.tokenAddress}`);
|
|
4137
|
+
console.log(` ${chalk4.cyan("Amount:")} ${listing.tokenAmount.toString()}`);
|
|
4138
|
+
console.log(` ${chalk4.cyan("Price:")} ${formatEthPrice(listing.price)} ${listing.currency.toUpperCase()}`);
|
|
4139
|
+
console.log(` ${chalk4.cyan("Price/Token:")} ${formatEthPrice(listing.pricePerToken)} ${listing.currency.toUpperCase()}`);
|
|
4140
|
+
console.log(` ${chalk4.cyan("Expires:")} ${expiry}`);
|
|
4141
|
+
console.log();
|
|
4142
|
+
}
|
|
4143
|
+
} catch (error) {
|
|
4144
|
+
exitWithError(
|
|
4145
|
+
`Failed to fetch ERC-20 listings: ${error instanceof Error ? error.message : String(error)}`
|
|
4146
|
+
);
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
async function executeListErc20Offers(options) {
|
|
4150
|
+
const readOnlyOptions = parseReadOnlyOptions({
|
|
4151
|
+
chainId: options.chainId,
|
|
4152
|
+
rpcUrl: options.rpcUrl
|
|
4080
4153
|
});
|
|
4081
|
-
const
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
price: options.price,
|
|
4085
|
-
offerer: options.offerer,
|
|
4086
|
-
privateKey: options.privateKey,
|
|
4087
|
-
chainId: options.chainId,
|
|
4088
|
-
rpcUrl: options.rpcUrl
|
|
4089
|
-
});
|
|
4154
|
+
const bazaarClient = new BazaarClient({
|
|
4155
|
+
chainId: readOnlyOptions.chainId,
|
|
4156
|
+
rpcUrl: readOnlyOptions.rpcUrl
|
|
4090
4157
|
});
|
|
4091
|
-
|
|
4092
|
-
await
|
|
4093
|
-
|
|
4094
|
-
signature: options.signature,
|
|
4095
|
-
privateKey: options.privateKey,
|
|
4096
|
-
chainId: options.chainId,
|
|
4097
|
-
rpcUrl: options.rpcUrl,
|
|
4098
|
-
encodeOnly: options.encodeOnly
|
|
4158
|
+
try {
|
|
4159
|
+
const offers = await bazaarClient.getErc20Offers({
|
|
4160
|
+
tokenAddress: options.tokenAddress
|
|
4099
4161
|
});
|
|
4162
|
+
if (options.json) {
|
|
4163
|
+
const output = offers.map((o) => ({
|
|
4164
|
+
orderHash: o.orderHash,
|
|
4165
|
+
maker: o.maker,
|
|
4166
|
+
tokenAddress: o.tokenAddress,
|
|
4167
|
+
tokenAmount: o.tokenAmount.toString(),
|
|
4168
|
+
price: o.price,
|
|
4169
|
+
priceWei: o.priceWei.toString(),
|
|
4170
|
+
pricePerToken: o.pricePerToken,
|
|
4171
|
+
pricePerTokenWei: o.pricePerTokenWei.toString(),
|
|
4172
|
+
currency: o.currency,
|
|
4173
|
+
expirationDate: o.expirationDate,
|
|
4174
|
+
orderStatus: o.orderStatus
|
|
4175
|
+
}));
|
|
4176
|
+
console.log(JSON.stringify(output, null, 2));
|
|
4177
|
+
return;
|
|
4178
|
+
}
|
|
4179
|
+
if (offers.length === 0) {
|
|
4180
|
+
console.log(chalk4.yellow("No active ERC-20 offers found"));
|
|
4181
|
+
return;
|
|
4182
|
+
}
|
|
4183
|
+
console.log(chalk4.white.bold(`
|
|
4184
|
+
ERC-20 Offers (${offers.length}):
|
|
4185
|
+
`));
|
|
4186
|
+
for (const offer of offers) {
|
|
4187
|
+
const expiry = new Date(offer.expirationDate * 1e3).toLocaleString();
|
|
4188
|
+
console.log(` ${chalk4.cyan("Order Hash:")} ${offer.orderHash}`);
|
|
4189
|
+
console.log(` ${chalk4.cyan("Buyer:")} ${offer.maker}`);
|
|
4190
|
+
console.log(` ${chalk4.cyan("Token:")} ${offer.tokenAddress}`);
|
|
4191
|
+
console.log(` ${chalk4.cyan("Amount:")} ${offer.tokenAmount.toString()}`);
|
|
4192
|
+
console.log(` ${chalk4.cyan("Price:")} ${formatEthPrice(offer.price)} ${offer.currency.toUpperCase()}`);
|
|
4193
|
+
console.log(` ${chalk4.cyan("Price/Token:")} ${formatEthPrice(offer.pricePerToken)} ${offer.currency.toUpperCase()}`);
|
|
4194
|
+
console.log(` ${chalk4.cyan("Expires:")} ${expiry}`);
|
|
4195
|
+
console.log();
|
|
4196
|
+
}
|
|
4197
|
+
} catch (error) {
|
|
4198
|
+
exitWithError(
|
|
4199
|
+
`Failed to fetch ERC-20 offers: ${error instanceof Error ? error.message : String(error)}`
|
|
4200
|
+
);
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
async function executeCreateErc20Listing(options) {
|
|
4204
|
+
const hasPrivateKey = !!(options.privateKey || process.env.NET_PRIVATE_KEY || process.env.PRIVATE_KEY);
|
|
4205
|
+
if (!hasPrivateKey) {
|
|
4206
|
+
await executeKeylessMode3(options);
|
|
4207
|
+
return;
|
|
4208
|
+
}
|
|
4209
|
+
const commonOptions = parseCommonOptions({
|
|
4210
|
+
privateKey: options.privateKey,
|
|
4211
|
+
chainId: options.chainId,
|
|
4212
|
+
rpcUrl: options.rpcUrl
|
|
4100
4213
|
});
|
|
4101
|
-
const
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
privateKey: options.privateKey,
|
|
4106
|
-
chainId: options.chainId,
|
|
4107
|
-
rpcUrl: options.rpcUrl,
|
|
4108
|
-
encodeOnly: options.encodeOnly
|
|
4109
|
-
});
|
|
4214
|
+
const account = privateKeyToAccount(commonOptions.privateKey);
|
|
4215
|
+
const bazaarClient = new BazaarClient({
|
|
4216
|
+
chainId: commonOptions.chainId,
|
|
4217
|
+
rpcUrl: commonOptions.rpcUrl
|
|
4110
4218
|
});
|
|
4111
|
-
const
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4219
|
+
const priceWei = parseEther(options.price);
|
|
4220
|
+
const tokenAmount = BigInt(options.tokenAmount);
|
|
4221
|
+
try {
|
|
4222
|
+
console.log(chalk4.blue("Preparing ERC-20 listing..."));
|
|
4223
|
+
const prepared = await bazaarClient.prepareCreateErc20Listing({
|
|
4224
|
+
tokenAddress: options.tokenAddress,
|
|
4225
|
+
tokenAmount,
|
|
4226
|
+
priceWei,
|
|
4227
|
+
offerer: account.address
|
|
4120
4228
|
});
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
orderHash: options.orderHash,
|
|
4125
|
-
nftAddress: options.nftAddress,
|
|
4126
|
-
tokenId: options.tokenId,
|
|
4127
|
-
seller: options.seller,
|
|
4128
|
-
privateKey: options.privateKey,
|
|
4129
|
-
chainId: options.chainId,
|
|
4130
|
-
rpcUrl: options.rpcUrl,
|
|
4131
|
-
encodeOnly: options.encodeOnly
|
|
4229
|
+
const rpcUrls = getChainRpcUrls({
|
|
4230
|
+
chainId: commonOptions.chainId,
|
|
4231
|
+
rpcUrl: commonOptions.rpcUrl
|
|
4132
4232
|
});
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
nftAddress: options.nftAddress,
|
|
4137
|
-
owner: options.owner,
|
|
4138
|
-
chainId: options.chainId,
|
|
4139
|
-
rpcUrl: options.rpcUrl,
|
|
4140
|
-
json: options.json,
|
|
4141
|
-
startTokenId: options.startTokenId,
|
|
4142
|
-
endTokenId: options.endTokenId
|
|
4233
|
+
const walletClient = createWalletClient({
|
|
4234
|
+
account,
|
|
4235
|
+
transport: http(rpcUrls[0])
|
|
4143
4236
|
});
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4237
|
+
for (const approval of prepared.approvals) {
|
|
4238
|
+
console.log(chalk4.blue("Sending approval transaction..."));
|
|
4239
|
+
const calldata = encodeFunctionData({
|
|
4240
|
+
abi: approval.abi,
|
|
4241
|
+
functionName: approval.functionName,
|
|
4242
|
+
args: approval.args
|
|
4243
|
+
});
|
|
4244
|
+
const approvalHash = await walletClient.sendTransaction({
|
|
4245
|
+
to: approval.to,
|
|
4246
|
+
data: calldata,
|
|
4247
|
+
chain: null,
|
|
4248
|
+
value: approval.value
|
|
4249
|
+
});
|
|
4250
|
+
console.log(chalk4.green(`Approval tx: ${approvalHash}`));
|
|
4251
|
+
}
|
|
4252
|
+
console.log(chalk4.blue("Signing order..."));
|
|
4253
|
+
const signature = await walletClient.signTypedData({
|
|
4254
|
+
domain: prepared.eip712.domain,
|
|
4255
|
+
types: prepared.eip712.types,
|
|
4256
|
+
primaryType: prepared.eip712.primaryType,
|
|
4257
|
+
message: prepared.eip712.message
|
|
4258
|
+
});
|
|
4259
|
+
console.log(chalk4.blue("Submitting ERC-20 listing..."));
|
|
4260
|
+
const submitTx = bazaarClient.prepareSubmitErc20Listing(
|
|
4261
|
+
prepared.eip712.orderParameters,
|
|
4262
|
+
prepared.eip712.counter,
|
|
4263
|
+
signature
|
|
4264
|
+
);
|
|
4265
|
+
const submitCalldata = encodeFunctionData({
|
|
4266
|
+
abi: submitTx.abi,
|
|
4267
|
+
functionName: submitTx.functionName,
|
|
4268
|
+
args: submitTx.args
|
|
4269
|
+
});
|
|
4270
|
+
const hash = await walletClient.sendTransaction({
|
|
4271
|
+
to: submitTx.to,
|
|
4272
|
+
data: submitCalldata,
|
|
4273
|
+
chain: null,
|
|
4274
|
+
value: submitTx.value
|
|
4275
|
+
});
|
|
4276
|
+
console.log(
|
|
4277
|
+
chalk4.green(
|
|
4278
|
+
`ERC-20 listing created successfully!
|
|
4279
|
+
Transaction: ${hash}
|
|
4280
|
+
Token: ${options.tokenAddress}
|
|
4281
|
+
Amount: ${options.tokenAmount}
|
|
4282
|
+
Price: ${options.price} ETH`
|
|
4283
|
+
)
|
|
4284
|
+
);
|
|
4285
|
+
} catch (error) {
|
|
4286
|
+
exitWithError(
|
|
4287
|
+
`Failed to create ERC-20 listing: ${error instanceof Error ? error.message : String(error)}`
|
|
4288
|
+
);
|
|
4289
|
+
}
|
|
4290
|
+
}
|
|
4291
|
+
async function executeKeylessMode3(options) {
|
|
4292
|
+
if (!options.offerer) {
|
|
4293
|
+
exitWithError("--offerer is required when not providing --private-key");
|
|
4294
|
+
}
|
|
4295
|
+
const readOnlyOptions = parseReadOnlyOptions({
|
|
4296
|
+
chainId: options.chainId,
|
|
4297
|
+
rpcUrl: options.rpcUrl
|
|
4298
|
+
});
|
|
4299
|
+
const bazaarClient = new BazaarClient({
|
|
4300
|
+
chainId: readOnlyOptions.chainId,
|
|
4301
|
+
rpcUrl: readOnlyOptions.rpcUrl
|
|
4302
|
+
});
|
|
4303
|
+
const priceWei = parseEther(options.price);
|
|
4304
|
+
const tokenAmount = BigInt(options.tokenAmount);
|
|
4305
|
+
try {
|
|
4306
|
+
const prepared = await bazaarClient.prepareCreateErc20Listing({
|
|
4307
|
+
tokenAddress: options.tokenAddress,
|
|
4308
|
+
tokenAmount,
|
|
4309
|
+
priceWei,
|
|
4310
|
+
offerer: options.offerer
|
|
4311
|
+
});
|
|
4312
|
+
const output = {
|
|
4313
|
+
eip712: {
|
|
4314
|
+
domain: prepared.eip712.domain,
|
|
4315
|
+
types: prepared.eip712.types,
|
|
4316
|
+
primaryType: prepared.eip712.primaryType,
|
|
4317
|
+
message: prepared.eip712.message
|
|
4318
|
+
},
|
|
4319
|
+
orderParameters: prepared.eip712.orderParameters,
|
|
4320
|
+
counter: prepared.eip712.counter.toString(),
|
|
4321
|
+
approvals: prepared.approvals.map((a) => ({
|
|
4322
|
+
to: a.to,
|
|
4323
|
+
data: encodeFunctionData({
|
|
4324
|
+
abi: a.abi,
|
|
4325
|
+
functionName: a.functionName,
|
|
4326
|
+
args: a.args
|
|
4327
|
+
}),
|
|
4328
|
+
description: `Approve ${a.functionName}`
|
|
4329
|
+
}))
|
|
4330
|
+
};
|
|
4331
|
+
console.log(JSON.stringify(output, bigintReplacer3, 2));
|
|
4332
|
+
} catch (error) {
|
|
4333
|
+
exitWithError(
|
|
4334
|
+
`Failed to prepare ERC-20 listing: ${error instanceof Error ? error.message : String(error)}`
|
|
4335
|
+
);
|
|
4336
|
+
}
|
|
4337
|
+
}
|
|
4338
|
+
function bigintReplacer3(_key, value) {
|
|
4339
|
+
if (typeof value === "bigint") {
|
|
4340
|
+
return value.toString();
|
|
4341
|
+
}
|
|
4342
|
+
return value;
|
|
4343
|
+
}
|
|
4344
|
+
async function executeCreateErc20Offer(options) {
|
|
4345
|
+
const hasPrivateKey = !!(options.privateKey || process.env.NET_PRIVATE_KEY || process.env.PRIVATE_KEY);
|
|
4346
|
+
if (!hasPrivateKey) {
|
|
4347
|
+
await executeKeylessMode4(options);
|
|
4348
|
+
return;
|
|
4349
|
+
}
|
|
4350
|
+
const commonOptions = parseCommonOptions({
|
|
4351
|
+
privateKey: options.privateKey,
|
|
4352
|
+
chainId: options.chainId,
|
|
4353
|
+
rpcUrl: options.rpcUrl
|
|
4354
|
+
});
|
|
4355
|
+
const account = privateKeyToAccount(commonOptions.privateKey);
|
|
4356
|
+
const bazaarClient = new BazaarClient({
|
|
4357
|
+
chainId: commonOptions.chainId,
|
|
4358
|
+
rpcUrl: commonOptions.rpcUrl
|
|
4359
|
+
});
|
|
4360
|
+
const priceWei = parseEther(options.price);
|
|
4361
|
+
const tokenAmount = BigInt(options.tokenAmount);
|
|
4362
|
+
try {
|
|
4363
|
+
console.log(chalk4.blue("Preparing ERC-20 offer..."));
|
|
4364
|
+
const prepared = await bazaarClient.prepareCreateErc20Offer({
|
|
4365
|
+
tokenAddress: options.tokenAddress,
|
|
4366
|
+
tokenAmount,
|
|
4367
|
+
priceWei,
|
|
4368
|
+
offerer: account.address
|
|
4369
|
+
});
|
|
4370
|
+
const rpcUrls = getChainRpcUrls({
|
|
4371
|
+
chainId: commonOptions.chainId,
|
|
4372
|
+
rpcUrl: commonOptions.rpcUrl
|
|
4373
|
+
});
|
|
4374
|
+
const walletClient = createWalletClient({
|
|
4375
|
+
account,
|
|
4376
|
+
transport: http(rpcUrls[0])
|
|
4377
|
+
});
|
|
4378
|
+
for (const approval of prepared.approvals) {
|
|
4379
|
+
console.log(chalk4.blue("Sending approval transaction..."));
|
|
4380
|
+
const calldata = encodeFunctionData({
|
|
4381
|
+
abi: approval.abi,
|
|
4382
|
+
functionName: approval.functionName,
|
|
4383
|
+
args: approval.args
|
|
4384
|
+
});
|
|
4385
|
+
const approvalHash = await walletClient.sendTransaction({
|
|
4386
|
+
to: approval.to,
|
|
4387
|
+
data: calldata,
|
|
4388
|
+
chain: null,
|
|
4389
|
+
value: approval.value
|
|
4390
|
+
});
|
|
4391
|
+
console.log(chalk4.green(`Approval tx: ${approvalHash}`));
|
|
4392
|
+
}
|
|
4393
|
+
console.log(chalk4.blue("Signing offer..."));
|
|
4394
|
+
const signature = await walletClient.signTypedData({
|
|
4395
|
+
domain: prepared.eip712.domain,
|
|
4396
|
+
types: prepared.eip712.types,
|
|
4397
|
+
primaryType: prepared.eip712.primaryType,
|
|
4398
|
+
message: prepared.eip712.message
|
|
4399
|
+
});
|
|
4400
|
+
console.log(chalk4.blue("Submitting ERC-20 offer..."));
|
|
4401
|
+
const submitTx = bazaarClient.prepareSubmitErc20Offer(
|
|
4402
|
+
prepared.eip712.orderParameters,
|
|
4403
|
+
prepared.eip712.counter,
|
|
4404
|
+
signature
|
|
4405
|
+
);
|
|
4406
|
+
const submitCalldata = encodeFunctionData({
|
|
4407
|
+
abi: submitTx.abi,
|
|
4408
|
+
functionName: submitTx.functionName,
|
|
4409
|
+
args: submitTx.args
|
|
4410
|
+
});
|
|
4411
|
+
const hash = await walletClient.sendTransaction({
|
|
4412
|
+
to: submitTx.to,
|
|
4413
|
+
data: submitCalldata,
|
|
4414
|
+
chain: null,
|
|
4415
|
+
value: submitTx.value
|
|
4416
|
+
});
|
|
4417
|
+
console.log(
|
|
4418
|
+
chalk4.green(
|
|
4419
|
+
`ERC-20 offer created successfully!
|
|
4420
|
+
Transaction: ${hash}
|
|
4421
|
+
Token: ${options.tokenAddress}
|
|
4422
|
+
Amount: ${options.tokenAmount}
|
|
4423
|
+
Price: ${options.price} ETH`
|
|
4424
|
+
)
|
|
4425
|
+
);
|
|
4426
|
+
} catch (error) {
|
|
4427
|
+
exitWithError(
|
|
4428
|
+
`Failed to create ERC-20 offer: ${error instanceof Error ? error.message : String(error)}`
|
|
4429
|
+
);
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
async function executeKeylessMode4(options) {
|
|
4433
|
+
if (!options.offerer) {
|
|
4434
|
+
exitWithError("--offerer is required when not providing --private-key");
|
|
4435
|
+
}
|
|
4436
|
+
const readOnlyOptions = parseReadOnlyOptions({
|
|
4437
|
+
chainId: options.chainId,
|
|
4438
|
+
rpcUrl: options.rpcUrl
|
|
4439
|
+
});
|
|
4440
|
+
const bazaarClient = new BazaarClient({
|
|
4441
|
+
chainId: readOnlyOptions.chainId,
|
|
4442
|
+
rpcUrl: readOnlyOptions.rpcUrl
|
|
4443
|
+
});
|
|
4444
|
+
const priceWei = parseEther(options.price);
|
|
4445
|
+
const tokenAmount = BigInt(options.tokenAmount);
|
|
4446
|
+
try {
|
|
4447
|
+
const prepared = await bazaarClient.prepareCreateErc20Offer({
|
|
4448
|
+
tokenAddress: options.tokenAddress,
|
|
4449
|
+
tokenAmount,
|
|
4450
|
+
priceWei,
|
|
4451
|
+
offerer: options.offerer
|
|
4452
|
+
});
|
|
4453
|
+
const output = {
|
|
4454
|
+
eip712: {
|
|
4455
|
+
domain: prepared.eip712.domain,
|
|
4456
|
+
types: prepared.eip712.types,
|
|
4457
|
+
primaryType: prepared.eip712.primaryType,
|
|
4458
|
+
message: prepared.eip712.message
|
|
4459
|
+
},
|
|
4460
|
+
orderParameters: prepared.eip712.orderParameters,
|
|
4461
|
+
counter: prepared.eip712.counter.toString(),
|
|
4462
|
+
approvals: prepared.approvals.map((a) => ({
|
|
4463
|
+
to: a.to,
|
|
4464
|
+
data: encodeFunctionData({
|
|
4465
|
+
abi: a.abi,
|
|
4466
|
+
functionName: a.functionName,
|
|
4467
|
+
args: a.args
|
|
4468
|
+
}),
|
|
4469
|
+
description: `Approve ${a.functionName}`
|
|
4470
|
+
}))
|
|
4471
|
+
};
|
|
4472
|
+
console.log(JSON.stringify(output, bigintReplacer4, 2));
|
|
4473
|
+
} catch (error) {
|
|
4474
|
+
exitWithError(
|
|
4475
|
+
`Failed to prepare ERC-20 offer: ${error instanceof Error ? error.message : String(error)}`
|
|
4476
|
+
);
|
|
4477
|
+
}
|
|
4478
|
+
}
|
|
4479
|
+
function bigintReplacer4(_key, value) {
|
|
4480
|
+
if (typeof value === "bigint") {
|
|
4481
|
+
return value.toString();
|
|
4482
|
+
}
|
|
4483
|
+
return value;
|
|
4484
|
+
}
|
|
4485
|
+
async function executeSubmitErc20Listing(options) {
|
|
4486
|
+
let orderData;
|
|
4487
|
+
try {
|
|
4488
|
+
const raw = readFileSync(options.orderData, "utf-8");
|
|
4489
|
+
orderData = JSON.parse(raw);
|
|
4490
|
+
} catch (error) {
|
|
4491
|
+
exitWithError(
|
|
4492
|
+
`Failed to read order data file: ${error instanceof Error ? error.message : String(error)}`
|
|
4493
|
+
);
|
|
4494
|
+
}
|
|
4495
|
+
if (options.encodeOnly) {
|
|
4496
|
+
await executeEncodeOnly7(options, orderData);
|
|
4497
|
+
return;
|
|
4498
|
+
}
|
|
4499
|
+
const commonOptions = parseCommonOptions(
|
|
4500
|
+
{
|
|
4501
|
+
privateKey: options.privateKey,
|
|
4502
|
+
chainId: options.chainId,
|
|
4503
|
+
rpcUrl: options.rpcUrl
|
|
4504
|
+
},
|
|
4505
|
+
true
|
|
4506
|
+
);
|
|
4507
|
+
const account = privateKeyToAccount(commonOptions.privateKey);
|
|
4508
|
+
const bazaarClient = new BazaarClient({
|
|
4509
|
+
chainId: commonOptions.chainId,
|
|
4510
|
+
rpcUrl: commonOptions.rpcUrl
|
|
4511
|
+
});
|
|
4512
|
+
try {
|
|
4513
|
+
const submitTx = bazaarClient.prepareSubmitErc20Listing(
|
|
4514
|
+
orderData.orderParameters,
|
|
4515
|
+
BigInt(orderData.counter),
|
|
4516
|
+
options.signature
|
|
4517
|
+
);
|
|
4518
|
+
const rpcUrls = getChainRpcUrls({
|
|
4519
|
+
chainId: commonOptions.chainId,
|
|
4520
|
+
rpcUrl: commonOptions.rpcUrl
|
|
4521
|
+
});
|
|
4522
|
+
const walletClient = createWalletClient({
|
|
4523
|
+
account,
|
|
4524
|
+
transport: http(rpcUrls[0])
|
|
4525
|
+
});
|
|
4526
|
+
console.log(chalk4.blue("Submitting ERC-20 listing..."));
|
|
4527
|
+
const calldata = encodeFunctionData({
|
|
4528
|
+
abi: submitTx.abi,
|
|
4529
|
+
functionName: submitTx.functionName,
|
|
4530
|
+
args: submitTx.args
|
|
4531
|
+
});
|
|
4532
|
+
const hash = await walletClient.sendTransaction({
|
|
4533
|
+
to: submitTx.to,
|
|
4534
|
+
data: calldata,
|
|
4535
|
+
chain: null,
|
|
4536
|
+
value: submitTx.value
|
|
4537
|
+
});
|
|
4538
|
+
console.log(chalk4.green(`ERC-20 listing submitted successfully!
|
|
4539
|
+
Transaction: ${hash}`));
|
|
4540
|
+
} catch (error) {
|
|
4541
|
+
exitWithError(
|
|
4542
|
+
`Failed to submit ERC-20 listing: ${error instanceof Error ? error.message : String(error)}`
|
|
4543
|
+
);
|
|
4544
|
+
}
|
|
4545
|
+
}
|
|
4546
|
+
async function executeEncodeOnly7(options, orderData) {
|
|
4547
|
+
const readOnlyOptions = parseReadOnlyOptions({
|
|
4548
|
+
chainId: options.chainId,
|
|
4549
|
+
rpcUrl: options.rpcUrl
|
|
4550
|
+
});
|
|
4551
|
+
const bazaarClient = new BazaarClient({
|
|
4552
|
+
chainId: readOnlyOptions.chainId,
|
|
4553
|
+
rpcUrl: readOnlyOptions.rpcUrl
|
|
4554
|
+
});
|
|
4555
|
+
const submitTx = bazaarClient.prepareSubmitErc20Listing(
|
|
4556
|
+
orderData.orderParameters,
|
|
4557
|
+
BigInt(orderData.counter),
|
|
4558
|
+
options.signature
|
|
4559
|
+
);
|
|
4560
|
+
const encoded = encodeTransaction(
|
|
4561
|
+
{ to: submitTx.to, functionName: submitTx.functionName, args: submitTx.args, abi: submitTx.abi, value: submitTx.value },
|
|
4562
|
+
readOnlyOptions.chainId
|
|
4563
|
+
);
|
|
4564
|
+
console.log(JSON.stringify(encoded, null, 2));
|
|
4565
|
+
}
|
|
4566
|
+
async function executeSubmitErc20Offer(options) {
|
|
4567
|
+
let orderData;
|
|
4568
|
+
try {
|
|
4569
|
+
const raw = readFileSync(options.orderData, "utf-8");
|
|
4570
|
+
orderData = JSON.parse(raw);
|
|
4571
|
+
} catch (error) {
|
|
4572
|
+
exitWithError(
|
|
4573
|
+
`Failed to read order data file: ${error instanceof Error ? error.message : String(error)}`
|
|
4574
|
+
);
|
|
4575
|
+
}
|
|
4576
|
+
if (options.encodeOnly) {
|
|
4577
|
+
await executeEncodeOnly8(options, orderData);
|
|
4578
|
+
return;
|
|
4579
|
+
}
|
|
4580
|
+
const commonOptions = parseCommonOptions(
|
|
4581
|
+
{
|
|
4582
|
+
privateKey: options.privateKey,
|
|
4583
|
+
chainId: options.chainId,
|
|
4584
|
+
rpcUrl: options.rpcUrl
|
|
4585
|
+
},
|
|
4586
|
+
true
|
|
4587
|
+
);
|
|
4588
|
+
const account = privateKeyToAccount(commonOptions.privateKey);
|
|
4589
|
+
const bazaarClient = new BazaarClient({
|
|
4590
|
+
chainId: commonOptions.chainId,
|
|
4591
|
+
rpcUrl: commonOptions.rpcUrl
|
|
4592
|
+
});
|
|
4593
|
+
try {
|
|
4594
|
+
const submitTx = bazaarClient.prepareSubmitErc20Offer(
|
|
4595
|
+
orderData.orderParameters,
|
|
4596
|
+
BigInt(orderData.counter),
|
|
4597
|
+
options.signature
|
|
4598
|
+
);
|
|
4599
|
+
const rpcUrls = getChainRpcUrls({
|
|
4600
|
+
chainId: commonOptions.chainId,
|
|
4601
|
+
rpcUrl: commonOptions.rpcUrl
|
|
4602
|
+
});
|
|
4603
|
+
const walletClient = createWalletClient({
|
|
4604
|
+
account,
|
|
4605
|
+
transport: http(rpcUrls[0])
|
|
4606
|
+
});
|
|
4607
|
+
console.log(chalk4.blue("Submitting ERC-20 offer..."));
|
|
4608
|
+
const calldata = encodeFunctionData({
|
|
4609
|
+
abi: submitTx.abi,
|
|
4610
|
+
functionName: submitTx.functionName,
|
|
4611
|
+
args: submitTx.args
|
|
4612
|
+
});
|
|
4613
|
+
const hash = await walletClient.sendTransaction({
|
|
4614
|
+
to: submitTx.to,
|
|
4615
|
+
data: calldata,
|
|
4616
|
+
chain: null,
|
|
4617
|
+
value: submitTx.value
|
|
4618
|
+
});
|
|
4619
|
+
console.log(chalk4.green(`ERC-20 offer submitted successfully!
|
|
4620
|
+
Transaction: ${hash}`));
|
|
4621
|
+
} catch (error) {
|
|
4622
|
+
exitWithError(
|
|
4623
|
+
`Failed to submit ERC-20 offer: ${error instanceof Error ? error.message : String(error)}`
|
|
4624
|
+
);
|
|
4625
|
+
}
|
|
4626
|
+
}
|
|
4627
|
+
async function executeEncodeOnly8(options, orderData) {
|
|
4628
|
+
const readOnlyOptions = parseReadOnlyOptions({
|
|
4629
|
+
chainId: options.chainId,
|
|
4630
|
+
rpcUrl: options.rpcUrl
|
|
4631
|
+
});
|
|
4632
|
+
const bazaarClient = new BazaarClient({
|
|
4633
|
+
chainId: readOnlyOptions.chainId,
|
|
4634
|
+
rpcUrl: readOnlyOptions.rpcUrl
|
|
4635
|
+
});
|
|
4636
|
+
const submitTx = bazaarClient.prepareSubmitErc20Offer(
|
|
4637
|
+
orderData.orderParameters,
|
|
4638
|
+
BigInt(orderData.counter),
|
|
4639
|
+
options.signature
|
|
4640
|
+
);
|
|
4641
|
+
const encoded = encodeTransaction(
|
|
4642
|
+
{ to: submitTx.to, functionName: submitTx.functionName, args: submitTx.args, abi: submitTx.abi, value: submitTx.value },
|
|
4643
|
+
readOnlyOptions.chainId
|
|
4644
|
+
);
|
|
4645
|
+
console.log(JSON.stringify(encoded, null, 2));
|
|
4646
|
+
}
|
|
4647
|
+
async function executeBuyErc20Listing(options) {
|
|
4648
|
+
if (options.encodeOnly) {
|
|
4649
|
+
await executeEncodeOnly9(options);
|
|
4650
|
+
return;
|
|
4651
|
+
}
|
|
4652
|
+
const commonOptions = parseCommonOptions(
|
|
4653
|
+
{
|
|
4654
|
+
privateKey: options.privateKey,
|
|
4655
|
+
chainId: options.chainId,
|
|
4656
|
+
rpcUrl: options.rpcUrl
|
|
4657
|
+
},
|
|
4658
|
+
true
|
|
4659
|
+
);
|
|
4660
|
+
const account = privateKeyToAccount(commonOptions.privateKey);
|
|
4661
|
+
const bazaarClient = new BazaarClient({
|
|
4662
|
+
chainId: commonOptions.chainId,
|
|
4663
|
+
rpcUrl: commonOptions.rpcUrl
|
|
4664
|
+
});
|
|
4665
|
+
try {
|
|
4666
|
+
console.log(chalk4.blue("Fetching ERC-20 listing..."));
|
|
4667
|
+
const listings = await bazaarClient.getErc20Listings({
|
|
4668
|
+
tokenAddress: options.tokenAddress
|
|
4669
|
+
});
|
|
4670
|
+
const listing = listings.find(
|
|
4671
|
+
(l) => l.orderHash.toLowerCase() === options.orderHash.toLowerCase()
|
|
4672
|
+
);
|
|
4673
|
+
if (!listing) {
|
|
4674
|
+
exitWithError(`ERC-20 listing with order hash ${options.orderHash} not found or no longer active`);
|
|
4675
|
+
}
|
|
4676
|
+
console.log(chalk4.blue("Preparing fulfillment..."));
|
|
4677
|
+
const prepared = await bazaarClient.prepareFulfillErc20Listing(listing, account.address);
|
|
4678
|
+
const rpcUrls = getChainRpcUrls({
|
|
4679
|
+
chainId: commonOptions.chainId,
|
|
4680
|
+
rpcUrl: commonOptions.rpcUrl
|
|
4681
|
+
});
|
|
4682
|
+
const walletClient = createWalletClient({
|
|
4683
|
+
account,
|
|
4684
|
+
transport: http(rpcUrls[0])
|
|
4685
|
+
});
|
|
4686
|
+
for (const approval of prepared.approvals) {
|
|
4687
|
+
console.log(chalk4.blue("Sending approval transaction..."));
|
|
4688
|
+
const calldata2 = encodeFunctionData({
|
|
4689
|
+
abi: approval.abi,
|
|
4690
|
+
functionName: approval.functionName,
|
|
4691
|
+
args: approval.args
|
|
4692
|
+
});
|
|
4693
|
+
const approvalHash = await walletClient.sendTransaction({
|
|
4694
|
+
to: approval.to,
|
|
4695
|
+
data: calldata2,
|
|
4696
|
+
chain: null,
|
|
4697
|
+
value: approval.value
|
|
4698
|
+
});
|
|
4699
|
+
console.log(chalk4.green(`Approval tx: ${approvalHash}`));
|
|
4700
|
+
}
|
|
4701
|
+
console.log(chalk4.blue("Sending fulfillment transaction..."));
|
|
4702
|
+
const calldata = encodeFunctionData({
|
|
4703
|
+
abi: prepared.fulfillment.abi,
|
|
4704
|
+
functionName: prepared.fulfillment.functionName,
|
|
4705
|
+
args: prepared.fulfillment.args
|
|
4706
|
+
});
|
|
4707
|
+
const hash = await walletClient.sendTransaction({
|
|
4708
|
+
to: prepared.fulfillment.to,
|
|
4709
|
+
data: calldata,
|
|
4710
|
+
chain: null,
|
|
4711
|
+
value: prepared.fulfillment.value
|
|
4712
|
+
});
|
|
4713
|
+
console.log(
|
|
4714
|
+
chalk4.green(
|
|
4715
|
+
`ERC-20 listing fulfilled successfully!
|
|
4716
|
+
Transaction: ${hash}
|
|
4717
|
+
Token: ${listing.tokenAddress}
|
|
4718
|
+
Amount: ${listing.tokenAmount.toString()}
|
|
4719
|
+
Price: ${listing.price} ${listing.currency.toUpperCase()}`
|
|
4720
|
+
)
|
|
4721
|
+
);
|
|
4722
|
+
} catch (error) {
|
|
4723
|
+
exitWithError(
|
|
4724
|
+
`Failed to buy ERC-20 listing: ${error instanceof Error ? error.message : String(error)}`
|
|
4725
|
+
);
|
|
4726
|
+
}
|
|
4727
|
+
}
|
|
4728
|
+
async function executeEncodeOnly9(options) {
|
|
4729
|
+
if (!options.buyer) {
|
|
4730
|
+
exitWithError("--buyer is required when using --encode-only without --private-key");
|
|
4731
|
+
}
|
|
4732
|
+
const readOnlyOptions = parseReadOnlyOptions({
|
|
4733
|
+
chainId: options.chainId,
|
|
4734
|
+
rpcUrl: options.rpcUrl
|
|
4735
|
+
});
|
|
4736
|
+
const buyerAddress = options.buyer;
|
|
4737
|
+
const bazaarClient = new BazaarClient({
|
|
4738
|
+
chainId: readOnlyOptions.chainId,
|
|
4739
|
+
rpcUrl: readOnlyOptions.rpcUrl
|
|
4740
|
+
});
|
|
4741
|
+
try {
|
|
4742
|
+
const listings = await bazaarClient.getErc20Listings({
|
|
4743
|
+
tokenAddress: options.tokenAddress
|
|
4744
|
+
});
|
|
4745
|
+
const listing = listings.find(
|
|
4746
|
+
(l) => l.orderHash.toLowerCase() === options.orderHash.toLowerCase()
|
|
4747
|
+
);
|
|
4748
|
+
if (!listing) {
|
|
4749
|
+
exitWithError(`ERC-20 listing with order hash ${options.orderHash} not found or no longer active`);
|
|
4750
|
+
}
|
|
4751
|
+
const prepared = await bazaarClient.prepareFulfillErc20Listing(listing, buyerAddress);
|
|
4752
|
+
const result = {
|
|
4753
|
+
approvals: prepared.approvals.map(
|
|
4754
|
+
(a) => encodeTransaction(
|
|
4755
|
+
{ to: a.to, functionName: a.functionName, args: a.args, abi: a.abi, value: a.value },
|
|
4756
|
+
readOnlyOptions.chainId
|
|
4757
|
+
)
|
|
4758
|
+
),
|
|
4759
|
+
fulfillment: encodeTransaction(
|
|
4760
|
+
{
|
|
4761
|
+
to: prepared.fulfillment.to,
|
|
4762
|
+
functionName: prepared.fulfillment.functionName,
|
|
4763
|
+
args: prepared.fulfillment.args,
|
|
4764
|
+
abi: prepared.fulfillment.abi,
|
|
4765
|
+
value: prepared.fulfillment.value
|
|
4766
|
+
},
|
|
4767
|
+
readOnlyOptions.chainId
|
|
4768
|
+
)
|
|
4769
|
+
};
|
|
4770
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4771
|
+
} catch (error) {
|
|
4772
|
+
exitWithError(
|
|
4773
|
+
`Failed to encode buy ERC-20 listing: ${error instanceof Error ? error.message : String(error)}`
|
|
4774
|
+
);
|
|
4775
|
+
}
|
|
4776
|
+
}
|
|
4777
|
+
async function executeAcceptErc20Offer(options) {
|
|
4778
|
+
if (options.encodeOnly) {
|
|
4779
|
+
await executeEncodeOnly10(options);
|
|
4780
|
+
return;
|
|
4781
|
+
}
|
|
4782
|
+
const commonOptions = parseCommonOptions(
|
|
4783
|
+
{
|
|
4784
|
+
privateKey: options.privateKey,
|
|
4785
|
+
chainId: options.chainId,
|
|
4786
|
+
rpcUrl: options.rpcUrl
|
|
4787
|
+
},
|
|
4788
|
+
true
|
|
4789
|
+
);
|
|
4790
|
+
const account = privateKeyToAccount(commonOptions.privateKey);
|
|
4791
|
+
const bazaarClient = new BazaarClient({
|
|
4792
|
+
chainId: commonOptions.chainId,
|
|
4793
|
+
rpcUrl: commonOptions.rpcUrl
|
|
4794
|
+
});
|
|
4795
|
+
try {
|
|
4796
|
+
console.log(chalk4.blue("Fetching ERC-20 offers..."));
|
|
4797
|
+
const offers = await bazaarClient.getErc20Offers({
|
|
4798
|
+
tokenAddress: options.tokenAddress
|
|
4799
|
+
});
|
|
4800
|
+
const offer = offers.find(
|
|
4801
|
+
(o) => o.orderHash.toLowerCase() === options.orderHash.toLowerCase()
|
|
4802
|
+
);
|
|
4803
|
+
if (!offer) {
|
|
4804
|
+
exitWithError(`ERC-20 offer with order hash ${options.orderHash} not found or no longer active`);
|
|
4805
|
+
}
|
|
4806
|
+
console.log(chalk4.blue("Preparing fulfillment..."));
|
|
4807
|
+
const prepared = await bazaarClient.prepareFulfillErc20Offer(
|
|
4808
|
+
offer,
|
|
4809
|
+
account.address
|
|
4810
|
+
);
|
|
4811
|
+
const rpcUrls = getChainRpcUrls({
|
|
4812
|
+
chainId: commonOptions.chainId,
|
|
4813
|
+
rpcUrl: commonOptions.rpcUrl
|
|
4814
|
+
});
|
|
4815
|
+
const walletClient = createWalletClient({
|
|
4816
|
+
account,
|
|
4817
|
+
transport: http(rpcUrls[0])
|
|
4818
|
+
});
|
|
4819
|
+
for (const approval of prepared.approvals) {
|
|
4820
|
+
console.log(chalk4.blue("Sending approval transaction..."));
|
|
4821
|
+
const calldata2 = encodeFunctionData({
|
|
4822
|
+
abi: approval.abi,
|
|
4823
|
+
functionName: approval.functionName,
|
|
4824
|
+
args: approval.args
|
|
4825
|
+
});
|
|
4826
|
+
const approvalHash = await walletClient.sendTransaction({
|
|
4827
|
+
to: approval.to,
|
|
4828
|
+
data: calldata2,
|
|
4829
|
+
chain: null,
|
|
4830
|
+
value: approval.value
|
|
4831
|
+
});
|
|
4832
|
+
console.log(chalk4.green(`Approval tx: ${approvalHash}`));
|
|
4833
|
+
}
|
|
4834
|
+
console.log(chalk4.blue("Sending fulfillment transaction..."));
|
|
4835
|
+
const calldata = encodeFunctionData({
|
|
4836
|
+
abi: prepared.fulfillment.abi,
|
|
4837
|
+
functionName: prepared.fulfillment.functionName,
|
|
4838
|
+
args: prepared.fulfillment.args
|
|
4839
|
+
});
|
|
4840
|
+
const hash = await walletClient.sendTransaction({
|
|
4841
|
+
to: prepared.fulfillment.to,
|
|
4842
|
+
data: calldata,
|
|
4843
|
+
chain: null,
|
|
4844
|
+
value: prepared.fulfillment.value
|
|
4845
|
+
});
|
|
4846
|
+
console.log(
|
|
4847
|
+
chalk4.green(
|
|
4848
|
+
`ERC-20 offer accepted successfully!
|
|
4849
|
+
Transaction: ${hash}
|
|
4850
|
+
Token: ${offer.tokenAddress}
|
|
4851
|
+
Amount: ${offer.tokenAmount.toString()}
|
|
4852
|
+
Price: ${offer.price} ${offer.currency.toUpperCase()}`
|
|
4853
|
+
)
|
|
4854
|
+
);
|
|
4855
|
+
} catch (error) {
|
|
4856
|
+
exitWithError(
|
|
4857
|
+
`Failed to accept ERC-20 offer: ${error instanceof Error ? error.message : String(error)}`
|
|
4858
|
+
);
|
|
4859
|
+
}
|
|
4860
|
+
}
|
|
4861
|
+
async function executeEncodeOnly10(options) {
|
|
4862
|
+
if (!options.seller) {
|
|
4863
|
+
exitWithError("--seller is required when using --encode-only without --private-key");
|
|
4864
|
+
}
|
|
4865
|
+
const readOnlyOptions = parseReadOnlyOptions({
|
|
4866
|
+
chainId: options.chainId,
|
|
4867
|
+
rpcUrl: options.rpcUrl
|
|
4868
|
+
});
|
|
4869
|
+
const sellerAddress = options.seller;
|
|
4870
|
+
const bazaarClient = new BazaarClient({
|
|
4871
|
+
chainId: readOnlyOptions.chainId,
|
|
4872
|
+
rpcUrl: readOnlyOptions.rpcUrl
|
|
4873
|
+
});
|
|
4874
|
+
try {
|
|
4875
|
+
const offers = await bazaarClient.getErc20Offers({
|
|
4876
|
+
tokenAddress: options.tokenAddress
|
|
4877
|
+
});
|
|
4878
|
+
const offer = offers.find(
|
|
4879
|
+
(o) => o.orderHash.toLowerCase() === options.orderHash.toLowerCase()
|
|
4880
|
+
);
|
|
4881
|
+
if (!offer) {
|
|
4882
|
+
exitWithError(`ERC-20 offer with order hash ${options.orderHash} not found or no longer active`);
|
|
4883
|
+
}
|
|
4884
|
+
const prepared = await bazaarClient.prepareFulfillErc20Offer(
|
|
4885
|
+
offer,
|
|
4886
|
+
sellerAddress
|
|
4887
|
+
);
|
|
4888
|
+
const result = {
|
|
4889
|
+
approvals: prepared.approvals.map(
|
|
4890
|
+
(a) => encodeTransaction(
|
|
4891
|
+
{ to: a.to, functionName: a.functionName, args: a.args, abi: a.abi, value: a.value },
|
|
4892
|
+
readOnlyOptions.chainId
|
|
4893
|
+
)
|
|
4894
|
+
),
|
|
4895
|
+
fulfillment: encodeTransaction(
|
|
4896
|
+
{
|
|
4897
|
+
to: prepared.fulfillment.to,
|
|
4898
|
+
functionName: prepared.fulfillment.functionName,
|
|
4899
|
+
args: prepared.fulfillment.args,
|
|
4900
|
+
abi: prepared.fulfillment.abi,
|
|
4901
|
+
value: prepared.fulfillment.value
|
|
4902
|
+
},
|
|
4903
|
+
readOnlyOptions.chainId
|
|
4904
|
+
)
|
|
4905
|
+
};
|
|
4906
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4907
|
+
} catch (error) {
|
|
4908
|
+
exitWithError(
|
|
4909
|
+
`Failed to encode accept ERC-20 offer: ${error instanceof Error ? error.message : String(error)}`
|
|
4910
|
+
);
|
|
4911
|
+
}
|
|
4912
|
+
}
|
|
4913
|
+
|
|
4914
|
+
// src/commands/bazaar/index.ts
|
|
4915
|
+
var chainIdOption = [
|
|
4916
|
+
"--chain-id <id>",
|
|
4917
|
+
"Chain ID. Can also be set via NET_CHAIN_ID env var",
|
|
4918
|
+
(value) => parseInt(value, 10)
|
|
4919
|
+
];
|
|
4920
|
+
var rpcUrlOption = [
|
|
4921
|
+
"--rpc-url <url>",
|
|
4922
|
+
"Custom RPC URL. Can also be set via NET_RPC_URL env var"
|
|
4923
|
+
];
|
|
4924
|
+
var privateKeyOption = [
|
|
4925
|
+
"--private-key <key>",
|
|
4926
|
+
"Private key (0x-prefixed hex). Can also be set via NET_PRIVATE_KEY env var"
|
|
4927
|
+
];
|
|
4928
|
+
function registerBazaarCommand(program2) {
|
|
4929
|
+
const bazaarCommand = program2.command("bazaar").description("Bazaar NFT trading operations");
|
|
4930
|
+
const listListingsCommand = new Command("list-listings").description("List active NFT listings").option("--nft-address <address>", "NFT contract address (optional for cross-collection)").option(...chainIdOption).option(...rpcUrlOption).option("--json", "Output in JSON format").action(async (options) => {
|
|
4931
|
+
await executeListListings({
|
|
4932
|
+
nftAddress: options.nftAddress,
|
|
4933
|
+
chainId: options.chainId,
|
|
4934
|
+
rpcUrl: options.rpcUrl,
|
|
4935
|
+
json: options.json
|
|
4936
|
+
});
|
|
4937
|
+
});
|
|
4938
|
+
const listOffersCommand = new Command("list-offers").description("List active collection offers").requiredOption("--nft-address <address>", "NFT contract address").option(...chainIdOption).option(...rpcUrlOption).option("--json", "Output in JSON format").action(async (options) => {
|
|
4939
|
+
await executeListOffers({
|
|
4940
|
+
nftAddress: options.nftAddress,
|
|
4941
|
+
chainId: options.chainId,
|
|
4942
|
+
rpcUrl: options.rpcUrl,
|
|
4943
|
+
json: options.json
|
|
4944
|
+
});
|
|
4945
|
+
});
|
|
4946
|
+
const listSalesCommand = new Command("list-sales").description("List recent sales").requiredOption("--nft-address <address>", "NFT contract address").option(...chainIdOption).option(...rpcUrlOption).option("--json", "Output in JSON format").action(async (options) => {
|
|
4947
|
+
await executeListSales({
|
|
4948
|
+
nftAddress: options.nftAddress,
|
|
4949
|
+
chainId: options.chainId,
|
|
4950
|
+
rpcUrl: options.rpcUrl,
|
|
4951
|
+
json: options.json
|
|
4952
|
+
});
|
|
4953
|
+
});
|
|
4954
|
+
const createListingCommand = new Command("create-listing").description("Create an NFT listing (with --private-key: full flow; without: output EIP-712 data)").requiredOption("--nft-address <address>", "NFT contract address").requiredOption("--token-id <id>", "Token ID to list").requiredOption("--price <eth>", "Price in ETH (e.g., 0.1)").option("--target-fulfiller <address>", "Make a private listing for this address").option("--offerer <address>", "Offerer address (required without --private-key)").option(...privateKeyOption).option(...chainIdOption).option(...rpcUrlOption).action(async (options) => {
|
|
4955
|
+
await executeCreateListing({
|
|
4956
|
+
nftAddress: options.nftAddress,
|
|
4957
|
+
tokenId: options.tokenId,
|
|
4958
|
+
price: options.price,
|
|
4959
|
+
targetFulfiller: options.targetFulfiller,
|
|
4960
|
+
offerer: options.offerer,
|
|
4961
|
+
privateKey: options.privateKey,
|
|
4962
|
+
chainId: options.chainId,
|
|
4963
|
+
rpcUrl: options.rpcUrl
|
|
4964
|
+
});
|
|
4965
|
+
});
|
|
4966
|
+
const createOfferCommand = new Command("create-offer").description("Create a collection offer (with --private-key: full flow; without: output EIP-712 data)").requiredOption("--nft-address <address>", "NFT contract address").requiredOption("--price <eth>", "Offer price in ETH (e.g., 0.1)").option("--offerer <address>", "Offerer address (required without --private-key)").option(...privateKeyOption).option(...chainIdOption).option(...rpcUrlOption).action(async (options) => {
|
|
4967
|
+
await executeCreateOffer({
|
|
4968
|
+
nftAddress: options.nftAddress,
|
|
4969
|
+
price: options.price,
|
|
4970
|
+
offerer: options.offerer,
|
|
4971
|
+
privateKey: options.privateKey,
|
|
4972
|
+
chainId: options.chainId,
|
|
4973
|
+
rpcUrl: options.rpcUrl
|
|
4974
|
+
});
|
|
4975
|
+
});
|
|
4976
|
+
const submitListingCommand = new Command("submit-listing").description("Submit a signed listing (follow-up to create-listing without --private-key)").requiredOption("--order-data <path>", "Path to order JSON file from create-listing output").requiredOption("--signature <sig>", "EIP-712 signature (0x-prefixed)").option(...privateKeyOption).option(...chainIdOption).option(...rpcUrlOption).option("--encode-only", "Output transaction data as JSON instead of executing").action(async (options) => {
|
|
4977
|
+
await executeSubmitListing({
|
|
4978
|
+
orderData: options.orderData,
|
|
4979
|
+
signature: options.signature,
|
|
4980
|
+
privateKey: options.privateKey,
|
|
4981
|
+
chainId: options.chainId,
|
|
4982
|
+
rpcUrl: options.rpcUrl,
|
|
4983
|
+
encodeOnly: options.encodeOnly
|
|
4984
|
+
});
|
|
4985
|
+
});
|
|
4986
|
+
const submitOfferCommand = new Command("submit-offer").description("Submit a signed offer (follow-up to create-offer without --private-key)").requiredOption("--order-data <path>", "Path to order JSON file from create-offer output").requiredOption("--signature <sig>", "EIP-712 signature (0x-prefixed)").option(...privateKeyOption).option(...chainIdOption).option(...rpcUrlOption).option("--encode-only", "Output transaction data as JSON instead of executing").action(async (options) => {
|
|
4987
|
+
await executeSubmitOffer({
|
|
4988
|
+
orderData: options.orderData,
|
|
4989
|
+
signature: options.signature,
|
|
4990
|
+
privateKey: options.privateKey,
|
|
4991
|
+
chainId: options.chainId,
|
|
4992
|
+
rpcUrl: options.rpcUrl,
|
|
4993
|
+
encodeOnly: options.encodeOnly
|
|
4994
|
+
});
|
|
4995
|
+
});
|
|
4996
|
+
const buyListingCommand = new Command("buy-listing").description("Buy an NFT listing").requiredOption("--order-hash <hash>", "Order hash of the listing to buy").requiredOption("--nft-address <address>", "NFT contract address").option("--buyer <address>", "Buyer address (required with --encode-only)").option(...privateKeyOption).option(...chainIdOption).option(...rpcUrlOption).option("--encode-only", "Output transaction data as JSON instead of executing").action(async (options) => {
|
|
4997
|
+
await executeBuyListing({
|
|
4998
|
+
orderHash: options.orderHash,
|
|
4999
|
+
nftAddress: options.nftAddress,
|
|
5000
|
+
buyer: options.buyer,
|
|
5001
|
+
privateKey: options.privateKey,
|
|
5002
|
+
chainId: options.chainId,
|
|
5003
|
+
rpcUrl: options.rpcUrl,
|
|
5004
|
+
encodeOnly: options.encodeOnly
|
|
5005
|
+
});
|
|
5006
|
+
});
|
|
5007
|
+
const acceptOfferCommand = new Command("accept-offer").description("Accept a collection offer by selling your NFT").requiredOption("--order-hash <hash>", "Order hash of the offer to accept").requiredOption("--nft-address <address>", "NFT contract address").requiredOption("--token-id <id>", "Token ID to sell").option("--seller <address>", "Seller address (required with --encode-only)").option(...privateKeyOption).option(...chainIdOption).option(...rpcUrlOption).option("--encode-only", "Output transaction data as JSON instead of executing").action(async (options) => {
|
|
5008
|
+
await executeAcceptOffer({
|
|
5009
|
+
orderHash: options.orderHash,
|
|
5010
|
+
nftAddress: options.nftAddress,
|
|
5011
|
+
tokenId: options.tokenId,
|
|
5012
|
+
seller: options.seller,
|
|
5013
|
+
privateKey: options.privateKey,
|
|
5014
|
+
chainId: options.chainId,
|
|
5015
|
+
rpcUrl: options.rpcUrl,
|
|
5016
|
+
encodeOnly: options.encodeOnly
|
|
5017
|
+
});
|
|
5018
|
+
});
|
|
5019
|
+
const ownedNftsCommand = new Command("owned-nfts").description("List NFTs owned by an address").requiredOption("--nft-address <address>", "NFT contract address").requiredOption("--owner <address>", "Owner address to check").option(...chainIdOption).option(...rpcUrlOption).option("--json", "Output in JSON format").option("--start-token-id <id>", "Start of token ID range (default: 0)").option("--end-token-id <id>", "End of token ID range (default: 10000)").action(async (options) => {
|
|
5020
|
+
await executeOwnedNfts({
|
|
5021
|
+
nftAddress: options.nftAddress,
|
|
5022
|
+
owner: options.owner,
|
|
5023
|
+
chainId: options.chainId,
|
|
5024
|
+
rpcUrl: options.rpcUrl,
|
|
5025
|
+
json: options.json,
|
|
5026
|
+
startTokenId: options.startTokenId,
|
|
5027
|
+
endTokenId: options.endTokenId
|
|
5028
|
+
});
|
|
5029
|
+
});
|
|
5030
|
+
const listErc20ListingsCommand = new Command("list-erc20-listings").description("List active ERC-20 token listings").requiredOption("--token-address <address>", "ERC-20 token contract address").option(...chainIdOption).option(...rpcUrlOption).option("--json", "Output in JSON format").action(async (options) => {
|
|
5031
|
+
await executeListErc20Listings({
|
|
5032
|
+
tokenAddress: options.tokenAddress,
|
|
5033
|
+
chainId: options.chainId,
|
|
5034
|
+
rpcUrl: options.rpcUrl,
|
|
5035
|
+
json: options.json
|
|
5036
|
+
});
|
|
5037
|
+
});
|
|
5038
|
+
const listErc20OffersCommand = new Command("list-erc20-offers").description("List active ERC-20 token offers").requiredOption("--token-address <address>", "ERC-20 token contract address").option(...chainIdOption).option(...rpcUrlOption).option("--json", "Output in JSON format").action(async (options) => {
|
|
5039
|
+
await executeListErc20Offers({
|
|
5040
|
+
tokenAddress: options.tokenAddress,
|
|
5041
|
+
chainId: options.chainId,
|
|
5042
|
+
rpcUrl: options.rpcUrl,
|
|
5043
|
+
json: options.json
|
|
5044
|
+
});
|
|
5045
|
+
});
|
|
5046
|
+
const createErc20ListingCommand = new Command("create-erc20-listing").description("Create an ERC-20 token listing (with --private-key: full flow; without: output EIP-712 data)").requiredOption("--token-address <address>", "ERC-20 token contract address").requiredOption("--token-amount <amount>", "Token amount in raw units (bigint string)").requiredOption("--price <eth>", "Total price in ETH for the token amount").option("--offerer <address>", "Offerer address (required without --private-key)").option(...privateKeyOption).option(...chainIdOption).option(...rpcUrlOption).action(async (options) => {
|
|
5047
|
+
await executeCreateErc20Listing({
|
|
5048
|
+
tokenAddress: options.tokenAddress,
|
|
5049
|
+
tokenAmount: options.tokenAmount,
|
|
5050
|
+
price: options.price,
|
|
5051
|
+
offerer: options.offerer,
|
|
5052
|
+
privateKey: options.privateKey,
|
|
5053
|
+
chainId: options.chainId,
|
|
5054
|
+
rpcUrl: options.rpcUrl
|
|
5055
|
+
});
|
|
5056
|
+
});
|
|
5057
|
+
const createErc20OfferCommand = new Command("create-erc20-offer").description("Create an ERC-20 token offer (with --private-key: full flow; without: output EIP-712 data)").requiredOption("--token-address <address>", "ERC-20 token contract address").requiredOption("--token-amount <amount>", "Token amount in raw units (bigint string)").requiredOption("--price <eth>", "Total price in ETH for the token amount").option("--offerer <address>", "Offerer address (required without --private-key)").option(...privateKeyOption).option(...chainIdOption).option(...rpcUrlOption).action(async (options) => {
|
|
5058
|
+
await executeCreateErc20Offer({
|
|
5059
|
+
tokenAddress: options.tokenAddress,
|
|
5060
|
+
tokenAmount: options.tokenAmount,
|
|
5061
|
+
price: options.price,
|
|
5062
|
+
offerer: options.offerer,
|
|
5063
|
+
privateKey: options.privateKey,
|
|
5064
|
+
chainId: options.chainId,
|
|
5065
|
+
rpcUrl: options.rpcUrl
|
|
5066
|
+
});
|
|
5067
|
+
});
|
|
5068
|
+
const submitErc20ListingCommand = new Command("submit-erc20-listing").description("Submit a signed ERC-20 listing (follow-up to create-erc20-listing without --private-key)").requiredOption("--order-data <path>", "Path to order JSON file from create-erc20-listing output").requiredOption("--signature <sig>", "EIP-712 signature (0x-prefixed)").option(...privateKeyOption).option(...chainIdOption).option(...rpcUrlOption).option("--encode-only", "Output transaction data as JSON instead of executing").action(async (options) => {
|
|
5069
|
+
await executeSubmitErc20Listing({
|
|
5070
|
+
orderData: options.orderData,
|
|
5071
|
+
signature: options.signature,
|
|
5072
|
+
privateKey: options.privateKey,
|
|
5073
|
+
chainId: options.chainId,
|
|
5074
|
+
rpcUrl: options.rpcUrl,
|
|
5075
|
+
encodeOnly: options.encodeOnly
|
|
5076
|
+
});
|
|
5077
|
+
});
|
|
5078
|
+
const submitErc20OfferCommand = new Command("submit-erc20-offer").description("Submit a signed ERC-20 offer (follow-up to create-erc20-offer without --private-key)").requiredOption("--order-data <path>", "Path to order JSON file from create-erc20-offer output").requiredOption("--signature <sig>", "EIP-712 signature (0x-prefixed)").option(...privateKeyOption).option(...chainIdOption).option(...rpcUrlOption).option("--encode-only", "Output transaction data as JSON instead of executing").action(async (options) => {
|
|
5079
|
+
await executeSubmitErc20Offer({
|
|
5080
|
+
orderData: options.orderData,
|
|
5081
|
+
signature: options.signature,
|
|
5082
|
+
privateKey: options.privateKey,
|
|
5083
|
+
chainId: options.chainId,
|
|
5084
|
+
rpcUrl: options.rpcUrl,
|
|
5085
|
+
encodeOnly: options.encodeOnly
|
|
5086
|
+
});
|
|
5087
|
+
});
|
|
5088
|
+
const buyErc20ListingCommand = new Command("buy-erc20-listing").description("Buy an ERC-20 token listing").requiredOption("--order-hash <hash>", "Order hash of the listing to buy").requiredOption("--token-address <address>", "ERC-20 token contract address").option("--buyer <address>", "Buyer address (required with --encode-only)").option(...privateKeyOption).option(...chainIdOption).option(...rpcUrlOption).option("--encode-only", "Output transaction data as JSON instead of executing").action(async (options) => {
|
|
5089
|
+
await executeBuyErc20Listing({
|
|
5090
|
+
orderHash: options.orderHash,
|
|
5091
|
+
tokenAddress: options.tokenAddress,
|
|
5092
|
+
buyer: options.buyer,
|
|
5093
|
+
privateKey: options.privateKey,
|
|
5094
|
+
chainId: options.chainId,
|
|
5095
|
+
rpcUrl: options.rpcUrl,
|
|
5096
|
+
encodeOnly: options.encodeOnly
|
|
5097
|
+
});
|
|
5098
|
+
});
|
|
5099
|
+
const acceptErc20OfferCommand = new Command("accept-erc20-offer").description("Accept an ERC-20 token offer by selling your tokens").requiredOption("--order-hash <hash>", "Order hash of the offer to accept").requiredOption("--token-address <address>", "ERC-20 token contract address").option("--seller <address>", "Seller address (required with --encode-only)").option(...privateKeyOption).option(...chainIdOption).option(...rpcUrlOption).option("--encode-only", "Output transaction data as JSON instead of executing").action(async (options) => {
|
|
5100
|
+
await executeAcceptErc20Offer({
|
|
5101
|
+
orderHash: options.orderHash,
|
|
5102
|
+
tokenAddress: options.tokenAddress,
|
|
5103
|
+
seller: options.seller,
|
|
5104
|
+
privateKey: options.privateKey,
|
|
5105
|
+
chainId: options.chainId,
|
|
5106
|
+
rpcUrl: options.rpcUrl,
|
|
5107
|
+
encodeOnly: options.encodeOnly
|
|
5108
|
+
});
|
|
5109
|
+
});
|
|
5110
|
+
bazaarCommand.addCommand(listListingsCommand);
|
|
5111
|
+
bazaarCommand.addCommand(listOffersCommand);
|
|
5112
|
+
bazaarCommand.addCommand(listSalesCommand);
|
|
5113
|
+
bazaarCommand.addCommand(createListingCommand);
|
|
5114
|
+
bazaarCommand.addCommand(createOfferCommand);
|
|
5115
|
+
bazaarCommand.addCommand(submitListingCommand);
|
|
5116
|
+
bazaarCommand.addCommand(submitOfferCommand);
|
|
5117
|
+
bazaarCommand.addCommand(buyListingCommand);
|
|
5118
|
+
bazaarCommand.addCommand(acceptOfferCommand);
|
|
5119
|
+
bazaarCommand.addCommand(ownedNftsCommand);
|
|
5120
|
+
bazaarCommand.addCommand(listErc20ListingsCommand);
|
|
5121
|
+
bazaarCommand.addCommand(listErc20OffersCommand);
|
|
5122
|
+
bazaarCommand.addCommand(createErc20ListingCommand);
|
|
5123
|
+
bazaarCommand.addCommand(createErc20OfferCommand);
|
|
5124
|
+
bazaarCommand.addCommand(submitErc20ListingCommand);
|
|
5125
|
+
bazaarCommand.addCommand(submitErc20OfferCommand);
|
|
5126
|
+
bazaarCommand.addCommand(buyErc20ListingCommand);
|
|
5127
|
+
bazaarCommand.addCommand(acceptErc20OfferCommand);
|
|
5128
|
+
}
|
|
5129
|
+
function truncateAddress(address) {
|
|
5130
|
+
if (address.length <= 12) return address;
|
|
5131
|
+
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
5132
|
+
}
|
|
5133
|
+
function formatTimestamp(timestamp) {
|
|
5134
|
+
return new Date(Number(timestamp) * 1e3).toISOString().replace("T", " ").slice(0, 19);
|
|
5135
|
+
}
|
|
5136
|
+
function parseTopic(topic) {
|
|
5137
|
+
const commentMatch = topic.match(/^(.+?):comments:/);
|
|
5138
|
+
if (commentMatch) {
|
|
5139
|
+
return { feedName: commentMatch[1], isComment: true };
|
|
5140
|
+
}
|
|
5141
|
+
return { feedName: topic, isComment: false };
|
|
5142
|
+
}
|
|
5143
|
+
function formatPost(post, index, options = {}) {
|
|
5144
|
+
const { commentCount, showTopic } = options;
|
|
5145
|
+
const timestamp = formatTimestamp(post.timestamp);
|
|
5146
|
+
const lines = [
|
|
5147
|
+
chalk4.cyan(`[${index}]`) + ` ${chalk4.gray(timestamp)}`,
|
|
5148
|
+
` ${chalk4.white("Sender:")} ${post.sender}`,
|
|
5149
|
+
` ${chalk4.white("Text:")} ${post.text}`
|
|
5150
|
+
];
|
|
5151
|
+
if (showTopic && post.topic) {
|
|
5152
|
+
const { feedName, isComment } = parseTopic(post.topic);
|
|
5153
|
+
const prefix = isComment ? "Comment on" : "Feed";
|
|
5154
|
+
lines.push(` ${chalk4.white(prefix + ":")} ${chalk4.magenta(feedName)}`);
|
|
5155
|
+
}
|
|
5156
|
+
if (commentCount !== void 0) {
|
|
5157
|
+
lines.push(` ${chalk4.white("Comments:")} ${commentCount}`);
|
|
5158
|
+
}
|
|
5159
|
+
if (post.data && post.data !== "0x") {
|
|
5160
|
+
lines.push(` ${chalk4.white("Data:")} ${post.data}`);
|
|
5161
|
+
}
|
|
5162
|
+
return lines.join("\n");
|
|
5163
|
+
}
|
|
5164
|
+
function formatFeed(feed, index) {
|
|
5165
|
+
const timestamp = formatTimestamp(feed.timestamp);
|
|
5166
|
+
const lines = [
|
|
5167
|
+
chalk4.cyan(`[${index}]`) + ` ${chalk4.white(feed.feedName)}`,
|
|
5168
|
+
` ${chalk4.gray("Registrant:")} ${feed.registrant}`,
|
|
5169
|
+
` ${chalk4.gray("Registered:")} ${timestamp}`
|
|
5170
|
+
];
|
|
5171
|
+
return lines.join("\n");
|
|
5172
|
+
}
|
|
5173
|
+
function formatComment(comment, depth) {
|
|
5174
|
+
const indent = " ".repeat(depth + 1);
|
|
5175
|
+
const timestamp = formatTimestamp(comment.timestamp);
|
|
5176
|
+
const lines = [
|
|
5177
|
+
`${indent}${chalk4.gray(timestamp)} ${chalk4.blue(truncateAddress(comment.sender))}`,
|
|
5178
|
+
`${indent}${comment.text}`
|
|
5179
|
+
];
|
|
5180
|
+
return lines.join("\n");
|
|
5181
|
+
}
|
|
5182
|
+
function postToJson(post, index, commentCount) {
|
|
5183
|
+
const result = {
|
|
5184
|
+
index,
|
|
5185
|
+
sender: post.sender,
|
|
5186
|
+
text: post.text,
|
|
5187
|
+
timestamp: Number(post.timestamp),
|
|
5188
|
+
topic: post.topic
|
|
5189
|
+
};
|
|
5190
|
+
if (commentCount !== void 0) {
|
|
5191
|
+
result.commentCount = commentCount;
|
|
5192
|
+
}
|
|
5193
|
+
if (post.data && post.data !== "0x") {
|
|
5194
|
+
result.data = post.data;
|
|
5195
|
+
}
|
|
5196
|
+
return result;
|
|
5197
|
+
}
|
|
5198
|
+
function feedToJson(feed, index) {
|
|
5199
|
+
return {
|
|
5200
|
+
index,
|
|
5201
|
+
feedName: feed.feedName,
|
|
5202
|
+
registrant: feed.registrant,
|
|
5203
|
+
timestamp: feed.timestamp
|
|
5204
|
+
};
|
|
5205
|
+
}
|
|
5206
|
+
function commentToJson(comment, depth) {
|
|
5207
|
+
return {
|
|
5208
|
+
sender: comment.sender,
|
|
5209
|
+
text: comment.text,
|
|
5210
|
+
timestamp: Number(comment.timestamp),
|
|
5211
|
+
depth,
|
|
5212
|
+
data: comment.data !== "0x" ? comment.data : void 0
|
|
5213
|
+
};
|
|
5214
|
+
}
|
|
5215
|
+
function printJson(data) {
|
|
5216
|
+
console.log(JSON.stringify(data, null, 2));
|
|
5217
|
+
}
|
|
5218
|
+
|
|
5219
|
+
// src/commands/feed/list.ts
|
|
5220
|
+
async function executeFeedList(options) {
|
|
5221
|
+
const readOnlyOptions = parseReadOnlyOptionsWithDefault({
|
|
5222
|
+
chainId: options.chainId,
|
|
5223
|
+
rpcUrl: options.rpcUrl
|
|
5224
|
+
});
|
|
5225
|
+
const client = createFeedRegistryClient(readOnlyOptions);
|
|
5226
|
+
try {
|
|
5227
|
+
const feeds = await client.getRegisteredFeeds({
|
|
5228
|
+
maxFeeds: options.limit ?? 50
|
|
5229
|
+
});
|
|
5230
|
+
if (options.json) {
|
|
5231
|
+
printJson(feeds.map((feed, i) => feedToJson(feed, i)));
|
|
5232
|
+
} else {
|
|
5233
|
+
if (feeds.length === 0) {
|
|
5234
|
+
console.log(chalk4.yellow("No registered feeds found"));
|
|
5235
|
+
return;
|
|
5236
|
+
}
|
|
5237
|
+
console.log(chalk4.white(`Found ${feeds.length} registered feed(s):
|
|
5238
|
+
`));
|
|
5239
|
+
feeds.forEach((feed, i) => {
|
|
5240
|
+
console.log(formatFeed(feed, i));
|
|
5241
|
+
if (i < feeds.length - 1) {
|
|
5242
|
+
console.log();
|
|
5243
|
+
}
|
|
5244
|
+
});
|
|
5245
|
+
}
|
|
5246
|
+
} catch (error) {
|
|
5247
|
+
exitWithError(
|
|
5248
|
+
`Failed to fetch feeds: ${error instanceof Error ? error.message : String(error)}`
|
|
5249
|
+
);
|
|
5250
|
+
}
|
|
5251
|
+
}
|
|
5252
|
+
function registerFeedListCommand(parent, commandName = "list") {
|
|
5253
|
+
parent.command(commandName).description("List registered feeds").option(
|
|
5254
|
+
"--limit <n>",
|
|
5255
|
+
"Maximum number of feeds to display",
|
|
5256
|
+
(value) => parseInt(value, 10)
|
|
5257
|
+
).option(
|
|
5258
|
+
"--chain-id <id>",
|
|
5259
|
+
"Chain ID (default: 8453 for Base)",
|
|
5260
|
+
(value) => parseInt(value, 10)
|
|
5261
|
+
).option("--rpc-url <url>", "Custom RPC URL").option("--json", "Output in JSON format").action(async (options) => {
|
|
5262
|
+
await executeFeedList(options);
|
|
5263
|
+
});
|
|
5264
|
+
}
|
|
5265
|
+
var MAX_HISTORY_ENTRIES = 100;
|
|
5266
|
+
var STATE_DIR = path.join(os.homedir(), ".botchan");
|
|
5267
|
+
var STATE_FILE = path.join(STATE_DIR, "state.json");
|
|
5268
|
+
function ensureStateDir() {
|
|
5269
|
+
if (!fs4.existsSync(STATE_DIR)) {
|
|
5270
|
+
fs4.mkdirSync(STATE_DIR, { recursive: true });
|
|
5271
|
+
}
|
|
5272
|
+
}
|
|
5273
|
+
function loadState() {
|
|
5274
|
+
try {
|
|
5275
|
+
if (fs4.existsSync(STATE_FILE)) {
|
|
5276
|
+
const data = fs4.readFileSync(STATE_FILE, "utf-8");
|
|
5277
|
+
return JSON.parse(data);
|
|
5278
|
+
}
|
|
5279
|
+
} catch {
|
|
5280
|
+
}
|
|
5281
|
+
return { feeds: {} };
|
|
5282
|
+
}
|
|
5283
|
+
function saveState(state) {
|
|
5284
|
+
ensureStateDir();
|
|
5285
|
+
const tempFile = `${STATE_FILE}.tmp`;
|
|
5286
|
+
fs4.writeFileSync(tempFile, JSON.stringify(state, null, 2));
|
|
5287
|
+
fs4.renameSync(tempFile, STATE_FILE);
|
|
5288
|
+
}
|
|
5289
|
+
function getLastSeenTimestamp(feedName) {
|
|
5290
|
+
const state = loadState();
|
|
5291
|
+
return state.feeds[feedName]?.lastSeenTimestamp ?? null;
|
|
5292
|
+
}
|
|
5293
|
+
function setLastSeenTimestamp(feedName, timestamp) {
|
|
5294
|
+
const state = loadState();
|
|
5295
|
+
if (!state.feeds[feedName]) {
|
|
5296
|
+
state.feeds[feedName] = { lastSeenTimestamp: timestamp };
|
|
5297
|
+
} else {
|
|
5298
|
+
state.feeds[feedName].lastSeenTimestamp = timestamp;
|
|
5299
|
+
}
|
|
5300
|
+
saveState(state);
|
|
5301
|
+
}
|
|
5302
|
+
function markFeedSeen(feedName, posts) {
|
|
5303
|
+
if (posts.length === 0) return;
|
|
5304
|
+
const maxTimestamp = posts.reduce(
|
|
5305
|
+
(max, post) => post.timestamp > max ? post.timestamp : max,
|
|
5306
|
+
posts[0].timestamp
|
|
5307
|
+
);
|
|
5308
|
+
setLastSeenTimestamp(feedName, Number(maxTimestamp));
|
|
5309
|
+
}
|
|
5310
|
+
function getMyAddress() {
|
|
5311
|
+
const state = loadState();
|
|
5312
|
+
return state.myAddress ?? null;
|
|
5313
|
+
}
|
|
5314
|
+
function setMyAddress(address) {
|
|
5315
|
+
const state = loadState();
|
|
5316
|
+
state.myAddress = address.toLowerCase();
|
|
5317
|
+
saveState(state);
|
|
5318
|
+
}
|
|
5319
|
+
function clearMyAddress() {
|
|
5320
|
+
const state = loadState();
|
|
5321
|
+
delete state.myAddress;
|
|
5322
|
+
saveState(state);
|
|
5323
|
+
}
|
|
5324
|
+
function getFullState() {
|
|
5325
|
+
return loadState();
|
|
5326
|
+
}
|
|
5327
|
+
function resetState() {
|
|
5328
|
+
if (fs4.existsSync(STATE_FILE)) {
|
|
5329
|
+
fs4.unlinkSync(STATE_FILE);
|
|
5330
|
+
}
|
|
5331
|
+
}
|
|
5332
|
+
function getStateFilePath() {
|
|
5333
|
+
return STATE_FILE;
|
|
5334
|
+
}
|
|
5335
|
+
function addHistoryEntry(entry) {
|
|
5336
|
+
const state = loadState();
|
|
5337
|
+
const history = state.history ?? [];
|
|
5338
|
+
const newEntry = {
|
|
5339
|
+
...entry,
|
|
5340
|
+
timestamp: Math.floor(Date.now() / 1e3)
|
|
5341
|
+
};
|
|
5342
|
+
history.unshift(newEntry);
|
|
5343
|
+
if (history.length > MAX_HISTORY_ENTRIES) {
|
|
5344
|
+
history.length = MAX_HISTORY_ENTRIES;
|
|
5345
|
+
}
|
|
5346
|
+
state.history = history;
|
|
5347
|
+
saveState(state);
|
|
5348
|
+
}
|
|
5349
|
+
function getHistory(limit) {
|
|
5350
|
+
const state = loadState();
|
|
5351
|
+
const history = state.history ?? [];
|
|
5352
|
+
return limit ? history.slice(0, limit) : history;
|
|
5353
|
+
}
|
|
5354
|
+
function getHistoryByType(type, limit) {
|
|
5355
|
+
const history = getHistory();
|
|
5356
|
+
const filtered = history.filter((entry) => entry.type === type);
|
|
5357
|
+
return limit ? filtered.slice(0, limit) : filtered;
|
|
5358
|
+
}
|
|
5359
|
+
function clearHistory() {
|
|
5360
|
+
const state = loadState();
|
|
5361
|
+
state.history = [];
|
|
5362
|
+
saveState(state);
|
|
5363
|
+
}
|
|
5364
|
+
function getHistoryCount() {
|
|
5365
|
+
const state = loadState();
|
|
5366
|
+
return state.history?.length ?? 0;
|
|
5367
|
+
}
|
|
5368
|
+
function isWalletAddress(feed) {
|
|
5369
|
+
return /^0x[a-fA-F0-9]{40}$/.test(feed);
|
|
5370
|
+
}
|
|
5371
|
+
function getContacts() {
|
|
5372
|
+
const history = getHistory();
|
|
5373
|
+
const contactMap = /* @__PURE__ */ new Map();
|
|
5374
|
+
for (const entry of history) {
|
|
5375
|
+
if (entry.type === "post" && isWalletAddress(entry.feed)) {
|
|
5376
|
+
const address = entry.feed.toLowerCase();
|
|
5377
|
+
const existing = contactMap.get(address);
|
|
5378
|
+
if (existing) {
|
|
5379
|
+
existing.interactionCount++;
|
|
5380
|
+
if (entry.timestamp > existing.lastInteraction) {
|
|
5381
|
+
existing.lastInteraction = entry.timestamp;
|
|
5382
|
+
}
|
|
5383
|
+
if (entry.timestamp < existing.firstInteraction) {
|
|
5384
|
+
existing.firstInteraction = entry.timestamp;
|
|
5385
|
+
}
|
|
5386
|
+
} else {
|
|
5387
|
+
contactMap.set(address, {
|
|
5388
|
+
address,
|
|
5389
|
+
lastInteraction: entry.timestamp,
|
|
5390
|
+
firstInteraction: entry.timestamp,
|
|
5391
|
+
interactionCount: 1
|
|
5392
|
+
});
|
|
5393
|
+
}
|
|
5394
|
+
}
|
|
5395
|
+
}
|
|
5396
|
+
return Array.from(contactMap.values()).sort(
|
|
5397
|
+
(a, b) => b.lastInteraction - a.lastInteraction
|
|
5398
|
+
);
|
|
5399
|
+
}
|
|
5400
|
+
function getActiveFeeds() {
|
|
5401
|
+
const history = getHistory();
|
|
5402
|
+
const feedMap = /* @__PURE__ */ new Map();
|
|
5403
|
+
for (const entry of history) {
|
|
5404
|
+
if (isWalletAddress(entry.feed)) continue;
|
|
5405
|
+
if (entry.type === "register") continue;
|
|
5406
|
+
const feedName = entry.feed.toLowerCase();
|
|
5407
|
+
const existing = feedMap.get(feedName);
|
|
5408
|
+
if (existing) {
|
|
5409
|
+
if (entry.type === "post") existing.postCount++;
|
|
5410
|
+
if (entry.type === "comment") existing.commentCount++;
|
|
5411
|
+
if (entry.timestamp > existing.lastActivity) {
|
|
5412
|
+
existing.lastActivity = entry.timestamp;
|
|
5413
|
+
}
|
|
5414
|
+
if (entry.timestamp < existing.firstActivity) {
|
|
5415
|
+
existing.firstActivity = entry.timestamp;
|
|
5416
|
+
}
|
|
5417
|
+
} else {
|
|
5418
|
+
feedMap.set(feedName, {
|
|
5419
|
+
feed: feedName,
|
|
5420
|
+
postCount: entry.type === "post" ? 1 : 0,
|
|
5421
|
+
commentCount: entry.type === "comment" ? 1 : 0,
|
|
5422
|
+
lastActivity: entry.timestamp,
|
|
5423
|
+
firstActivity: entry.timestamp
|
|
5424
|
+
});
|
|
5425
|
+
}
|
|
5426
|
+
}
|
|
5427
|
+
return Array.from(feedMap.values()).sort(
|
|
5428
|
+
(a, b) => b.lastActivity - a.lastActivity
|
|
5429
|
+
);
|
|
5430
|
+
}
|
|
5431
|
+
|
|
5432
|
+
// src/commands/feed/types.ts
|
|
5433
|
+
function normalizeFeedName(feed) {
|
|
5434
|
+
return feed.toLowerCase();
|
|
5435
|
+
}
|
|
5436
|
+
|
|
5437
|
+
// src/commands/feed/read.ts
|
|
5438
|
+
async function executeFeedRead(feed, options) {
|
|
5439
|
+
const normalizedFeed = normalizeFeedName(feed);
|
|
5440
|
+
const readOnlyOptions = parseReadOnlyOptionsWithDefault({
|
|
5441
|
+
chainId: options.chainId,
|
|
5442
|
+
rpcUrl: options.rpcUrl
|
|
5443
|
+
});
|
|
5444
|
+
const client = createFeedClient(readOnlyOptions);
|
|
5445
|
+
const limit = options.limit ?? 20;
|
|
5446
|
+
try {
|
|
5447
|
+
const count = await client.getFeedPostCount(normalizedFeed);
|
|
5448
|
+
if (count === 0) {
|
|
5449
|
+
if (options.json) {
|
|
5450
|
+
printJson([]);
|
|
5451
|
+
} else {
|
|
5452
|
+
console.log(chalk4.yellow(`No posts found in feed "${normalizedFeed}"`));
|
|
5453
|
+
}
|
|
5454
|
+
return;
|
|
5455
|
+
}
|
|
5456
|
+
const fetchLimit = options.sender ? Math.max(limit * 5, 100) : limit;
|
|
5457
|
+
let posts = await client.getFeedPosts({
|
|
5458
|
+
topic: normalizedFeed,
|
|
5459
|
+
maxPosts: fetchLimit
|
|
5460
|
+
});
|
|
5461
|
+
if (options.sender) {
|
|
5462
|
+
const senderLower = options.sender.toLowerCase();
|
|
5463
|
+
posts = posts.filter(
|
|
5464
|
+
(post) => post.sender.toLowerCase() === senderLower
|
|
5465
|
+
);
|
|
5466
|
+
posts = posts.slice(0, limit);
|
|
5467
|
+
}
|
|
5468
|
+
if (options.unseen) {
|
|
5469
|
+
const lastSeen = getLastSeenTimestamp(normalizedFeed);
|
|
5470
|
+
const myAddress = getMyAddress();
|
|
5471
|
+
posts = posts.filter((post) => {
|
|
5472
|
+
const isNew = lastSeen === null || Number(post.timestamp) > lastSeen;
|
|
5473
|
+
const isFromOther = !myAddress || post.sender.toLowerCase() !== myAddress;
|
|
5474
|
+
return isNew && isFromOther;
|
|
5475
|
+
});
|
|
5476
|
+
}
|
|
5477
|
+
if (options.markSeen) {
|
|
5478
|
+
const allPosts = await client.getFeedPosts({
|
|
5479
|
+
topic: normalizedFeed,
|
|
5480
|
+
maxPosts: 1
|
|
5481
|
+
// Just need the latest
|
|
5482
|
+
});
|
|
5483
|
+
if (allPosts.length > 0) {
|
|
5484
|
+
markFeedSeen(normalizedFeed, allPosts);
|
|
5485
|
+
}
|
|
5486
|
+
}
|
|
5487
|
+
const commentCounts = await Promise.all(
|
|
5488
|
+
posts.map((post) => client.getCommentCount(post))
|
|
5489
|
+
);
|
|
5490
|
+
if (options.json) {
|
|
5491
|
+
printJson(
|
|
5492
|
+
posts.map((post, i) => postToJson(post, i, commentCounts[i]))
|
|
5493
|
+
);
|
|
5494
|
+
} else {
|
|
5495
|
+
if (posts.length === 0) {
|
|
5496
|
+
const senderNote2 = options.sender ? ` by ${options.sender}` : "";
|
|
5497
|
+
console.log(chalk4.yellow(`No posts found in feed "${normalizedFeed}"${senderNote2}`));
|
|
5498
|
+
return;
|
|
5499
|
+
}
|
|
5500
|
+
const senderNote = options.sender ? ` by ${options.sender}` : "";
|
|
5501
|
+
console.log(
|
|
5502
|
+
chalk4.white(`Found ${posts.length} post(s) in feed "${normalizedFeed}"${senderNote}:
|
|
5503
|
+
`)
|
|
5504
|
+
);
|
|
5505
|
+
posts.forEach((post, i) => {
|
|
5506
|
+
console.log(formatPost(post, i, { commentCount: commentCounts[i] }));
|
|
5507
|
+
if (i < posts.length - 1) {
|
|
5508
|
+
console.log();
|
|
5509
|
+
}
|
|
5510
|
+
});
|
|
5511
|
+
}
|
|
5512
|
+
} catch (error) {
|
|
5513
|
+
exitWithError(
|
|
5514
|
+
`Failed to read feed: ${error instanceof Error ? error.message : String(error)}`
|
|
5515
|
+
);
|
|
5516
|
+
}
|
|
5517
|
+
}
|
|
5518
|
+
function registerFeedReadCommand(parent) {
|
|
5519
|
+
parent.command("read <feed>").description("Read posts from a feed").option(
|
|
5520
|
+
"--limit <n>",
|
|
5521
|
+
"Maximum number of posts to display",
|
|
5522
|
+
(value) => parseInt(value, 10)
|
|
5523
|
+
).option(
|
|
5524
|
+
"--chain-id <id>",
|
|
5525
|
+
"Chain ID (default: 8453 for Base)",
|
|
5526
|
+
(value) => parseInt(value, 10)
|
|
5527
|
+
).option("--rpc-url <url>", "Custom RPC URL").option("--sender <address>", "Filter posts by sender address").option("--unseen", "Only show posts not yet seen (newer than last --mark-seen)").option("--mark-seen", "Mark the feed as seen up to the latest post").option("--json", "Output in JSON format").action(async (feed, options) => {
|
|
5528
|
+
await executeFeedRead(feed, options);
|
|
5529
|
+
});
|
|
5530
|
+
}
|
|
5531
|
+
function createWallet(privateKey, chainId, rpcUrl) {
|
|
5532
|
+
const account = privateKeyToAccount(privateKey);
|
|
5533
|
+
const rpcUrls = getChainRpcUrls({
|
|
5534
|
+
chainId,
|
|
5535
|
+
rpcUrl
|
|
5536
|
+
});
|
|
5537
|
+
return createWalletClient({
|
|
5538
|
+
account,
|
|
5539
|
+
transport: http(rpcUrls[0])
|
|
5540
|
+
});
|
|
5541
|
+
}
|
|
5542
|
+
async function executeTransaction(walletClient, txConfig) {
|
|
5543
|
+
const hash = await walletClient.writeContract({
|
|
5544
|
+
address: txConfig.to,
|
|
5545
|
+
abi: txConfig.abi,
|
|
5546
|
+
functionName: txConfig.functionName,
|
|
5547
|
+
args: txConfig.args,
|
|
5548
|
+
value: txConfig.value,
|
|
5549
|
+
chain: null
|
|
5550
|
+
});
|
|
5551
|
+
return hash;
|
|
5552
|
+
}
|
|
5553
|
+
|
|
5554
|
+
// src/commands/feed/post.ts
|
|
5555
|
+
var MAX_MESSAGE_LENGTH = 4e3;
|
|
5556
|
+
async function executeFeedPost(feed, message, options) {
|
|
5557
|
+
const normalizedFeed = normalizeFeedName(feed);
|
|
5558
|
+
if (message.length === 0) {
|
|
5559
|
+
exitWithError("Message cannot be empty");
|
|
5560
|
+
}
|
|
5561
|
+
const fullMessage = options.body ? `${message}
|
|
5562
|
+
|
|
5563
|
+
${options.body}` : message;
|
|
5564
|
+
if (fullMessage.length > MAX_MESSAGE_LENGTH) {
|
|
5565
|
+
exitWithError(
|
|
5566
|
+
`Message too long (${fullMessage.length} chars). Maximum is ${MAX_MESSAGE_LENGTH} characters.`
|
|
5567
|
+
);
|
|
5568
|
+
}
|
|
5569
|
+
if (options.encodeOnly) {
|
|
5570
|
+
const readOnlyOptions = parseReadOnlyOptionsWithDefault({
|
|
5571
|
+
chainId: options.chainId,
|
|
5572
|
+
rpcUrl: options.rpcUrl
|
|
5573
|
+
});
|
|
5574
|
+
const client2 = createFeedClient(readOnlyOptions);
|
|
5575
|
+
const txConfig2 = client2.preparePostToFeed({
|
|
5576
|
+
topic: normalizedFeed,
|
|
5577
|
+
text: fullMessage,
|
|
5578
|
+
data: options.data
|
|
5579
|
+
});
|
|
5580
|
+
const encoded = encodeTransaction(txConfig2, readOnlyOptions.chainId);
|
|
5581
|
+
printJson(encoded);
|
|
5582
|
+
return;
|
|
5583
|
+
}
|
|
5584
|
+
const commonOptions = parseCommonOptionsWithDefault(
|
|
5585
|
+
{
|
|
5586
|
+
privateKey: options.privateKey,
|
|
5587
|
+
chainId: options.chainId,
|
|
5588
|
+
rpcUrl: options.rpcUrl
|
|
5589
|
+
},
|
|
5590
|
+
true
|
|
5591
|
+
// supports --encode-only
|
|
5592
|
+
);
|
|
5593
|
+
const client = createFeedClient(commonOptions);
|
|
5594
|
+
const txConfig = client.preparePostToFeed({
|
|
5595
|
+
topic: normalizedFeed,
|
|
5596
|
+
text: fullMessage,
|
|
5597
|
+
data: options.data
|
|
5598
|
+
});
|
|
5599
|
+
const walletClient = createWallet(
|
|
5600
|
+
commonOptions.privateKey,
|
|
5601
|
+
commonOptions.chainId,
|
|
5602
|
+
commonOptions.rpcUrl
|
|
5603
|
+
);
|
|
5604
|
+
console.log(chalk4.blue(`Posting to feed "${normalizedFeed}"...`));
|
|
5605
|
+
try {
|
|
5606
|
+
const hash = await executeTransaction(walletClient, txConfig);
|
|
5607
|
+
const senderAddress = walletClient.account.address;
|
|
5608
|
+
let postId;
|
|
5609
|
+
try {
|
|
5610
|
+
const posts = await client.getFeedPosts({
|
|
5611
|
+
topic: normalizedFeed,
|
|
5612
|
+
maxPosts: 10
|
|
5613
|
+
});
|
|
5614
|
+
const ourPost = posts.find(
|
|
5615
|
+
(p) => p.sender.toLowerCase() === senderAddress.toLowerCase() && p.text === fullMessage
|
|
5616
|
+
);
|
|
5617
|
+
if (ourPost) {
|
|
5618
|
+
postId = `${ourPost.sender}:${ourPost.timestamp}`;
|
|
5619
|
+
}
|
|
5620
|
+
} catch {
|
|
5621
|
+
}
|
|
5622
|
+
addHistoryEntry({
|
|
5623
|
+
type: "post",
|
|
5624
|
+
txHash: hash,
|
|
5625
|
+
chainId: commonOptions.chainId,
|
|
5626
|
+
feed: normalizedFeed,
|
|
5627
|
+
sender: senderAddress,
|
|
5628
|
+
text: fullMessage,
|
|
5629
|
+
postId
|
|
5630
|
+
// Now we have the actual post ID for checking comments later
|
|
5631
|
+
});
|
|
5632
|
+
const displayText = options.body ? `${message} (+ body)` : message;
|
|
5633
|
+
const postIdInfo = postId ? `
|
|
5634
|
+
Post ID: ${postId}` : "";
|
|
5635
|
+
console.log(
|
|
5636
|
+
chalk4.green(
|
|
5637
|
+
`Message posted successfully!
|
|
5638
|
+
Transaction: ${hash}
|
|
5639
|
+
Feed: ${normalizedFeed}${postIdInfo}
|
|
5640
|
+
Text: ${displayText}`
|
|
5641
|
+
)
|
|
5642
|
+
);
|
|
5643
|
+
} catch (error) {
|
|
5644
|
+
exitWithError(
|
|
5645
|
+
`Failed to post message: ${error instanceof Error ? error.message : String(error)}`
|
|
5646
|
+
);
|
|
5647
|
+
}
|
|
5648
|
+
}
|
|
5649
|
+
function registerFeedPostCommand(parent) {
|
|
5650
|
+
parent.command("post <feed> <message>").description("Post a message to a feed").option(
|
|
5651
|
+
"--chain-id <id>",
|
|
5652
|
+
"Chain ID (default: 8453 for Base)",
|
|
5653
|
+
(value) => parseInt(value, 10)
|
|
5654
|
+
).option("--rpc-url <url>", "Custom RPC URL").option("--private-key <key>", "Private key (0x-prefixed)").option(
|
|
5655
|
+
"--encode-only",
|
|
5656
|
+
"Output transaction data as JSON instead of executing"
|
|
5657
|
+
).option("--data <data>", "Optional data to attach to the post").option("--body <text>", "Post body (message becomes the title)").action(async (feed, message, options) => {
|
|
5658
|
+
await executeFeedPost(feed, message, options);
|
|
5659
|
+
});
|
|
5660
|
+
}
|
|
5661
|
+
|
|
5662
|
+
// src/shared/postId.ts
|
|
5663
|
+
function parsePostId(postId) {
|
|
5664
|
+
const parts = postId.split(":");
|
|
5665
|
+
if (parts.length !== 2) {
|
|
5666
|
+
throw new Error(
|
|
5667
|
+
`Invalid post ID format. Expected {sender}:{timestamp}, got: ${postId}`
|
|
5668
|
+
);
|
|
5669
|
+
}
|
|
5670
|
+
const [sender, timestampStr] = parts;
|
|
5671
|
+
if (!sender.startsWith("0x") || sender.length !== 42) {
|
|
5672
|
+
throw new Error(`Invalid sender address in post ID: ${sender}`);
|
|
5673
|
+
}
|
|
5674
|
+
const timestamp = BigInt(timestampStr);
|
|
5675
|
+
if (timestamp <= 0) {
|
|
5676
|
+
throw new Error(`Invalid timestamp in post ID: ${timestampStr}`);
|
|
5677
|
+
}
|
|
5678
|
+
return {
|
|
5679
|
+
sender,
|
|
5680
|
+
timestamp
|
|
5681
|
+
};
|
|
5682
|
+
}
|
|
5683
|
+
function findPostByParsedId(posts, parsedId) {
|
|
5684
|
+
return posts.find(
|
|
5685
|
+
(p) => p.sender.toLowerCase() === parsedId.sender.toLowerCase() && p.timestamp === parsedId.timestamp
|
|
5686
|
+
);
|
|
5687
|
+
}
|
|
5688
|
+
|
|
5689
|
+
// src/commands/feed/comment-write.ts
|
|
5690
|
+
var MAX_MESSAGE_LENGTH2 = 4e3;
|
|
5691
|
+
async function executeFeedCommentWrite(feed, postId, message, options) {
|
|
5692
|
+
const normalizedFeed = normalizeFeedName(feed);
|
|
5693
|
+
if (message.length === 0) {
|
|
5694
|
+
exitWithError("Comment cannot be empty");
|
|
5695
|
+
}
|
|
5696
|
+
if (message.length > MAX_MESSAGE_LENGTH2) {
|
|
5697
|
+
exitWithError(
|
|
5698
|
+
`Comment too long (${message.length} chars). Maximum is ${MAX_MESSAGE_LENGTH2} characters.`
|
|
5699
|
+
);
|
|
5700
|
+
}
|
|
5701
|
+
let parsedId;
|
|
5702
|
+
try {
|
|
5703
|
+
parsedId = parsePostId(postId);
|
|
5704
|
+
} catch (error) {
|
|
5705
|
+
exitWithError(
|
|
5706
|
+
error instanceof Error ? error.message : "Invalid post ID format"
|
|
5707
|
+
);
|
|
5708
|
+
}
|
|
5709
|
+
const readOnlyOptions = parseReadOnlyOptionsWithDefault({
|
|
5710
|
+
chainId: options.chainId,
|
|
5711
|
+
rpcUrl: options.rpcUrl
|
|
5712
|
+
});
|
|
5713
|
+
const client = createFeedClient(readOnlyOptions);
|
|
5714
|
+
const count = await client.getFeedPostCount(normalizedFeed);
|
|
5715
|
+
if (count === 0) {
|
|
5716
|
+
exitWithError(
|
|
5717
|
+
`Feed "${normalizedFeed}" has no posts. Cannot find post ${postId}.`
|
|
5718
|
+
);
|
|
5719
|
+
}
|
|
5720
|
+
const posts = await client.getFeedPosts({
|
|
5721
|
+
topic: normalizedFeed,
|
|
5722
|
+
maxPosts: 100
|
|
5723
|
+
// Fetch enough to find the post
|
|
5724
|
+
});
|
|
5725
|
+
const targetPost = findPostByParsedId(posts, parsedId);
|
|
5726
|
+
if (!targetPost) {
|
|
5727
|
+
exitWithError(
|
|
5728
|
+
`Post not found with ID ${postId} in feed "${normalizedFeed}". Make sure the sender and timestamp are correct.`
|
|
5729
|
+
);
|
|
5730
|
+
}
|
|
5731
|
+
const txConfig = client.prepareComment({
|
|
5732
|
+
post: targetPost,
|
|
5733
|
+
text: message
|
|
5734
|
+
});
|
|
5735
|
+
if (options.encodeOnly) {
|
|
5736
|
+
const encoded = encodeTransaction(txConfig, readOnlyOptions.chainId);
|
|
5737
|
+
printJson(encoded);
|
|
5738
|
+
return;
|
|
5739
|
+
}
|
|
5740
|
+
const commonOptions = parseCommonOptionsWithDefault(
|
|
5741
|
+
{
|
|
5742
|
+
privateKey: options.privateKey,
|
|
5743
|
+
chainId: options.chainId,
|
|
5744
|
+
rpcUrl: options.rpcUrl
|
|
5745
|
+
},
|
|
5746
|
+
true
|
|
5747
|
+
// supports --encode-only
|
|
5748
|
+
);
|
|
5749
|
+
const walletClient = createWallet(
|
|
5750
|
+
commonOptions.privateKey,
|
|
5751
|
+
commonOptions.chainId,
|
|
5752
|
+
commonOptions.rpcUrl
|
|
5753
|
+
);
|
|
5754
|
+
console.log(chalk4.blue(`Commenting on post ${postId}...`));
|
|
5755
|
+
try {
|
|
5756
|
+
const hash = await executeTransaction(walletClient, txConfig);
|
|
5757
|
+
addHistoryEntry({
|
|
5758
|
+
type: "comment",
|
|
5759
|
+
txHash: hash,
|
|
5760
|
+
chainId: commonOptions.chainId,
|
|
5761
|
+
feed: normalizedFeed,
|
|
5762
|
+
sender: walletClient.account.address,
|
|
5763
|
+
text: message,
|
|
5764
|
+
postId
|
|
5765
|
+
});
|
|
5766
|
+
console.log(
|
|
5767
|
+
chalk4.green(
|
|
5768
|
+
`Comment posted successfully!
|
|
5769
|
+
Transaction: ${hash}
|
|
5770
|
+
Post: ${postId}
|
|
5771
|
+
Comment: ${message}`
|
|
5772
|
+
)
|
|
5773
|
+
);
|
|
5774
|
+
} catch (error) {
|
|
5775
|
+
exitWithError(
|
|
5776
|
+
`Failed to post comment: ${error instanceof Error ? error.message : String(error)}`
|
|
5777
|
+
);
|
|
5778
|
+
}
|
|
5779
|
+
}
|
|
5780
|
+
function registerFeedCommentWriteCommand(parent) {
|
|
5781
|
+
parent.command("comment <feed> <post-id> <message>").description(
|
|
5782
|
+
"Comment on a post. Post ID format: {sender}:{timestamp}"
|
|
5783
|
+
).option(
|
|
5784
|
+
"--chain-id <id>",
|
|
5785
|
+
"Chain ID (default: 8453 for Base)",
|
|
5786
|
+
(value) => parseInt(value, 10)
|
|
5787
|
+
).option("--rpc-url <url>", "Custom RPC URL").option("--private-key <key>", "Private key (0x-prefixed)").option(
|
|
5788
|
+
"--encode-only",
|
|
5789
|
+
"Output transaction data as JSON instead of executing"
|
|
5790
|
+
).action(async (feed, postId, message, options) => {
|
|
5791
|
+
await executeFeedCommentWrite(feed, postId, message, options);
|
|
5792
|
+
});
|
|
5793
|
+
}
|
|
5794
|
+
async function executeFeedCommentRead(feed, postId, options) {
|
|
5795
|
+
const normalizedFeed = normalizeFeedName(feed);
|
|
5796
|
+
const readOnlyOptions = parseReadOnlyOptionsWithDefault({
|
|
5797
|
+
chainId: options.chainId,
|
|
5798
|
+
rpcUrl: options.rpcUrl
|
|
5799
|
+
});
|
|
5800
|
+
const client = createFeedClient(readOnlyOptions);
|
|
5801
|
+
let parsedId;
|
|
5802
|
+
try {
|
|
5803
|
+
parsedId = parsePostId(postId);
|
|
5804
|
+
} catch (error) {
|
|
5805
|
+
exitWithError(
|
|
5806
|
+
error instanceof Error ? error.message : "Invalid post ID format"
|
|
5807
|
+
);
|
|
5808
|
+
}
|
|
5809
|
+
try {
|
|
5810
|
+
const count = await client.getFeedPostCount(normalizedFeed);
|
|
5811
|
+
if (count === 0) {
|
|
5812
|
+
exitWithError(
|
|
5813
|
+
`Feed "${normalizedFeed}" has no posts. Cannot find post ${postId}.`
|
|
5814
|
+
);
|
|
5815
|
+
}
|
|
5816
|
+
const posts = await client.getFeedPosts({
|
|
5817
|
+
topic: normalizedFeed,
|
|
5818
|
+
maxPosts: 100
|
|
5819
|
+
// Fetch enough to find the post
|
|
5820
|
+
});
|
|
5821
|
+
const targetPost = findPostByParsedId(posts, parsedId);
|
|
5822
|
+
if (!targetPost) {
|
|
5823
|
+
exitWithError(
|
|
5824
|
+
`Post not found with ID ${postId} in feed "${normalizedFeed}". Make sure the sender and timestamp are correct.`
|
|
5825
|
+
);
|
|
5826
|
+
}
|
|
5827
|
+
const commentCount = await client.getCommentCount(targetPost);
|
|
5828
|
+
if (commentCount === 0) {
|
|
5829
|
+
if (options.json) {
|
|
5830
|
+
printJson([]);
|
|
5831
|
+
} else {
|
|
5832
|
+
console.log(chalk4.yellow(`No comments found for post ${postId}`));
|
|
5833
|
+
}
|
|
5834
|
+
return;
|
|
5835
|
+
}
|
|
5836
|
+
const comments = await client.getComments({
|
|
5837
|
+
post: targetPost,
|
|
5838
|
+
maxComments: options.limit ?? 50
|
|
5839
|
+
});
|
|
5840
|
+
const commentsWithDepth = comments.map((comment) => ({
|
|
5841
|
+
comment,
|
|
5842
|
+
depth: 0
|
|
5843
|
+
}));
|
|
5844
|
+
if (options.json) {
|
|
5845
|
+
printJson(
|
|
5846
|
+
commentsWithDepth.map(
|
|
5847
|
+
({ comment, depth }) => commentToJson(comment, depth)
|
|
5848
|
+
)
|
|
5849
|
+
);
|
|
5850
|
+
} else {
|
|
5851
|
+
if (comments.length === 0) {
|
|
5852
|
+
console.log(chalk4.yellow(`No comments found for post ${postId}`));
|
|
5853
|
+
return;
|
|
5854
|
+
}
|
|
5855
|
+
console.log(
|
|
5856
|
+
chalk4.white(`Found ${comments.length} comment(s) for post ${postId}:
|
|
5857
|
+
`)
|
|
5858
|
+
);
|
|
5859
|
+
commentsWithDepth.forEach(({ comment, depth }, i) => {
|
|
5860
|
+
console.log(formatComment(comment, depth));
|
|
5861
|
+
if (i < commentsWithDepth.length - 1) {
|
|
5862
|
+
console.log();
|
|
5863
|
+
}
|
|
5864
|
+
});
|
|
5865
|
+
}
|
|
5866
|
+
} catch (error) {
|
|
5867
|
+
if (error.message?.includes("Post not found")) {
|
|
5868
|
+
throw error;
|
|
5869
|
+
}
|
|
5870
|
+
exitWithError(
|
|
5871
|
+
`Failed to fetch comments: ${error instanceof Error ? error.message : String(error)}`
|
|
5872
|
+
);
|
|
5873
|
+
}
|
|
5874
|
+
}
|
|
5875
|
+
function registerFeedCommentReadCommand(parent) {
|
|
5876
|
+
parent.command("comments <feed> <post-id>").description(
|
|
5877
|
+
"Read comments on a post. Post ID format: {sender}:{timestamp}"
|
|
5878
|
+
).option(
|
|
5879
|
+
"--limit <n>",
|
|
5880
|
+
"Maximum number of comments to display",
|
|
5881
|
+
(value) => parseInt(value, 10)
|
|
5882
|
+
).option(
|
|
5883
|
+
"--chain-id <id>",
|
|
5884
|
+
"Chain ID (default: 8453 for Base)",
|
|
5885
|
+
(value) => parseInt(value, 10)
|
|
5886
|
+
).option("--rpc-url <url>", "Custom RPC URL").option("--json", "Output in JSON format").action(async (feed, postId, options) => {
|
|
5887
|
+
await executeFeedCommentRead(feed, postId, options);
|
|
5888
|
+
});
|
|
5889
|
+
}
|
|
5890
|
+
async function executeFeedRegister(feedName, options) {
|
|
5891
|
+
if (feedName.length > 64) {
|
|
5892
|
+
exitWithError("Feed name cannot exceed 64 characters");
|
|
5893
|
+
}
|
|
5894
|
+
if (feedName.length === 0) {
|
|
5895
|
+
exitWithError("Feed name cannot be empty");
|
|
5896
|
+
}
|
|
5897
|
+
if (options.encodeOnly) {
|
|
5898
|
+
const readOnlyOptions = parseReadOnlyOptionsWithDefault({
|
|
5899
|
+
chainId: options.chainId,
|
|
5900
|
+
rpcUrl: options.rpcUrl
|
|
5901
|
+
});
|
|
5902
|
+
const client2 = createFeedRegistryClient(readOnlyOptions);
|
|
5903
|
+
const txConfig2 = client2.prepareRegisterFeed({ feedName });
|
|
5904
|
+
const encoded = encodeTransaction(txConfig2, readOnlyOptions.chainId);
|
|
5905
|
+
printJson(encoded);
|
|
5906
|
+
return;
|
|
5907
|
+
}
|
|
5908
|
+
const commonOptions = parseCommonOptionsWithDefault(
|
|
5909
|
+
{
|
|
5910
|
+
privateKey: options.privateKey,
|
|
5911
|
+
chainId: options.chainId,
|
|
5912
|
+
rpcUrl: options.rpcUrl
|
|
5913
|
+
},
|
|
5914
|
+
true
|
|
5915
|
+
// supports --encode-only
|
|
5916
|
+
);
|
|
5917
|
+
const client = createFeedRegistryClient(commonOptions);
|
|
5918
|
+
const isRegistered = await client.isFeedRegistered(feedName);
|
|
5919
|
+
if (isRegistered) {
|
|
5920
|
+
exitWithError(`Feed "${feedName}" is already registered`);
|
|
5921
|
+
}
|
|
5922
|
+
const txConfig = client.prepareRegisterFeed({ feedName });
|
|
5923
|
+
const walletClient = createWallet(
|
|
5924
|
+
commonOptions.privateKey,
|
|
5925
|
+
commonOptions.chainId,
|
|
5926
|
+
commonOptions.rpcUrl
|
|
5927
|
+
);
|
|
5928
|
+
console.log(chalk4.blue(`Registering feed "${feedName}"...`));
|
|
5929
|
+
try {
|
|
5930
|
+
const hash = await executeTransaction(walletClient, txConfig);
|
|
5931
|
+
addHistoryEntry({
|
|
5932
|
+
type: "register",
|
|
5933
|
+
txHash: hash,
|
|
5934
|
+
chainId: commonOptions.chainId,
|
|
5935
|
+
feed: feedName,
|
|
5936
|
+
sender: walletClient.account.address
|
|
5937
|
+
});
|
|
5938
|
+
console.log(
|
|
5939
|
+
chalk4.green(
|
|
5940
|
+
`Feed registered successfully!
|
|
5941
|
+
Transaction: ${hash}
|
|
5942
|
+
Feed: ${feedName}`
|
|
5943
|
+
)
|
|
5944
|
+
);
|
|
5945
|
+
} catch (error) {
|
|
5946
|
+
exitWithError(
|
|
5947
|
+
`Failed to register feed: ${error instanceof Error ? error.message : String(error)}`
|
|
5948
|
+
);
|
|
5949
|
+
}
|
|
5950
|
+
}
|
|
5951
|
+
function registerFeedRegisterCommand(parent) {
|
|
5952
|
+
parent.command("register <feed-name>").description("Register a new feed").option(
|
|
5953
|
+
"--chain-id <id>",
|
|
5954
|
+
"Chain ID (default: 8453 for Base)",
|
|
5955
|
+
(value) => parseInt(value, 10)
|
|
5956
|
+
).option("--rpc-url <url>", "Custom RPC URL").option("--private-key <key>", "Private key (0x-prefixed)").option(
|
|
5957
|
+
"--encode-only",
|
|
5958
|
+
"Output transaction data as JSON instead of executing"
|
|
5959
|
+
).action(async (feedName, options) => {
|
|
5960
|
+
await executeFeedRegister(feedName, options);
|
|
5961
|
+
});
|
|
5962
|
+
}
|
|
5963
|
+
function truncateText(text, maxLen) {
|
|
5964
|
+
if (text.length <= maxLen) return text;
|
|
5965
|
+
return text.slice(0, maxLen) + "...";
|
|
5966
|
+
}
|
|
5967
|
+
async function executeFeedReplies(options) {
|
|
5968
|
+
const postHistory = getHistoryByType("post", options.limit ?? 10);
|
|
5969
|
+
const postsWithIds = postHistory.filter(
|
|
5970
|
+
(entry) => !!entry.postId
|
|
5971
|
+
);
|
|
5972
|
+
if (postsWithIds.length === 0) {
|
|
5973
|
+
if (options.json) {
|
|
5974
|
+
printJson([]);
|
|
5975
|
+
} else {
|
|
5976
|
+
console.log(chalk4.gray("No posts with trackable IDs found in history."));
|
|
5977
|
+
console.log(
|
|
5978
|
+
chalk4.gray("Post IDs are captured when you post with a wallet.")
|
|
5979
|
+
);
|
|
5980
|
+
}
|
|
5981
|
+
return;
|
|
5982
|
+
}
|
|
5983
|
+
const readOnlyOptions = parseReadOnlyOptionsWithDefault({
|
|
5984
|
+
chainId: options.chainId,
|
|
5985
|
+
rpcUrl: options.rpcUrl
|
|
5986
|
+
});
|
|
5987
|
+
const client = createFeedClient(readOnlyOptions);
|
|
5988
|
+
console.log(chalk4.blue(`Checking replies on ${postsWithIds.length} posts...
|
|
5989
|
+
`));
|
|
5990
|
+
const results = [];
|
|
5991
|
+
for (const entry of postsWithIds) {
|
|
5992
|
+
try {
|
|
5993
|
+
const [sender, timestampStr] = entry.postId.split(":");
|
|
5994
|
+
const timestamp = BigInt(timestampStr);
|
|
5995
|
+
const postObj = {
|
|
5996
|
+
sender,
|
|
5997
|
+
timestamp,
|
|
5998
|
+
text: entry.text ?? "",
|
|
5999
|
+
topic: entry.feed,
|
|
6000
|
+
app: "",
|
|
6001
|
+
data: "0x"
|
|
6002
|
+
};
|
|
6003
|
+
const commentCount = await client.getCommentCount(postObj);
|
|
6004
|
+
results.push({
|
|
6005
|
+
feed: entry.feed,
|
|
6006
|
+
postId: entry.postId,
|
|
6007
|
+
text: entry.text ?? "",
|
|
6008
|
+
postedAt: entry.timestamp,
|
|
6009
|
+
commentCount: Number(commentCount)
|
|
6010
|
+
});
|
|
6011
|
+
} catch {
|
|
6012
|
+
}
|
|
6013
|
+
}
|
|
6014
|
+
if (options.json) {
|
|
6015
|
+
printJson(results);
|
|
6016
|
+
return;
|
|
6017
|
+
}
|
|
6018
|
+
const postsWithReplies = results.filter((r) => r.commentCount > 0);
|
|
6019
|
+
const totalReplies = results.reduce((sum, r) => sum + r.commentCount, 0);
|
|
6020
|
+
console.log(
|
|
6021
|
+
chalk4.cyan(
|
|
6022
|
+
`Found ${totalReplies} total replies across ${postsWithReplies.length} posts
|
|
6023
|
+
`
|
|
6024
|
+
)
|
|
6025
|
+
);
|
|
6026
|
+
if (results.length === 0) {
|
|
6027
|
+
console.log(chalk4.gray("Could not check any posts."));
|
|
6028
|
+
return;
|
|
6029
|
+
}
|
|
6030
|
+
results.sort((a, b) => b.commentCount - a.commentCount);
|
|
6031
|
+
for (const post of results) {
|
|
6032
|
+
const timeAgo = formatTimestamp(post.postedAt);
|
|
6033
|
+
const replyText = post.commentCount === 0 ? chalk4.gray("no replies") : post.commentCount === 1 ? chalk4.green("1 reply") : chalk4.green(`${post.commentCount} replies`);
|
|
6034
|
+
console.log(
|
|
6035
|
+
`${chalk4.white(post.feed)} ${chalk4.gray("\u2022")} ${replyText} ${chalk4.gray(`\u2022 ${timeAgo}`)}`
|
|
6036
|
+
);
|
|
6037
|
+
console.log(` ${chalk4.gray(truncateText(post.text, 60))}`);
|
|
6038
|
+
if (post.commentCount > 0) {
|
|
6039
|
+
console.log(
|
|
6040
|
+
chalk4.blue(` \u2192 netp feed comments ${post.feed} ${post.postId}`)
|
|
6041
|
+
);
|
|
6042
|
+
}
|
|
6043
|
+
console.log("");
|
|
6044
|
+
}
|
|
6045
|
+
}
|
|
6046
|
+
function registerFeedRepliesCommand(parent) {
|
|
6047
|
+
parent.command("replies").description("Check for replies on your recent posts").option(
|
|
6048
|
+
"--chain-id <id>",
|
|
6049
|
+
"Chain ID (default: 8453 for Base)",
|
|
6050
|
+
(value) => parseInt(value, 10)
|
|
6051
|
+
).option("--rpc-url <url>", "Custom RPC URL").option(
|
|
6052
|
+
"--limit <n>",
|
|
6053
|
+
"Number of recent posts to check (default: 10)",
|
|
6054
|
+
(value) => parseInt(value, 10)
|
|
6055
|
+
).option("--json", "Output as JSON").action(async (options) => {
|
|
6056
|
+
await executeFeedReplies(options);
|
|
6057
|
+
});
|
|
6058
|
+
}
|
|
6059
|
+
async function executeFeedPosts(address, options) {
|
|
6060
|
+
if (!address.startsWith("0x") || address.length !== 42) {
|
|
6061
|
+
exitWithError("Invalid address format. Must be 0x-prefixed, 42 characters");
|
|
6062
|
+
}
|
|
6063
|
+
const readOnlyOptions = parseReadOnlyOptionsWithDefault({
|
|
6064
|
+
chainId: options.chainId,
|
|
6065
|
+
rpcUrl: options.rpcUrl
|
|
6066
|
+
});
|
|
6067
|
+
const client = createNetClient(readOnlyOptions);
|
|
6068
|
+
const limit = options.limit ?? 20;
|
|
6069
|
+
try {
|
|
6070
|
+
const count = await client.getMessageCount({
|
|
6071
|
+
filter: {
|
|
6072
|
+
appAddress: NULL_ADDRESS,
|
|
6073
|
+
maker: address
|
|
6074
|
+
}
|
|
6075
|
+
});
|
|
6076
|
+
if (count === 0) {
|
|
6077
|
+
if (options.json) {
|
|
6078
|
+
printJson([]);
|
|
6079
|
+
} else {
|
|
6080
|
+
console.log(chalk4.yellow(`No posts found for address ${address}`));
|
|
6081
|
+
}
|
|
6082
|
+
return;
|
|
6083
|
+
}
|
|
6084
|
+
const startIndex = count > limit ? count - limit : 0;
|
|
6085
|
+
const messages = await client.getMessages({
|
|
6086
|
+
filter: {
|
|
6087
|
+
appAddress: NULL_ADDRESS,
|
|
6088
|
+
maker: address
|
|
6089
|
+
},
|
|
6090
|
+
startIndex,
|
|
6091
|
+
endIndex: count
|
|
6092
|
+
});
|
|
6093
|
+
if (options.json) {
|
|
6094
|
+
printJson(messages.map((msg, i) => postToJson(msg, i)));
|
|
6095
|
+
} else {
|
|
6096
|
+
console.log(
|
|
6097
|
+
chalk4.white(`Found ${messages.length} post(s) by ${address}:
|
|
6098
|
+
`)
|
|
6099
|
+
);
|
|
6100
|
+
messages.forEach((msg, i) => {
|
|
6101
|
+
console.log(formatPost(msg, i, { showTopic: true }));
|
|
6102
|
+
if (i < messages.length - 1) {
|
|
6103
|
+
console.log();
|
|
6104
|
+
}
|
|
6105
|
+
});
|
|
6106
|
+
}
|
|
6107
|
+
} catch (error) {
|
|
6108
|
+
exitWithError(
|
|
6109
|
+
`Failed to fetch posts: ${error instanceof Error ? error.message : String(error)}`
|
|
6110
|
+
);
|
|
6111
|
+
}
|
|
6112
|
+
}
|
|
6113
|
+
function registerFeedPostsCommand(parent) {
|
|
6114
|
+
parent.command("posts <address>").description("View posts by an address").option(
|
|
6115
|
+
"--limit <n>",
|
|
6116
|
+
"Maximum number of posts to display",
|
|
6117
|
+
(value) => parseInt(value, 10)
|
|
6118
|
+
).option(
|
|
6119
|
+
"--chain-id <id>",
|
|
6120
|
+
"Chain ID (default: 8453 for Base)",
|
|
6121
|
+
(value) => parseInt(value, 10)
|
|
6122
|
+
).option("--rpc-url <url>", "Custom RPC URL").option("--json", "Output in JSON format").action(async (address, options) => {
|
|
6123
|
+
await executeFeedPosts(address, options);
|
|
6124
|
+
});
|
|
6125
|
+
}
|
|
6126
|
+
async function confirm(message) {
|
|
6127
|
+
const rl = readline.createInterface({
|
|
6128
|
+
input: process.stdin,
|
|
6129
|
+
output: process.stdout
|
|
6130
|
+
});
|
|
6131
|
+
return new Promise((resolve3) => {
|
|
6132
|
+
rl.question(`${message} (y/N): `, (answer) => {
|
|
6133
|
+
rl.close();
|
|
6134
|
+
resolve3(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
6135
|
+
});
|
|
6136
|
+
});
|
|
6137
|
+
}
|
|
6138
|
+
|
|
6139
|
+
// src/commands/feed/config.ts
|
|
6140
|
+
async function executeFeedConfig(options) {
|
|
6141
|
+
if (options.reset) {
|
|
6142
|
+
const statePath = getStateFilePath();
|
|
6143
|
+
console.log(chalk4.yellow(`This will delete all stored state at:`));
|
|
6144
|
+
console.log(chalk4.white(` ${statePath}`));
|
|
6145
|
+
console.log(chalk4.yellow(`
|
|
6146
|
+
This includes:`));
|
|
6147
|
+
console.log(chalk4.white(` - All "last seen" timestamps for feeds`));
|
|
6148
|
+
console.log(chalk4.white(` - Your configured address`));
|
|
6149
|
+
console.log(chalk4.white(` - Your activity history`));
|
|
6150
|
+
if (!options.force) {
|
|
6151
|
+
const confirmed = await confirm(chalk4.red("\nAre you sure you want to reset?"));
|
|
6152
|
+
if (!confirmed) {
|
|
6153
|
+
console.log(chalk4.gray("Cancelled."));
|
|
6154
|
+
return;
|
|
6155
|
+
}
|
|
6156
|
+
}
|
|
6157
|
+
resetState();
|
|
6158
|
+
console.log(chalk4.green("State reset successfully."));
|
|
6159
|
+
return;
|
|
6160
|
+
}
|
|
6161
|
+
if (options.myAddress) {
|
|
6162
|
+
if (!options.myAddress.match(/^0x[a-fA-F0-9]{40}$/)) {
|
|
6163
|
+
console.error(chalk4.red("Invalid address format. Expected 0x followed by 40 hex characters."));
|
|
6164
|
+
process.exit(1);
|
|
6165
|
+
}
|
|
6166
|
+
setMyAddress(options.myAddress);
|
|
6167
|
+
console.log(chalk4.green(`Set my address to: ${options.myAddress}`));
|
|
6168
|
+
return;
|
|
6169
|
+
}
|
|
6170
|
+
if (options.clearAddress) {
|
|
6171
|
+
clearMyAddress();
|
|
6172
|
+
console.log(chalk4.green("Cleared my address."));
|
|
6173
|
+
return;
|
|
6174
|
+
}
|
|
6175
|
+
const state = getFullState();
|
|
6176
|
+
const myAddress = getMyAddress();
|
|
6177
|
+
console.log(chalk4.cyan("Feed Configuration\n"));
|
|
6178
|
+
console.log(chalk4.white(`State file: ${getStateFilePath()}`));
|
|
6179
|
+
console.log(chalk4.white(`My address: ${myAddress ?? chalk4.gray("(not set)")}`));
|
|
6180
|
+
const feedCount = Object.keys(state.feeds).length;
|
|
6181
|
+
console.log(chalk4.white(`Tracked feeds: ${feedCount}`));
|
|
6182
|
+
const historyCount = getHistoryCount();
|
|
6183
|
+
console.log(chalk4.white(`History entries: ${historyCount}`));
|
|
6184
|
+
if (feedCount > 0 && feedCount <= 20) {
|
|
6185
|
+
console.log(chalk4.gray("\nLast seen timestamps:"));
|
|
6186
|
+
for (const [feed, data] of Object.entries(state.feeds)) {
|
|
6187
|
+
const date = new Date(data.lastSeenTimestamp * 1e3);
|
|
6188
|
+
console.log(chalk4.gray(` ${feed}: ${date.toLocaleString()}`));
|
|
6189
|
+
}
|
|
6190
|
+
} else if (feedCount > 20) {
|
|
6191
|
+
console.log(chalk4.gray(`
|
|
6192
|
+
(${feedCount} feeds tracked, use --json for full list)`));
|
|
6193
|
+
}
|
|
6194
|
+
const activeFeeds = getActiveFeeds();
|
|
6195
|
+
if (activeFeeds.length > 0) {
|
|
6196
|
+
console.log(chalk4.cyan("\nActive Feeds:"));
|
|
6197
|
+
const displayFeeds = activeFeeds.slice(0, 10);
|
|
6198
|
+
for (const feed of displayFeeds) {
|
|
6199
|
+
const activity = [];
|
|
6200
|
+
if (feed.postCount > 0) activity.push(`${feed.postCount} post${feed.postCount !== 1 ? "s" : ""}`);
|
|
6201
|
+
if (feed.commentCount > 0) activity.push(`${feed.commentCount} comment${feed.commentCount !== 1 ? "s" : ""}`);
|
|
6202
|
+
const lastActive = formatTimestamp(feed.lastActivity);
|
|
6203
|
+
console.log(chalk4.white(` ${feed.feed}`) + chalk4.gray(` \u2022 ${activity.join(", ")} \u2022 ${lastActive}`));
|
|
6204
|
+
}
|
|
6205
|
+
if (activeFeeds.length > 10) {
|
|
6206
|
+
console.log(chalk4.gray(` ... and ${activeFeeds.length - 10} more`));
|
|
6207
|
+
}
|
|
6208
|
+
}
|
|
6209
|
+
const contacts = getContacts();
|
|
6210
|
+
if (contacts.length > 0) {
|
|
6211
|
+
console.log(chalk4.cyan("\nRecent Contacts (DMs):"));
|
|
6212
|
+
const displayContacts = contacts.slice(0, 10);
|
|
6213
|
+
for (const contact of displayContacts) {
|
|
6214
|
+
const truncAddr = `${contact.address.slice(0, 6)}...${contact.address.slice(-4)}`;
|
|
6215
|
+
const msgCount = contact.interactionCount;
|
|
6216
|
+
const lastActive = formatTimestamp(contact.lastInteraction);
|
|
6217
|
+
console.log(
|
|
6218
|
+
chalk4.white(` ${truncAddr}`) + chalk4.gray(` \u2022 ${msgCount} message${msgCount !== 1 ? "s" : ""} \u2022 ${lastActive}`)
|
|
6219
|
+
);
|
|
6220
|
+
}
|
|
6221
|
+
if (contacts.length > 10) {
|
|
6222
|
+
console.log(chalk4.gray(` ... and ${contacts.length - 10} more`));
|
|
6223
|
+
}
|
|
6224
|
+
}
|
|
6225
|
+
}
|
|
6226
|
+
function registerFeedConfigCommand(parent) {
|
|
6227
|
+
parent.command("config").description("View or modify feed configuration").option("--my-address <address>", "Set your address (to filter out own posts with --unseen)").option("--clear-address", "Clear your configured address").option("--show", "Show current configuration (default)").option("--reset", "Reset all state (clears last-seen timestamps and address)").option("--force", "Skip confirmation prompt for --reset").action(async (options) => {
|
|
6228
|
+
await executeFeedConfig(options);
|
|
6229
|
+
});
|
|
6230
|
+
}
|
|
6231
|
+
function formatHistoryEntry(entry, index) {
|
|
6232
|
+
const timestamp = formatTimestamp(entry.timestamp);
|
|
6233
|
+
const typeColor = entry.type === "post" ? chalk4.green : entry.type === "comment" ? chalk4.blue : chalk4.yellow;
|
|
6234
|
+
const lines = [
|
|
6235
|
+
chalk4.cyan(`[${index}]`) + ` ${chalk4.gray(timestamp)} ` + typeColor(entry.type.toUpperCase()),
|
|
6236
|
+
` ${chalk4.white("Feed:")} ${entry.feed}`,
|
|
6237
|
+
` ${chalk4.white("Tx:")} ${entry.txHash}`
|
|
6238
|
+
];
|
|
6239
|
+
if (entry.sender) {
|
|
6240
|
+
lines.push(` ${chalk4.white("Sender:")} ${truncateAddress(entry.sender)}`);
|
|
6241
|
+
}
|
|
6242
|
+
if (entry.text) {
|
|
6243
|
+
const truncatedText = entry.text.length > 80 ? entry.text.slice(0, 80) + "..." : entry.text;
|
|
6244
|
+
lines.push(` ${chalk4.white("Text:")} ${truncatedText}`);
|
|
6245
|
+
}
|
|
6246
|
+
if (entry.type === "post" && entry.postId) {
|
|
6247
|
+
lines.push(` ${chalk4.white("Post ID:")} ${entry.postId}`);
|
|
6248
|
+
} else if (entry.type === "comment" && entry.postId) {
|
|
6249
|
+
lines.push(` ${chalk4.white("Reply to:")} ${entry.postId}`);
|
|
6250
|
+
}
|
|
6251
|
+
if (entry.type === "post" && entry.postId) {
|
|
6252
|
+
lines.push(
|
|
6253
|
+
chalk4.gray(` \u2192 Check replies: netp feed comments ${entry.feed} ${entry.postId}`)
|
|
6254
|
+
);
|
|
6255
|
+
} else if (entry.type === "post" && entry.sender) {
|
|
6256
|
+
lines.push(
|
|
6257
|
+
chalk4.gray(` \u2192 Find post: netp feed read ${entry.feed} --sender ${entry.sender} --json`)
|
|
6258
|
+
);
|
|
6259
|
+
} else if (entry.type === "comment" && entry.postId) {
|
|
6260
|
+
lines.push(
|
|
6261
|
+
chalk4.gray(` \u2192 See thread: netp feed comments ${entry.feed} ${entry.postId}`)
|
|
6262
|
+
);
|
|
6263
|
+
}
|
|
6264
|
+
return lines.join("\n");
|
|
6265
|
+
}
|
|
6266
|
+
function historyEntryToJson(entry, index) {
|
|
6267
|
+
const result = {
|
|
6268
|
+
index,
|
|
6269
|
+
type: entry.type,
|
|
6270
|
+
timestamp: entry.timestamp,
|
|
6271
|
+
txHash: entry.txHash,
|
|
6272
|
+
chainId: entry.chainId,
|
|
6273
|
+
feed: entry.feed
|
|
6274
|
+
};
|
|
6275
|
+
if (entry.sender) {
|
|
6276
|
+
result.sender = entry.sender;
|
|
6277
|
+
}
|
|
6278
|
+
if (entry.text) {
|
|
6279
|
+
result.text = entry.text;
|
|
6280
|
+
}
|
|
6281
|
+
if (entry.postId) {
|
|
6282
|
+
result.postId = entry.postId;
|
|
6283
|
+
}
|
|
6284
|
+
return result;
|
|
6285
|
+
}
|
|
6286
|
+
function validateType(type) {
|
|
6287
|
+
const validTypes = ["post", "comment", "register"];
|
|
6288
|
+
if (!validTypes.includes(type)) {
|
|
6289
|
+
console.error(
|
|
6290
|
+
chalk4.red(
|
|
6291
|
+
`Invalid type "${type}". Must be one of: ${validTypes.join(", ")}`
|
|
6292
|
+
)
|
|
6293
|
+
);
|
|
6294
|
+
process.exit(1);
|
|
6295
|
+
}
|
|
6296
|
+
return type;
|
|
6297
|
+
}
|
|
6298
|
+
async function executeFeedHistory(options) {
|
|
6299
|
+
if (options.clear) {
|
|
6300
|
+
const count = getHistoryCount();
|
|
6301
|
+
if (count === 0) {
|
|
6302
|
+
console.log(chalk4.gray("History is already empty."));
|
|
6303
|
+
return;
|
|
6304
|
+
}
|
|
6305
|
+
console.log(chalk4.yellow(`This will delete ${count} history entries.`));
|
|
6306
|
+
if (!options.force) {
|
|
6307
|
+
const confirmed = await confirm(
|
|
6308
|
+
chalk4.red("\nAre you sure you want to clear history?")
|
|
6309
|
+
);
|
|
6310
|
+
if (!confirmed) {
|
|
6311
|
+
console.log(chalk4.gray("Cancelled."));
|
|
6312
|
+
return;
|
|
6313
|
+
}
|
|
6314
|
+
}
|
|
6315
|
+
clearHistory();
|
|
6316
|
+
console.log(chalk4.green("History cleared."));
|
|
6317
|
+
return;
|
|
6318
|
+
}
|
|
6319
|
+
let entries;
|
|
6320
|
+
if (options.type) {
|
|
6321
|
+
const validType = validateType(options.type);
|
|
6322
|
+
entries = getHistoryByType(validType, options.limit);
|
|
6323
|
+
} else {
|
|
6324
|
+
entries = getHistory(options.limit);
|
|
6325
|
+
}
|
|
6326
|
+
if (entries.length === 0) {
|
|
6327
|
+
if (options.json) {
|
|
6328
|
+
printJson([]);
|
|
6329
|
+
} else {
|
|
6330
|
+
console.log(chalk4.gray("No history entries found."));
|
|
6331
|
+
console.log(
|
|
6332
|
+
chalk4.gray(
|
|
6333
|
+
"History is recorded when you post, comment, or register feeds."
|
|
6334
|
+
)
|
|
6335
|
+
);
|
|
6336
|
+
}
|
|
6337
|
+
return;
|
|
6338
|
+
}
|
|
6339
|
+
if (options.json) {
|
|
6340
|
+
const jsonEntries = entries.map(
|
|
6341
|
+
(entry, idx) => historyEntryToJson(entry, idx)
|
|
6342
|
+
);
|
|
6343
|
+
printJson(jsonEntries);
|
|
6344
|
+
} else {
|
|
6345
|
+
const totalCount = getHistoryCount();
|
|
6346
|
+
const typeFilter = options.type ? ` (type: ${options.type})` : "";
|
|
6347
|
+
console.log(
|
|
6348
|
+
chalk4.cyan(`Feed History${typeFilter} (${entries.length} of ${totalCount})
|
|
6349
|
+
`)
|
|
6350
|
+
);
|
|
6351
|
+
for (let i = 0; i < entries.length; i++) {
|
|
6352
|
+
console.log(formatHistoryEntry(entries[i], i));
|
|
6353
|
+
if (i < entries.length - 1) {
|
|
6354
|
+
console.log("");
|
|
6355
|
+
}
|
|
6356
|
+
}
|
|
6357
|
+
}
|
|
6358
|
+
}
|
|
6359
|
+
function registerFeedHistoryCommand(parent) {
|
|
6360
|
+
parent.command("history").description("View feed activity history (posts, comments, registrations)").option(
|
|
6361
|
+
"--limit <n>",
|
|
6362
|
+
"Limit number of entries",
|
|
6363
|
+
(value) => parseInt(value, 10)
|
|
6364
|
+
).option("--type <type>", "Filter by type: post, comment, or register").option("--json", "Output as JSON").option("--clear", "Clear all history").option("--force", "Skip confirmation prompt for --clear").action(async (options) => {
|
|
6365
|
+
await executeFeedHistory(options);
|
|
6366
|
+
});
|
|
6367
|
+
}
|
|
6368
|
+
|
|
6369
|
+
// src/commands/feed/index.ts
|
|
6370
|
+
function registerFeedCommand(program2) {
|
|
6371
|
+
const feedCommand = program2.command("feed").description("Feed operations (read/write posts, comments, manage feeds)");
|
|
6372
|
+
registerFeedListCommand(feedCommand);
|
|
6373
|
+
registerFeedReadCommand(feedCommand);
|
|
6374
|
+
registerFeedPostCommand(feedCommand);
|
|
6375
|
+
registerFeedCommentWriteCommand(feedCommand);
|
|
6376
|
+
registerFeedCommentReadCommand(feedCommand);
|
|
6377
|
+
registerFeedRegisterCommand(feedCommand);
|
|
6378
|
+
registerFeedRepliesCommand(feedCommand);
|
|
6379
|
+
registerFeedPostsCommand(feedCommand);
|
|
6380
|
+
registerFeedConfigCommand(feedCommand);
|
|
6381
|
+
registerFeedHistoryCommand(feedCommand);
|
|
4155
6382
|
}
|
|
4156
6383
|
|
|
4157
6384
|
// src/cli/index.ts
|
|
@@ -4171,6 +6398,7 @@ registerInfoCommand(program);
|
|
|
4171
6398
|
registerTokenCommand(program);
|
|
4172
6399
|
registerProfileCommand(program);
|
|
4173
6400
|
registerBazaarCommand(program);
|
|
6401
|
+
registerFeedCommand(program);
|
|
4174
6402
|
program.parse();
|
|
4175
6403
|
//# sourceMappingURL=index.mjs.map
|
|
4176
6404
|
//# sourceMappingURL=index.mjs.map
|