@btc-vision/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +2 -0
- package/.github/dependabot.yml +9 -0
- package/.github/workflows/ci.yml +48 -0
- package/.prettierrc.json +12 -0
- package/CONTRIBUTING.md +56 -0
- package/LICENSE +190 -0
- package/NOTICE +17 -0
- package/README.md +509 -0
- package/SECURITY.md +35 -0
- package/build/commands/AcceptCommand.d.ts +7 -0
- package/build/commands/AcceptCommand.js +110 -0
- package/build/commands/BaseCommand.d.ts +12 -0
- package/build/commands/BaseCommand.js +27 -0
- package/build/commands/CompileCommand.d.ts +7 -0
- package/build/commands/CompileCommand.js +138 -0
- package/build/commands/ConfigCommand.d.ts +17 -0
- package/build/commands/ConfigCommand.js +124 -0
- package/build/commands/DeprecateCommand.d.ts +7 -0
- package/build/commands/DeprecateCommand.js +112 -0
- package/build/commands/InfoCommand.d.ts +10 -0
- package/build/commands/InfoCommand.js +223 -0
- package/build/commands/InitCommand.d.ts +16 -0
- package/build/commands/InitCommand.js +336 -0
- package/build/commands/InstallCommand.d.ts +7 -0
- package/build/commands/InstallCommand.js +130 -0
- package/build/commands/KeygenCommand.d.ts +13 -0
- package/build/commands/KeygenCommand.js +133 -0
- package/build/commands/ListCommand.d.ts +7 -0
- package/build/commands/ListCommand.js +117 -0
- package/build/commands/LoginCommand.d.ts +9 -0
- package/build/commands/LoginCommand.js +139 -0
- package/build/commands/LogoutCommand.d.ts +7 -0
- package/build/commands/LogoutCommand.js +57 -0
- package/build/commands/PublishCommand.d.ts +7 -0
- package/build/commands/PublishCommand.js +163 -0
- package/build/commands/SearchCommand.d.ts +7 -0
- package/build/commands/SearchCommand.js +97 -0
- package/build/commands/SignCommand.d.ts +7 -0
- package/build/commands/SignCommand.js +80 -0
- package/build/commands/TransferCommand.d.ts +8 -0
- package/build/commands/TransferCommand.js +179 -0
- package/build/commands/UndeprecateCommand.d.ts +7 -0
- package/build/commands/UndeprecateCommand.js +95 -0
- package/build/commands/UpdateCommand.d.ts +7 -0
- package/build/commands/UpdateCommand.js +130 -0
- package/build/commands/VerifyCommand.d.ts +7 -0
- package/build/commands/VerifyCommand.js +167 -0
- package/build/commands/WhoamiCommand.d.ts +7 -0
- package/build/commands/WhoamiCommand.js +84 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +64 -0
- package/build/lib/PackageRegistry.abi.d.ts +2 -0
- package/build/lib/PackageRegistry.abi.js +356 -0
- package/build/lib/binary.d.ts +16 -0
- package/build/lib/binary.js +165 -0
- package/build/lib/config.d.ts +11 -0
- package/build/lib/config.js +160 -0
- package/build/lib/credentials.d.ts +10 -0
- package/build/lib/credentials.js +89 -0
- package/build/lib/ipfs.d.ts +16 -0
- package/build/lib/ipfs.js +209 -0
- package/build/lib/manifest.d.ts +14 -0
- package/build/lib/manifest.js +88 -0
- package/build/lib/provider.d.ts +9 -0
- package/build/lib/provider.js +48 -0
- package/build/lib/registry.d.ts +58 -0
- package/build/lib/registry.js +197 -0
- package/build/lib/wallet.d.ts +32 -0
- package/build/lib/wallet.js +114 -0
- package/build/types/PackageRegistry.d.ts +177 -0
- package/build/types/PackageRegistry.js +1 -0
- package/build/types/index.d.ts +30 -0
- package/build/types/index.js +52 -0
- package/eslint.config.js +41 -0
- package/gulpfile.js +41 -0
- package/package.json +83 -0
- package/src/commands/AcceptCommand.ts +151 -0
- package/src/commands/BaseCommand.ts +59 -0
- package/src/commands/CompileCommand.ts +196 -0
- package/src/commands/ConfigCommand.ts +144 -0
- package/src/commands/DeprecateCommand.ts +156 -0
- package/src/commands/InfoCommand.ts +293 -0
- package/src/commands/InitCommand.ts +465 -0
- package/src/commands/InstallCommand.ts +179 -0
- package/src/commands/KeygenCommand.ts +157 -0
- package/src/commands/ListCommand.ts +169 -0
- package/src/commands/LoginCommand.ts +197 -0
- package/src/commands/LogoutCommand.ts +76 -0
- package/src/commands/PublishCommand.ts +230 -0
- package/src/commands/SearchCommand.ts +141 -0
- package/src/commands/SignCommand.ts +122 -0
- package/src/commands/TransferCommand.ts +235 -0
- package/src/commands/UndeprecateCommand.ts +134 -0
- package/src/commands/UpdateCommand.ts +200 -0
- package/src/commands/VerifyCommand.ts +228 -0
- package/src/commands/WhoamiCommand.ts +113 -0
- package/src/index.ts +86 -0
- package/src/lib/PackageRegistry.abi.json +765 -0
- package/src/lib/PackageRegistry.abi.ts +365 -0
- package/src/lib/binary.ts +336 -0
- package/src/lib/config.ts +265 -0
- package/src/lib/credentials.ts +176 -0
- package/src/lib/ipfs.ts +369 -0
- package/src/lib/manifest.ts +172 -0
- package/src/lib/provider.ts +121 -0
- package/src/lib/registry.ts +464 -0
- package/src/lib/wallet.ts +271 -0
- package/src/types/PackageRegistry.ts +344 -0
- package/src/types/index.ts +145 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { IParsedPluginFile, IPluginMetadata } from '@btc-vision/plugin-sdk';
|
|
2
|
+
import { CLIMldsaLevel } from '../types/index.js';
|
|
3
|
+
export declare function parseOpnetBinary(data: Buffer): IParsedPluginFile;
|
|
4
|
+
export declare function computeChecksum(metadata: Buffer, bytecode: Buffer, proto: Buffer): Buffer;
|
|
5
|
+
export declare function verifyChecksum(parsed: IParsedPluginFile): boolean;
|
|
6
|
+
export declare function buildOpnetBinary(options: {
|
|
7
|
+
mldsaLevel: CLIMldsaLevel;
|
|
8
|
+
publicKey: Buffer;
|
|
9
|
+
signature: Buffer;
|
|
10
|
+
metadata: IPluginMetadata;
|
|
11
|
+
bytecode: Buffer;
|
|
12
|
+
proto?: Buffer;
|
|
13
|
+
}): Buffer;
|
|
14
|
+
export declare function extractMetadata(data: Buffer): IPluginMetadata | null;
|
|
15
|
+
export declare function getParsedMldsaLevel(parsed: IParsedPluginFile): CLIMldsaLevel;
|
|
16
|
+
export declare function formatFileSize(bytes: number): string;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
import { PLUGIN_MAGIC_BYTES, PLUGIN_FORMAT_VERSION, MLDSA_PUBLIC_KEY_SIZES, MLDSA_SIGNATURE_SIZES, } from '@btc-vision/plugin-sdk';
|
|
3
|
+
import { cliLevelToMLDSALevel, mldsaLevelToCLI } from '../types/index.js';
|
|
4
|
+
export function parseOpnetBinary(data) {
|
|
5
|
+
let offset = 0;
|
|
6
|
+
if (data.length < 8 + 4 + 1) {
|
|
7
|
+
throw new Error('Binary too small: missing header');
|
|
8
|
+
}
|
|
9
|
+
const magic = data.subarray(offset, offset + 8);
|
|
10
|
+
offset += 8;
|
|
11
|
+
if (!magic.equals(PLUGIN_MAGIC_BYTES)) {
|
|
12
|
+
throw new Error(`Invalid magic bytes: expected ${PLUGIN_MAGIC_BYTES.toString('hex')}, got ${magic.toString('hex')}`);
|
|
13
|
+
}
|
|
14
|
+
const formatVersion = data.readUInt32LE(offset);
|
|
15
|
+
offset += 4;
|
|
16
|
+
if (formatVersion !== PLUGIN_FORMAT_VERSION) {
|
|
17
|
+
throw new Error(`Unsupported format version: ${formatVersion} (expected ${PLUGIN_FORMAT_VERSION})`);
|
|
18
|
+
}
|
|
19
|
+
const mldsaLevelValue = data.readUInt8(offset);
|
|
20
|
+
offset += 1;
|
|
21
|
+
if (mldsaLevelValue > 2) {
|
|
22
|
+
throw new Error(`Invalid MLDSA level: ${mldsaLevelValue} (must be 0, 1, or 2)`);
|
|
23
|
+
}
|
|
24
|
+
const mldsaLevel = mldsaLevelValue;
|
|
25
|
+
const publicKeySize = MLDSA_PUBLIC_KEY_SIZES[mldsaLevel];
|
|
26
|
+
const signatureSize = MLDSA_SIGNATURE_SIZES[mldsaLevel];
|
|
27
|
+
const minSize = offset + publicKeySize + signatureSize + 4 + 4 + 4 + 32;
|
|
28
|
+
if (data.length < minSize) {
|
|
29
|
+
throw new Error(`Binary truncated: expected at least ${minSize} bytes, got ${data.length}`);
|
|
30
|
+
}
|
|
31
|
+
const publicKey = Buffer.from(data.subarray(offset, offset + publicKeySize));
|
|
32
|
+
offset += publicKeySize;
|
|
33
|
+
const signature = Buffer.from(data.subarray(offset, offset + signatureSize));
|
|
34
|
+
offset += signatureSize;
|
|
35
|
+
const metadataLength = data.readUInt32LE(offset);
|
|
36
|
+
offset += 4;
|
|
37
|
+
if (offset + metadataLength > data.length) {
|
|
38
|
+
throw new Error(`Metadata section truncated at offset ${offset}`);
|
|
39
|
+
}
|
|
40
|
+
const metadataBytes = data.subarray(offset, offset + metadataLength);
|
|
41
|
+
offset += metadataLength;
|
|
42
|
+
let rawMetadata;
|
|
43
|
+
let metadata;
|
|
44
|
+
try {
|
|
45
|
+
rawMetadata = metadataBytes.toString('utf-8');
|
|
46
|
+
metadata = JSON.parse(rawMetadata);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
throw new Error('Malformed JSON metadata');
|
|
50
|
+
}
|
|
51
|
+
const bytecodeLength = data.readUInt32LE(offset);
|
|
52
|
+
offset += 4;
|
|
53
|
+
if (offset + bytecodeLength > data.length) {
|
|
54
|
+
throw new Error(`Bytecode section truncated at offset ${offset}`);
|
|
55
|
+
}
|
|
56
|
+
const bytecode = Buffer.from(data.subarray(offset, offset + bytecodeLength));
|
|
57
|
+
offset += bytecodeLength;
|
|
58
|
+
const protoLength = data.readUInt32LE(offset);
|
|
59
|
+
offset += 4;
|
|
60
|
+
if (offset + protoLength > data.length) {
|
|
61
|
+
throw new Error(`Proto section truncated at offset ${offset}`);
|
|
62
|
+
}
|
|
63
|
+
const proto = protoLength > 0 ? Buffer.from(data.subarray(offset, offset + protoLength)) : undefined;
|
|
64
|
+
offset += protoLength;
|
|
65
|
+
if (offset + 32 > data.length) {
|
|
66
|
+
throw new Error('Checksum truncated');
|
|
67
|
+
}
|
|
68
|
+
const checksum = Buffer.from(data.subarray(offset, offset + 32));
|
|
69
|
+
return {
|
|
70
|
+
formatVersion,
|
|
71
|
+
mldsaLevel,
|
|
72
|
+
publicKey,
|
|
73
|
+
signature,
|
|
74
|
+
metadata,
|
|
75
|
+
rawMetadata,
|
|
76
|
+
bytecode,
|
|
77
|
+
proto,
|
|
78
|
+
checksum,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export function computeChecksum(metadata, bytecode, proto) {
|
|
82
|
+
const hash = crypto.createHash('sha256');
|
|
83
|
+
hash.update(metadata);
|
|
84
|
+
hash.update(bytecode);
|
|
85
|
+
hash.update(proto);
|
|
86
|
+
return hash.digest();
|
|
87
|
+
}
|
|
88
|
+
export function verifyChecksum(parsed) {
|
|
89
|
+
const metadataBytes = Buffer.from(parsed.rawMetadata, 'utf-8');
|
|
90
|
+
const computed = computeChecksum(metadataBytes, parsed.bytecode, parsed.proto ?? Buffer.alloc(0));
|
|
91
|
+
return computed.equals(parsed.checksum);
|
|
92
|
+
}
|
|
93
|
+
export function buildOpnetBinary(options) {
|
|
94
|
+
const { mldsaLevel, publicKey, signature, metadata, bytecode, proto = Buffer.alloc(0) } = options;
|
|
95
|
+
const sdkLevel = cliLevelToMLDSALevel(mldsaLevel);
|
|
96
|
+
const expectedPkSize = MLDSA_PUBLIC_KEY_SIZES[sdkLevel];
|
|
97
|
+
const expectedSigSize = MLDSA_SIGNATURE_SIZES[sdkLevel];
|
|
98
|
+
if (publicKey.length !== expectedPkSize) {
|
|
99
|
+
throw new Error(`Public key size mismatch: expected ${expectedPkSize}, got ${publicKey.length}`);
|
|
100
|
+
}
|
|
101
|
+
if (signature.length !== expectedSigSize) {
|
|
102
|
+
throw new Error(`Signature size mismatch: expected ${expectedSigSize}, got ${signature.length}`);
|
|
103
|
+
}
|
|
104
|
+
const metadataStr = JSON.stringify(metadata);
|
|
105
|
+
const metadataBytes = Buffer.from(metadataStr, 'utf-8');
|
|
106
|
+
const checksum = computeChecksum(metadataBytes, bytecode, proto);
|
|
107
|
+
const totalSize = 8 +
|
|
108
|
+
4 +
|
|
109
|
+
1 +
|
|
110
|
+
publicKey.length +
|
|
111
|
+
signature.length +
|
|
112
|
+
4 + metadataBytes.length +
|
|
113
|
+
4 + bytecode.length +
|
|
114
|
+
4 + proto.length +
|
|
115
|
+
32;
|
|
116
|
+
const buffer = Buffer.alloc(totalSize);
|
|
117
|
+
let offset = 0;
|
|
118
|
+
PLUGIN_MAGIC_BYTES.copy(buffer, offset);
|
|
119
|
+
offset += 8;
|
|
120
|
+
buffer.writeUInt32LE(PLUGIN_FORMAT_VERSION, offset);
|
|
121
|
+
offset += 4;
|
|
122
|
+
buffer.writeUInt8(sdkLevel, offset);
|
|
123
|
+
offset += 1;
|
|
124
|
+
publicKey.copy(buffer, offset);
|
|
125
|
+
offset += publicKey.length;
|
|
126
|
+
signature.copy(buffer, offset);
|
|
127
|
+
offset += signature.length;
|
|
128
|
+
buffer.writeUInt32LE(metadataBytes.length, offset);
|
|
129
|
+
offset += 4;
|
|
130
|
+
metadataBytes.copy(buffer, offset);
|
|
131
|
+
offset += metadataBytes.length;
|
|
132
|
+
buffer.writeUInt32LE(bytecode.length, offset);
|
|
133
|
+
offset += 4;
|
|
134
|
+
bytecode.copy(buffer, offset);
|
|
135
|
+
offset += bytecode.length;
|
|
136
|
+
buffer.writeUInt32LE(proto.length, offset);
|
|
137
|
+
offset += 4;
|
|
138
|
+
proto.copy(buffer, offset);
|
|
139
|
+
offset += proto.length;
|
|
140
|
+
checksum.copy(buffer, offset);
|
|
141
|
+
return buffer;
|
|
142
|
+
}
|
|
143
|
+
export function extractMetadata(data) {
|
|
144
|
+
try {
|
|
145
|
+
const parsed = parseOpnetBinary(data);
|
|
146
|
+
return parsed.metadata;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
export function getParsedMldsaLevel(parsed) {
|
|
153
|
+
return mldsaLevelToCLI(parsed.mldsaLevel);
|
|
154
|
+
}
|
|
155
|
+
export function formatFileSize(bytes) {
|
|
156
|
+
if (bytes < 1024) {
|
|
157
|
+
return `${bytes} B`;
|
|
158
|
+
}
|
|
159
|
+
else if (bytes < 1024 * 1024) {
|
|
160
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CLIConfig, NetworkName, CLIMldsaLevel } from '../types/index.js';
|
|
2
|
+
export declare function ensureConfigDir(): void;
|
|
3
|
+
export declare function loadConfig(): CLIConfig;
|
|
4
|
+
export declare function saveConfig(config: CLIConfig): void;
|
|
5
|
+
export declare function getConfigValue(key: string): unknown;
|
|
6
|
+
export declare function setConfigValue(key: string, value: unknown): void;
|
|
7
|
+
export declare function getRpcUrl(network?: NetworkName): string;
|
|
8
|
+
export declare function getRegistryAddress(network?: NetworkName): string;
|
|
9
|
+
export declare function getDefaultMldsaLevel(): CLIMldsaLevel;
|
|
10
|
+
export declare function getDefaultNetwork(): NetworkName;
|
|
11
|
+
export declare function displayConfig(): string;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
const CONFIG_DIR = path.join(os.homedir(), '.opnet');
|
|
5
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
6
|
+
const DEFAULT_CONFIG = {
|
|
7
|
+
defaultNetwork: 'regtest',
|
|
8
|
+
rpcUrls: {
|
|
9
|
+
mainnet: 'https://api.opnet.org',
|
|
10
|
+
testnet: 'https://testnet.opnet.org',
|
|
11
|
+
regtest: 'https://regtest.opnet.org',
|
|
12
|
+
},
|
|
13
|
+
ipfsGateway: 'https://ipfs.opnet.org/ipfs/',
|
|
14
|
+
ipfsGateways: [
|
|
15
|
+
'https://ipfs.opnet.org/ipfs/',
|
|
16
|
+
'https://ipfs.io/ipfs/',
|
|
17
|
+
'https://cloudflare-ipfs.com/ipfs/',
|
|
18
|
+
'https://dweb.link/ipfs/',
|
|
19
|
+
],
|
|
20
|
+
ipfsPinningEndpoint: 'https://ipfs.opnet.org/api/v0/add',
|
|
21
|
+
ipfsPinningApiKey: '',
|
|
22
|
+
ipfsPinningAuthHeader: 'Authorization',
|
|
23
|
+
registryAddresses: {
|
|
24
|
+
mainnet: '',
|
|
25
|
+
testnet: '',
|
|
26
|
+
regtest: '',
|
|
27
|
+
},
|
|
28
|
+
defaultMldsaLevel: 44,
|
|
29
|
+
indexerUrl: 'https://indexer.opnet.org',
|
|
30
|
+
};
|
|
31
|
+
function getEnvOverrides() {
|
|
32
|
+
const overrides = {};
|
|
33
|
+
if (process.env.OPNET_NETWORK) {
|
|
34
|
+
const network = process.env.OPNET_NETWORK;
|
|
35
|
+
if (['mainnet', 'testnet', 'regtest'].includes(network)) {
|
|
36
|
+
overrides.defaultNetwork = network;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (process.env.OPNET_RPC_URL) {
|
|
40
|
+
const config = loadConfig();
|
|
41
|
+
overrides.rpcUrls = {
|
|
42
|
+
...config.rpcUrls,
|
|
43
|
+
[config.defaultNetwork]: process.env.OPNET_RPC_URL,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (process.env.OPNET_IPFS_GATEWAY) {
|
|
47
|
+
overrides.ipfsGateway = process.env.OPNET_IPFS_GATEWAY;
|
|
48
|
+
}
|
|
49
|
+
if (process.env.OPNET_IPFS_PINNING_ENDPOINT) {
|
|
50
|
+
overrides.ipfsPinningEndpoint = process.env.OPNET_IPFS_PINNING_ENDPOINT;
|
|
51
|
+
}
|
|
52
|
+
if (process.env.OPNET_IPFS_PINNING_KEY) {
|
|
53
|
+
overrides.ipfsPinningApiKey = process.env.OPNET_IPFS_PINNING_KEY;
|
|
54
|
+
}
|
|
55
|
+
if (process.env.OPNET_REGISTRY_ADDRESS) {
|
|
56
|
+
const config = loadConfig();
|
|
57
|
+
overrides.registryAddresses = {
|
|
58
|
+
...config.registryAddresses,
|
|
59
|
+
[config.defaultNetwork]: process.env.OPNET_REGISTRY_ADDRESS,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (process.env.OPNET_INDEXER_URL) {
|
|
63
|
+
overrides.indexerUrl = process.env.OPNET_INDEXER_URL;
|
|
64
|
+
}
|
|
65
|
+
return overrides;
|
|
66
|
+
}
|
|
67
|
+
export function ensureConfigDir() {
|
|
68
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
69
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function loadConfig() {
|
|
73
|
+
ensureConfigDir();
|
|
74
|
+
let fileConfig = {};
|
|
75
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
76
|
+
try {
|
|
77
|
+
const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
78
|
+
fileConfig = JSON.parse(content);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const envOverrides = getEnvOverrides();
|
|
84
|
+
return {
|
|
85
|
+
...DEFAULT_CONFIG,
|
|
86
|
+
...fileConfig,
|
|
87
|
+
...envOverrides,
|
|
88
|
+
rpcUrls: {
|
|
89
|
+
...DEFAULT_CONFIG.rpcUrls,
|
|
90
|
+
...fileConfig.rpcUrls,
|
|
91
|
+
...envOverrides.rpcUrls,
|
|
92
|
+
},
|
|
93
|
+
ipfsGateways: fileConfig.ipfsGateways || DEFAULT_CONFIG.ipfsGateways,
|
|
94
|
+
registryAddresses: {
|
|
95
|
+
...DEFAULT_CONFIG.registryAddresses,
|
|
96
|
+
...fileConfig.registryAddresses,
|
|
97
|
+
...envOverrides.registryAddresses,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export function saveConfig(config) {
|
|
102
|
+
ensureConfigDir();
|
|
103
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 4), { mode: 0o600 });
|
|
104
|
+
}
|
|
105
|
+
export function getConfigValue(key) {
|
|
106
|
+
const config = loadConfig();
|
|
107
|
+
const keys = key.split('.');
|
|
108
|
+
let value = config;
|
|
109
|
+
for (const k of keys) {
|
|
110
|
+
if (value && typeof value === 'object' && k in value) {
|
|
111
|
+
value = value[k];
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
export function setConfigValue(key, value) {
|
|
120
|
+
const config = loadConfig();
|
|
121
|
+
const keys = key.split('.');
|
|
122
|
+
let target = config;
|
|
123
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
124
|
+
const k = keys[i];
|
|
125
|
+
if (!(k in target) || typeof target[k] !== 'object') {
|
|
126
|
+
target[k] = {};
|
|
127
|
+
}
|
|
128
|
+
target = target[k];
|
|
129
|
+
}
|
|
130
|
+
const finalKey = keys[keys.length - 1];
|
|
131
|
+
target[finalKey] = value;
|
|
132
|
+
saveConfig(config);
|
|
133
|
+
}
|
|
134
|
+
export function getRpcUrl(network) {
|
|
135
|
+
const config = loadConfig();
|
|
136
|
+
const targetNetwork = network || config.defaultNetwork;
|
|
137
|
+
return config.rpcUrls[targetNetwork] || config.rpcUrls.mainnet;
|
|
138
|
+
}
|
|
139
|
+
export function getRegistryAddress(network) {
|
|
140
|
+
const config = loadConfig();
|
|
141
|
+
const targetNetwork = network || config.defaultNetwork;
|
|
142
|
+
const address = config.registryAddresses[targetNetwork];
|
|
143
|
+
if (!address) {
|
|
144
|
+
throw new Error(`Registry address not configured for network: ${targetNetwork}\n` +
|
|
145
|
+
`Run: opnet config set registryAddresses.${targetNetwork} <address>`);
|
|
146
|
+
}
|
|
147
|
+
return address;
|
|
148
|
+
}
|
|
149
|
+
export function getDefaultMldsaLevel() {
|
|
150
|
+
const config = loadConfig();
|
|
151
|
+
return config.defaultMldsaLevel;
|
|
152
|
+
}
|
|
153
|
+
export function getDefaultNetwork() {
|
|
154
|
+
const config = loadConfig();
|
|
155
|
+
return config.defaultNetwork;
|
|
156
|
+
}
|
|
157
|
+
export function displayConfig() {
|
|
158
|
+
const config = loadConfig();
|
|
159
|
+
return JSON.stringify(config, null, 2);
|
|
160
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CLICredentials, NetworkName, CLIMldsaLevel } from '../types/index.js';
|
|
2
|
+
export declare function loadCredentials(): CLICredentials | null;
|
|
3
|
+
export declare function saveCredentials(credentials: CLICredentials): void;
|
|
4
|
+
export declare function deleteCredentials(): boolean;
|
|
5
|
+
export declare function hasCredentials(): boolean;
|
|
6
|
+
export declare function canSign(credentials: CLICredentials | null): boolean;
|
|
7
|
+
export declare function getCredentialSource(): string;
|
|
8
|
+
export declare function maskSensitive(value: string, showChars?: number): string;
|
|
9
|
+
export declare function isValidMldsaLevel(level: number): level is CLIMldsaLevel;
|
|
10
|
+
export declare function isValidNetwork(network: string): network is NetworkName;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import { ensureConfigDir } from './config.js';
|
|
5
|
+
const CREDENTIALS_FILE = path.join(os.homedir(), '.opnet', 'credentials.json');
|
|
6
|
+
export function loadCredentials() {
|
|
7
|
+
if (process.env.OPNET_MNEMONIC) {
|
|
8
|
+
return {
|
|
9
|
+
mnemonic: process.env.OPNET_MNEMONIC,
|
|
10
|
+
mldsaLevel: parseInt(process.env.OPNET_MLDSA_LEVEL || '44', 10) || 44,
|
|
11
|
+
network: process.env.OPNET_NETWORK || 'mainnet',
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
if (process.env.OPNET_PRIVATE_KEY || process.env.OPNET_MLDSA_KEY) {
|
|
15
|
+
return {
|
|
16
|
+
wif: process.env.OPNET_PRIVATE_KEY,
|
|
17
|
+
mldsaPrivateKey: process.env.OPNET_MLDSA_KEY,
|
|
18
|
+
mldsaLevel: parseInt(process.env.OPNET_MLDSA_LEVEL || '44', 10) || 44,
|
|
19
|
+
network: process.env.OPNET_NETWORK || 'mainnet',
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (!fs.existsSync(CREDENTIALS_FILE)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const content = fs.readFileSync(CREDENTIALS_FILE, 'utf-8');
|
|
27
|
+
return JSON.parse(content);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function saveCredentials(credentials) {
|
|
34
|
+
ensureConfigDir();
|
|
35
|
+
fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(credentials, null, 4), {
|
|
36
|
+
mode: 0o600,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export function deleteCredentials() {
|
|
40
|
+
if (fs.existsSync(CREDENTIALS_FILE)) {
|
|
41
|
+
fs.unlinkSync(CREDENTIALS_FILE);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
export function hasCredentials() {
|
|
47
|
+
if (process.env.OPNET_MNEMONIC ||
|
|
48
|
+
process.env.OPNET_PRIVATE_KEY ||
|
|
49
|
+
process.env.OPNET_MLDSA_KEY) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
return fs.existsSync(CREDENTIALS_FILE);
|
|
53
|
+
}
|
|
54
|
+
export function canSign(credentials) {
|
|
55
|
+
if (!credentials) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
if (credentials.mnemonic) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
if (credentials.wif && credentials.mldsaPrivateKey) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
export function getCredentialSource() {
|
|
67
|
+
if (process.env.OPNET_MNEMONIC) {
|
|
68
|
+
return 'environment (OPNET_MNEMONIC)';
|
|
69
|
+
}
|
|
70
|
+
if (process.env.OPNET_PRIVATE_KEY || process.env.OPNET_MLDSA_KEY) {
|
|
71
|
+
return 'environment (OPNET_PRIVATE_KEY/OPNET_MLDSA_KEY)';
|
|
72
|
+
}
|
|
73
|
+
if (fs.existsSync(CREDENTIALS_FILE)) {
|
|
74
|
+
return CREDENTIALS_FILE;
|
|
75
|
+
}
|
|
76
|
+
return 'none';
|
|
77
|
+
}
|
|
78
|
+
export function maskSensitive(value, showChars = 4) {
|
|
79
|
+
if (value.length <= showChars * 2) {
|
|
80
|
+
return '*'.repeat(value.length);
|
|
81
|
+
}
|
|
82
|
+
return value.slice(0, showChars) + '...' + value.slice(-showChars);
|
|
83
|
+
}
|
|
84
|
+
export function isValidMldsaLevel(level) {
|
|
85
|
+
return level === 44 || level === 65 || level === 87;
|
|
86
|
+
}
|
|
87
|
+
export function isValidNetwork(network) {
|
|
88
|
+
return network === 'mainnet' || network === 'testnet' || network === 'regtest';
|
|
89
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface PinResult {
|
|
2
|
+
cid: string;
|
|
3
|
+
size: number;
|
|
4
|
+
}
|
|
5
|
+
export interface FetchResult {
|
|
6
|
+
data: Buffer;
|
|
7
|
+
size: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function pinToIPFS(data: Buffer, name?: string): Promise<PinResult>;
|
|
10
|
+
export declare function fetchFromIPFS(cid: string): Promise<FetchResult>;
|
|
11
|
+
export declare function buildGatewayUrl(gateway: string, cid: string): string;
|
|
12
|
+
export declare function isValidCid(cid: string): boolean;
|
|
13
|
+
export declare function downloadPlugin(cid: string, outputPath: string): Promise<number>;
|
|
14
|
+
export declare function uploadPlugin(filePath: string): Promise<PinResult>;
|
|
15
|
+
export declare function checkAvailability(cid: string): Promise<boolean>;
|
|
16
|
+
export declare function getGatewayStatus(): Promise<Record<string, boolean>>;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import * as https from 'https';
|
|
2
|
+
import * as http from 'http';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import { loadConfig } from './config.js';
|
|
5
|
+
async function httpRequest(url, options) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const parsedUrl = new URL(url);
|
|
8
|
+
const isHttps = parsedUrl.protocol === 'https:';
|
|
9
|
+
const lib = isHttps ? https : http;
|
|
10
|
+
const reqOptions = {
|
|
11
|
+
hostname: parsedUrl.hostname,
|
|
12
|
+
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
13
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
14
|
+
method: options.method,
|
|
15
|
+
headers: options.headers || {},
|
|
16
|
+
timeout: options.timeout || 30000,
|
|
17
|
+
};
|
|
18
|
+
const req = lib.request(reqOptions, (res) => {
|
|
19
|
+
const chunks = [];
|
|
20
|
+
res.on('data', (chunk) => {
|
|
21
|
+
chunks.push(chunk);
|
|
22
|
+
});
|
|
23
|
+
res.on('end', () => {
|
|
24
|
+
const body = Buffer.concat(chunks);
|
|
25
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
26
|
+
resolve(body);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage} - ${body.toString()}`));
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
req.on('error', reject);
|
|
34
|
+
req.on('timeout', () => {
|
|
35
|
+
req.destroy();
|
|
36
|
+
reject(new Error('Request timeout'));
|
|
37
|
+
});
|
|
38
|
+
if (options.body) {
|
|
39
|
+
req.write(options.body);
|
|
40
|
+
}
|
|
41
|
+
req.end();
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export async function pinToIPFS(data, name) {
|
|
45
|
+
const config = loadConfig();
|
|
46
|
+
const endpoint = config.ipfsPinningEndpoint;
|
|
47
|
+
if (!endpoint) {
|
|
48
|
+
throw new Error('IPFS pinning endpoint not configured. Run `opnet config set ipfsPinningEndpoint <url>`');
|
|
49
|
+
}
|
|
50
|
+
const boundary = '----FormBoundary' + Math.random().toString(36).substring(2);
|
|
51
|
+
const fileName = name || 'plugin.opnet';
|
|
52
|
+
const formParts = [];
|
|
53
|
+
formParts.push(Buffer.from(`--${boundary}\r\n` +
|
|
54
|
+
`Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n` +
|
|
55
|
+
`Content-Type: application/octet-stream\r\n\r\n`));
|
|
56
|
+
formParts.push(data);
|
|
57
|
+
formParts.push(Buffer.from('\r\n'));
|
|
58
|
+
formParts.push(Buffer.from(`--${boundary}--\r\n`));
|
|
59
|
+
const body = Buffer.concat(formParts);
|
|
60
|
+
const headers = {
|
|
61
|
+
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
62
|
+
'Content-Length': body.length.toString(),
|
|
63
|
+
};
|
|
64
|
+
if (config.ipfsPinningAuthHeader) {
|
|
65
|
+
const [headerName, headerValue] = config.ipfsPinningAuthHeader.split(':').map((s) => s.trim());
|
|
66
|
+
if (headerName && headerValue) {
|
|
67
|
+
headers[headerName] = headerValue;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else if (config.ipfsPinningApiKey) {
|
|
71
|
+
headers['Authorization'] = `Bearer ${config.ipfsPinningApiKey}`;
|
|
72
|
+
}
|
|
73
|
+
const url = new URL(endpoint);
|
|
74
|
+
let requestUrl;
|
|
75
|
+
if (url.hostname.includes('ipfs.opnet.org')) {
|
|
76
|
+
requestUrl = endpoint;
|
|
77
|
+
}
|
|
78
|
+
else if (url.hostname.includes('pinata')) {
|
|
79
|
+
requestUrl = 'https://api.pinata.cloud/pinning/pinFileToIPFS';
|
|
80
|
+
if (config.ipfsPinningApiKey) {
|
|
81
|
+
headers['pinata_api_key'] = config.ipfsPinningApiKey;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (url.hostname.includes('web3.storage') || url.hostname.includes('w3s.link')) {
|
|
85
|
+
requestUrl = endpoint.endsWith('/') ? endpoint + 'upload' : endpoint + '/upload';
|
|
86
|
+
}
|
|
87
|
+
else if (url.hostname.includes('nft.storage')) {
|
|
88
|
+
requestUrl = 'https://api.nft.storage/upload';
|
|
89
|
+
}
|
|
90
|
+
else if (url.pathname.includes('/api/v0/')) {
|
|
91
|
+
requestUrl = endpoint;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
requestUrl = endpoint.endsWith('/') ? endpoint + 'pins' : endpoint + '/pins';
|
|
95
|
+
}
|
|
96
|
+
const response = await httpRequest(requestUrl, {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers,
|
|
99
|
+
body,
|
|
100
|
+
timeout: 120000,
|
|
101
|
+
});
|
|
102
|
+
const result = JSON.parse(response.toString());
|
|
103
|
+
let cid;
|
|
104
|
+
if (typeof result.IpfsHash === 'string') {
|
|
105
|
+
cid = result.IpfsHash;
|
|
106
|
+
}
|
|
107
|
+
else if (typeof result.cid === 'string') {
|
|
108
|
+
cid = result.cid;
|
|
109
|
+
}
|
|
110
|
+
else if (typeof result.Hash === 'string') {
|
|
111
|
+
cid = result.Hash;
|
|
112
|
+
}
|
|
113
|
+
else if (result.value && typeof result.value.cid === 'string') {
|
|
114
|
+
cid = result.value.cid;
|
|
115
|
+
}
|
|
116
|
+
if (!cid) {
|
|
117
|
+
throw new Error(`Failed to extract CID from pinning response: ${JSON.stringify(result)}`);
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
cid,
|
|
121
|
+
size: data.length,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
export async function fetchFromIPFS(cid) {
|
|
125
|
+
const config = loadConfig();
|
|
126
|
+
const gateways = config.ipfsGateways.length > 0 ? config.ipfsGateways : [config.ipfsGateway];
|
|
127
|
+
let lastError;
|
|
128
|
+
for (const gateway of gateways) {
|
|
129
|
+
try {
|
|
130
|
+
const url = buildGatewayUrl(gateway, cid);
|
|
131
|
+
const data = await httpRequest(url, {
|
|
132
|
+
method: 'GET',
|
|
133
|
+
timeout: 60000,
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
data,
|
|
137
|
+
size: data.length,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
throw new Error(`Failed to fetch from all gateways: ${lastError?.message}`);
|
|
146
|
+
}
|
|
147
|
+
export function buildGatewayUrl(gateway, cid) {
|
|
148
|
+
const base = gateway.replace(/\/$/, '');
|
|
149
|
+
if (base.includes('{cid}')) {
|
|
150
|
+
return base.replace('{cid}', cid);
|
|
151
|
+
}
|
|
152
|
+
else if (base.includes('/ipfs/')) {
|
|
153
|
+
return `${base}${cid}`;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
return `${base}/ipfs/${cid}`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
export function isValidCid(cid) {
|
|
160
|
+
if (cid.startsWith('Qm') && cid.length === 46) {
|
|
161
|
+
return /^Qm[1-9A-HJ-NP-Za-km-z]{44}$/.test(cid);
|
|
162
|
+
}
|
|
163
|
+
else if (cid.startsWith('ba')) {
|
|
164
|
+
return /^ba[a-z2-7]{57,}$/.test(cid);
|
|
165
|
+
}
|
|
166
|
+
else if (cid.startsWith('b')) {
|
|
167
|
+
return cid.length >= 50;
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
export async function downloadPlugin(cid, outputPath) {
|
|
172
|
+
const result = await fetchFromIPFS(cid);
|
|
173
|
+
fs.writeFileSync(outputPath, result.data);
|
|
174
|
+
return result.size;
|
|
175
|
+
}
|
|
176
|
+
export async function uploadPlugin(filePath) {
|
|
177
|
+
const data = fs.readFileSync(filePath);
|
|
178
|
+
const fileName = filePath.split('/').pop() || 'plugin.opnet';
|
|
179
|
+
return pinToIPFS(data, fileName);
|
|
180
|
+
}
|
|
181
|
+
export async function checkAvailability(cid) {
|
|
182
|
+
try {
|
|
183
|
+
await fetchFromIPFS(cid);
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
export async function getGatewayStatus() {
|
|
191
|
+
const config = loadConfig();
|
|
192
|
+
const gateways = config.ipfsGateways.length > 0 ? config.ipfsGateways : [config.ipfsGateway];
|
|
193
|
+
const testCid = 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn';
|
|
194
|
+
const status = {};
|
|
195
|
+
await Promise.all(gateways.map(async (gateway) => {
|
|
196
|
+
try {
|
|
197
|
+
const url = buildGatewayUrl(gateway, testCid);
|
|
198
|
+
await httpRequest(url, {
|
|
199
|
+
method: 'HEAD',
|
|
200
|
+
timeout: 10000,
|
|
201
|
+
});
|
|
202
|
+
status[gateway] = true;
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
status[gateway] = false;
|
|
206
|
+
}
|
|
207
|
+
}));
|
|
208
|
+
return status;
|
|
209
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { IPluginMetadata, IPluginPermissions, IValidationResult } from '@btc-vision/plugin-sdk';
|
|
2
|
+
export declare function validatePluginName(name: string): string[];
|
|
3
|
+
export declare function validateManifest(manifest: Partial<IPluginMetadata>): IValidationResult;
|
|
4
|
+
export declare function validatePermissions(permissions: IPluginPermissions): string[];
|
|
5
|
+
export declare function loadManifest(manifestPath: string): IPluginMetadata;
|
|
6
|
+
export declare function saveManifest(manifestPath: string, manifest: IPluginMetadata): void;
|
|
7
|
+
export declare function createManifest(options: {
|
|
8
|
+
name: string;
|
|
9
|
+
author: string;
|
|
10
|
+
email?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
pluginType: 'standalone' | 'library';
|
|
13
|
+
}): IPluginMetadata;
|
|
14
|
+
export declare function getManifestPath(dir?: string): string;
|