@5ive-tech/cli 1.0.4
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 +226 -0
- package/dist/assets/vm/five_vm_wasm.d.ts +762 -0
- package/dist/assets/vm/five_vm_wasm.js +3754 -0
- package/dist/assets/vm/five_vm_wasm_bg.wasm +0 -0
- package/dist/assets/vm/five_vm_wasm_bg.wasm.d.ts +247 -0
- package/dist/assets/vm/package.json +11 -0
- package/dist/cli.d.ts +47 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +343 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/analyze.d.ts +3 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +435 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/build.d.ts +3 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +66 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/compile.d.ts +3 -0
- package/dist/commands/compile.d.ts.map +1 -0
- package/dist/commands/compile.js +872 -0
- package/dist/commands/compile.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +431 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/deploy-and-execute.d.ts +3 -0
- package/dist/commands/deploy-and-execute.d.ts.map +1 -0
- package/dist/commands/deploy-and-execute.js +317 -0
- package/dist/commands/deploy-and-execute.js.map +1 -0
- package/dist/commands/deploy.d.ts +21 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +806 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/donate.d.ts +4 -0
- package/dist/commands/donate.d.ts.map +1 -0
- package/dist/commands/donate.js +104 -0
- package/dist/commands/donate.js.map +1 -0
- package/dist/commands/execute.d.ts +6 -0
- package/dist/commands/execute.d.ts.map +1 -0
- package/dist/commands/execute.js +749 -0
- package/dist/commands/execute.js.map +1 -0
- package/dist/commands/fmt.d.ts +3 -0
- package/dist/commands/fmt.d.ts.map +1 -0
- package/dist/commands/fmt.js +327 -0
- package/dist/commands/fmt.js.map +1 -0
- package/dist/commands/help.d.ts +6 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +224 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/index.d.ts +45 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +119 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +887 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/local.d.ts +3 -0
- package/dist/commands/local.d.ts.map +1 -0
- package/dist/commands/local.js +703 -0
- package/dist/commands/local.js.map +1 -0
- package/dist/commands/namespace.d.ts +3 -0
- package/dist/commands/namespace.d.ts.map +1 -0
- package/dist/commands/namespace.js +328 -0
- package/dist/commands/namespace.js.map +1 -0
- package/dist/commands/template.d.ts +4 -0
- package/dist/commands/template.d.ts.map +1 -0
- package/dist/commands/template.js +486 -0
- package/dist/commands/template.js.map +1 -0
- package/dist/commands/test.d.ts +6 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +890 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/version.d.ts +6 -0
- package/dist/commands/version.d.ts.map +1 -0
- package/dist/commands/version.js +339 -0
- package/dist/commands/version.js.map +1 -0
- package/dist/config/ConfigManager.d.ts +69 -0
- package/dist/config/ConfigManager.d.ts.map +1 -0
- package/dist/config/ConfigManager.js +261 -0
- package/dist/config/ConfigManager.js.map +1 -0
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +21 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/types.d.ts +35 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +105 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/project/ProjectLoader.d.ts +12 -0
- package/dist/project/ProjectLoader.d.ts.map +1 -0
- package/dist/project/ProjectLoader.js +115 -0
- package/dist/project/ProjectLoader.js.map +1 -0
- package/dist/types.d.ts +334 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/AccountFixtureGenerator.d.ts +48 -0
- package/dist/utils/AccountFixtureGenerator.d.ts.map +1 -0
- package/dist/utils/AccountFixtureGenerator.js +265 -0
- package/dist/utils/AccountFixtureGenerator.js.map +1 -0
- package/dist/utils/FiveFileManager.d.ts +96 -0
- package/dist/utils/FiveFileManager.d.ts.map +1 -0
- package/dist/utils/FiveFileManager.js +329 -0
- package/dist/utils/FiveFileManager.js.map +1 -0
- package/dist/utils/ascii-art.d.ts +72 -0
- package/dist/utils/ascii-art.d.ts.map +1 -0
- package/dist/utils/ascii-art.js +314 -0
- package/dist/utils/ascii-art.js.map +1 -0
- package/dist/utils/cli-ui.d.ts +39 -0
- package/dist/utils/cli-ui.d.ts.map +1 -0
- package/dist/utils/cli-ui.js +75 -0
- package/dist/utils/cli-ui.js.map +1 -0
- package/dist/utils/fileUtils.d.ts +25 -0
- package/dist/utils/fileUtils.d.ts.map +1 -0
- package/dist/utils/fileUtils.js +50 -0
- package/dist/utils/fileUtils.js.map +1 -0
- package/dist/utils/logger.d.ts +53 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +287 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/wasm/compiler.d.ts +101 -0
- package/dist/wasm/compiler.d.ts.map +1 -0
- package/dist/wasm/compiler.js +906 -0
- package/dist/wasm/compiler.js.map +1 -0
- package/dist/wasm/loader.d.ts +2 -0
- package/dist/wasm/loader.d.ts.map +1 -0
- package/dist/wasm/loader.js +90 -0
- package/dist/wasm/loader.js.map +1 -0
- package/dist/wasm/vm.d.ts +32 -0
- package/dist/wasm/vm.d.ts.map +1 -0
- package/dist/wasm/vm.js +440 -0
- package/dist/wasm/vm.js.map +1 -0
- package/package.json +100 -0
|
@@ -0,0 +1,806 @@
|
|
|
1
|
+
// Deploy command.
|
|
2
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
3
|
+
import { isAbsolute, join } from 'path';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { Connection, Keypair } from '@solana/web3.js';
|
|
6
|
+
import { parse as parseToml, stringify as stringifyToml } from '@iarna/toml';
|
|
7
|
+
import { FiveSDK, ProgramIdResolver } from '@5ive-tech/sdk';
|
|
8
|
+
import { ConfigManager } from '../config/ConfigManager.js';
|
|
9
|
+
import { FiveFileManager } from '../utils/FiveFileManager.js';
|
|
10
|
+
import { computeHash, loadBuildManifest, loadProjectConfig } from '../project/ProjectLoader.js';
|
|
11
|
+
import { section, keyValue, success as uiSuccess, error as uiError, isTTY } from '../utils/cli-ui.js';
|
|
12
|
+
/**
|
|
13
|
+
* Five deploy command implementation
|
|
14
|
+
*/
|
|
15
|
+
export const deployCommand = {
|
|
16
|
+
name: 'deploy',
|
|
17
|
+
description: 'Deploy bytecode to Solana',
|
|
18
|
+
aliases: ['d'],
|
|
19
|
+
options: [
|
|
20
|
+
{
|
|
21
|
+
flags: '-t, --target <target>',
|
|
22
|
+
description: 'Override target network (devnet, testnet, mainnet, local)',
|
|
23
|
+
required: false
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
flags: '-n, --network <url>',
|
|
27
|
+
description: 'Override network RPC URL',
|
|
28
|
+
required: false
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
flags: '-k, --keypair <file>',
|
|
32
|
+
description: 'Override keypair file path',
|
|
33
|
+
required: false
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
flags: '--program-id <pubkey>',
|
|
37
|
+
description: 'Specify the Five VM program ID to be the owner of the new script account',
|
|
38
|
+
required: false
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
flags: '--vm-state-account <pubkey>',
|
|
42
|
+
description: 'Specify an existing VM State Account (skips creation)',
|
|
43
|
+
required: false
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
flags: '--max-data-size <size>',
|
|
47
|
+
description: 'Maximum data size for program account (bytes)',
|
|
48
|
+
defaultValue: 1000000
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
flags: '--compute-budget <units>',
|
|
52
|
+
description: 'Compute budget for deployment transaction',
|
|
53
|
+
defaultValue: 1400000
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
flags: '--skip-verification',
|
|
57
|
+
description: 'Skip deployment verification',
|
|
58
|
+
defaultValue: false
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
flags: '--dry-run',
|
|
62
|
+
description: 'Simulate deployment without executing',
|
|
63
|
+
defaultValue: false
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
flags: '--format <format>',
|
|
67
|
+
description: 'Output format',
|
|
68
|
+
choices: ['text', 'json'],
|
|
69
|
+
defaultValue: 'text'
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
flags: '--debug',
|
|
73
|
+
description: 'Enable debug output',
|
|
74
|
+
defaultValue: false
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
flags: '--chunk-size <bytes>',
|
|
78
|
+
description: 'Chunk size for large program deployment (default: 750)',
|
|
79
|
+
defaultValue: 750
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
flags: '--progress',
|
|
83
|
+
description: 'Show progress for large program deployments',
|
|
84
|
+
defaultValue: false
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
flags: '--force-chunked',
|
|
88
|
+
description: 'Force chunked deployment even for small programs',
|
|
89
|
+
defaultValue: false
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
flags: '--optimized',
|
|
93
|
+
description: 'Use optimized deployment instructions (50-70% fewer transactions)',
|
|
94
|
+
defaultValue: false
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
flags: '--project <path>',
|
|
98
|
+
description: 'Project directory or five.toml path',
|
|
99
|
+
required: false
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
flags: '--namespace <scoped>',
|
|
103
|
+
description: 'Bind deployed script to scoped namespace (e.g. @5ive-tech/program)',
|
|
104
|
+
required: false
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
flags: '--namespace-manager <script>',
|
|
108
|
+
description: 'Namespace manager script account (overrides project/env/lockfile)',
|
|
109
|
+
required: false
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
arguments: [
|
|
113
|
+
{
|
|
114
|
+
name: 'bytecode',
|
|
115
|
+
description: 'Five VM artifact file (.five or .bin)',
|
|
116
|
+
required: true
|
|
117
|
+
}
|
|
118
|
+
],
|
|
119
|
+
examples: [
|
|
120
|
+
{
|
|
121
|
+
command: 'five deploy program.five',
|
|
122
|
+
description: 'Deploy to configured target (uses config defaults)'
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
command: 'five deploy program.five --target mainnet',
|
|
126
|
+
description: 'Deploy to mainnet (overrides config)'
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
command: 'five deploy program.five --keypair deployer.json --target devnet',
|
|
130
|
+
description: 'Deploy to devnet with specific keypair'
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
command: 'five deploy program.five --dry-run --format json',
|
|
134
|
+
description: 'Simulate deployment with JSON output'
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
command: 'five deploy large-program.bin --optimized --progress',
|
|
138
|
+
description: 'Deploy large program with optimized instructions (50-70% fewer transactions)'
|
|
139
|
+
}
|
|
140
|
+
],
|
|
141
|
+
handler: async (args, options, context) => {
|
|
142
|
+
const { logger } = context;
|
|
143
|
+
if (context.options.debug) {
|
|
144
|
+
logger.debug(`deploy args: ${JSON.stringify(args)}`);
|
|
145
|
+
logger.debug(`deploy options: ${JSON.stringify(options)}`);
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
const projectContext = await loadProjectConfig(options.project, process.cwd());
|
|
149
|
+
const manifest = projectContext ? await loadBuildManifest(projectContext.rootDir) : null;
|
|
150
|
+
// Apply project defaults to options if not provided
|
|
151
|
+
if (!options.target && projectContext?.config.cluster) {
|
|
152
|
+
options.target = projectContext.config.cluster;
|
|
153
|
+
}
|
|
154
|
+
if (!options.network && projectContext?.config.rpcUrl) {
|
|
155
|
+
options.network = projectContext.config.rpcUrl;
|
|
156
|
+
}
|
|
157
|
+
if (!options.keypair && projectContext?.config.keypairPath) {
|
|
158
|
+
options.keypair = projectContext.config.keypairPath;
|
|
159
|
+
}
|
|
160
|
+
if (context.options.verbose) {
|
|
161
|
+
logger.info('Applying configuration overrides...');
|
|
162
|
+
}
|
|
163
|
+
// Apply config with CLI overrides
|
|
164
|
+
const configManager = ConfigManager.getInstance();
|
|
165
|
+
const overrides = {
|
|
166
|
+
target: options.target,
|
|
167
|
+
network: options.network,
|
|
168
|
+
keypair: options.keypair
|
|
169
|
+
};
|
|
170
|
+
if (context.options.debug) {
|
|
171
|
+
logger.debug(`overrides: ${JSON.stringify(overrides)}`);
|
|
172
|
+
}
|
|
173
|
+
const config = await configManager.applyOverrides(overrides);
|
|
174
|
+
// Resolve program ID AFTER target override is applied, using the correct target
|
|
175
|
+
// Precedence: CLI flag → project config → config file (per-target) → env var
|
|
176
|
+
if (!options.programId) {
|
|
177
|
+
const configuredProgramId = await configManager.getProgramId(config.target);
|
|
178
|
+
options.programId = projectContext?.config.programId || configuredProgramId || process.env.FIVE_PROGRAM_ID;
|
|
179
|
+
}
|
|
180
|
+
if (context.options.debug) {
|
|
181
|
+
logger.debug(`config: ${JSON.stringify(config, null, 2)}`);
|
|
182
|
+
}
|
|
183
|
+
// Show target context prefix
|
|
184
|
+
const targetPrefix = ConfigManager.getTargetPrefix(config.target);
|
|
185
|
+
if (context.options.verbose) {
|
|
186
|
+
logger.info(`${targetPrefix} Deploying Five VM bytecode`);
|
|
187
|
+
}
|
|
188
|
+
// Show config details if enabled
|
|
189
|
+
if (config.showConfig && context.options.verbose) {
|
|
190
|
+
logger.info(`Target: ${config.target}`);
|
|
191
|
+
logger.info(`Network: ${config.networks[config.target].rpcUrl}`);
|
|
192
|
+
logger.info(`Keypair: ${config.keypairPath}`);
|
|
193
|
+
}
|
|
194
|
+
// Load bytecode using centralized file manager
|
|
195
|
+
let bytecodeFile = args[0] || manifest?.artifact_path;
|
|
196
|
+
if (bytecodeFile && projectContext && !isAbsolute(bytecodeFile)) {
|
|
197
|
+
bytecodeFile = join(projectContext.rootDir, bytecodeFile);
|
|
198
|
+
}
|
|
199
|
+
if (!bytecodeFile) {
|
|
200
|
+
throw new Error('Bytecode file is required (pass a path or compile to generate .five/.bin and manifest)');
|
|
201
|
+
}
|
|
202
|
+
if (context.options.verbose) {
|
|
203
|
+
logger.info(`Loading bytecode: ${bytecodeFile}`);
|
|
204
|
+
}
|
|
205
|
+
const spinner = isTTY() ? ora('Loading bytecode...').start() : null;
|
|
206
|
+
const fileManager = FiveFileManager.getInstance();
|
|
207
|
+
const loadedFile = await fileManager.loadFile(bytecodeFile, {
|
|
208
|
+
validateFormat: true
|
|
209
|
+
});
|
|
210
|
+
if (spinner) {
|
|
211
|
+
spinner.succeed(`Loaded ${loadedFile.format.toUpperCase()} (${loadedFile.bytecode.length} bytes)`);
|
|
212
|
+
}
|
|
213
|
+
if (context.options.verbose) {
|
|
214
|
+
logger.info(`Loaded ${loadedFile.bytecode.length} bytes`);
|
|
215
|
+
}
|
|
216
|
+
// Show additional info for .five files
|
|
217
|
+
if (loadedFile.abi) {
|
|
218
|
+
const functionCount = Object.keys(loadedFile.abi.functions || {}).length;
|
|
219
|
+
if (context.options.verbose) {
|
|
220
|
+
logger.info(`ABI functions: ${functionCount}`);
|
|
221
|
+
}
|
|
222
|
+
if (functionCount > 0) {
|
|
223
|
+
const functionNames = Object.keys(loadedFile.abi.functions).join(', ');
|
|
224
|
+
if (context.options.debug) {
|
|
225
|
+
logger.debug(`ABI functions: ${functionNames}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// Validate bytecode using Five SDK
|
|
230
|
+
if (context.options.verbose) {
|
|
231
|
+
logger.info('Validating bytecode...');
|
|
232
|
+
}
|
|
233
|
+
if (spinner) {
|
|
234
|
+
spinner.start('Validating bytecode...');
|
|
235
|
+
}
|
|
236
|
+
const validation = await FiveSDK.validateBytecode(loadedFile.bytecode, {
|
|
237
|
+
debug: options.debug || false
|
|
238
|
+
});
|
|
239
|
+
if (context.options.debug) {
|
|
240
|
+
logger.debug(`validation: ${JSON.stringify(validation)}`);
|
|
241
|
+
}
|
|
242
|
+
if (!validation.valid) {
|
|
243
|
+
if (spinner) {
|
|
244
|
+
spinner.fail('Bytecode validation failed');
|
|
245
|
+
}
|
|
246
|
+
logger.error(`Validation failed: ${validation.errors?.join(', ')}`);
|
|
247
|
+
throw new Error(`Invalid bytecode: ${validation.errors?.join(', ')}`);
|
|
248
|
+
}
|
|
249
|
+
if (spinner) {
|
|
250
|
+
spinner.succeed('Bytecode validation passed');
|
|
251
|
+
}
|
|
252
|
+
if (context.options.verbose) {
|
|
253
|
+
logger.info('Bytecode validation passed');
|
|
254
|
+
}
|
|
255
|
+
// Validate bytecode size
|
|
256
|
+
if (context.options.debug) {
|
|
257
|
+
logger.debug(`bytecode size: ${loadedFile.bytecode.length} bytes (max: ${options.maxDataSize})`);
|
|
258
|
+
}
|
|
259
|
+
if (loadedFile.bytecode.length > options.maxDataSize) {
|
|
260
|
+
throw new Error(`Bytecode too large: ${loadedFile.bytecode.length} bytes (max: ${options.maxDataSize})`);
|
|
261
|
+
}
|
|
262
|
+
// Setup deployment options
|
|
263
|
+
if (context.options.verbose) {
|
|
264
|
+
logger.info('Preparing deployment options...');
|
|
265
|
+
}
|
|
266
|
+
const deploymentOptions = await setupDeploymentOptions(loadedFile.bytecode, loadedFile.abi, options, config, logger);
|
|
267
|
+
if (context.options.debug) {
|
|
268
|
+
logger.debug(`deployment options: ${JSON.stringify(deploymentOptions, null, 2)}`);
|
|
269
|
+
}
|
|
270
|
+
// Validate program ID for on-chain deployment
|
|
271
|
+
let resolvedProgramId;
|
|
272
|
+
if (config.target !== 'wasm') {
|
|
273
|
+
try {
|
|
274
|
+
resolvedProgramId = ProgramIdResolver.resolve(options.programId);
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
throw new Error(`Program ID required for deployment to ${config.target}. ` +
|
|
278
|
+
`Provide via: --program-id <pubkey>, five.toml programId, ` +
|
|
279
|
+
`or: five config set --program-id <pubkey>`);
|
|
280
|
+
}
|
|
281
|
+
options.programId = resolvedProgramId;
|
|
282
|
+
}
|
|
283
|
+
// Fetch current fees from VM state to determine if extra accounts are needed
|
|
284
|
+
let fees;
|
|
285
|
+
try {
|
|
286
|
+
const rpcUrl = config.networks[config.target].rpcUrl;
|
|
287
|
+
const connection = new Connection(rpcUrl, 'confirmed');
|
|
288
|
+
fees = await FiveSDK.getFees(connection, options.programId);
|
|
289
|
+
if (fees.deployFeeBps > 0 || fees.executeFeeBps > 0) {
|
|
290
|
+
if (context.options.verbose || options.debug) {
|
|
291
|
+
console.log('\n' + section('VM Fees'));
|
|
292
|
+
if (fees.deployFeeBps > 0) {
|
|
293
|
+
console.log(keyValue('Deployment Fee', `${(fees.deployFeeBps / 100).toFixed(2)}%`));
|
|
294
|
+
}
|
|
295
|
+
if (fees.executeFeeBps > 0) {
|
|
296
|
+
console.log(keyValue('Execution Fee', `${(fees.executeFeeBps / 100).toFixed(2)}%`));
|
|
297
|
+
}
|
|
298
|
+
if (fees.adminAccount) {
|
|
299
|
+
console.log(keyValue('Admin Account', fees.adminAccount));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// Attach admin account to options for use in deployment
|
|
303
|
+
options.adminAccount = fees.adminAccount;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
catch (e) {
|
|
307
|
+
if (context.options.debug) {
|
|
308
|
+
logger.debug(`Could not fetch VM fees: ${e instanceof Error ? e.message : String(e)}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// Execute deployment
|
|
312
|
+
let result;
|
|
313
|
+
const namespaceManagerScript = options.namespace && projectContext
|
|
314
|
+
? await resolveNamespaceManagerScript(projectContext.rootDir, projectContext.config.namespaceManager, options.namespaceManager)
|
|
315
|
+
: undefined;
|
|
316
|
+
if (options.dryRun) {
|
|
317
|
+
if (context.options.verbose) {
|
|
318
|
+
logger.info('Simulating deployment...');
|
|
319
|
+
}
|
|
320
|
+
result = await simulateDeployment(deploymentOptions, options, context);
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
if (options.namespace && projectContext && !namespaceManagerScript) {
|
|
324
|
+
await validateNamespaceOwnership(projectContext.rootDir, options.namespace, config.keypairPath);
|
|
325
|
+
}
|
|
326
|
+
if (context.options.verbose) {
|
|
327
|
+
logger.info('Deploying to network...');
|
|
328
|
+
}
|
|
329
|
+
if (context.options.debug) {
|
|
330
|
+
logger.debug(`deployment options: ${JSON.stringify(deploymentOptions)}`);
|
|
331
|
+
logger.debug(`config: ${JSON.stringify(config)}`);
|
|
332
|
+
}
|
|
333
|
+
// Ensure admin account is passed into deployment options
|
|
334
|
+
const execDeploymentOptions = {
|
|
335
|
+
...deploymentOptions,
|
|
336
|
+
adminAccount: options.adminAccount
|
|
337
|
+
};
|
|
338
|
+
result = await executeDeployment(execDeploymentOptions, options, context, config);
|
|
339
|
+
}
|
|
340
|
+
// Display results
|
|
341
|
+
if (context.options.verbose) {
|
|
342
|
+
logger.info('Deployment completed');
|
|
343
|
+
}
|
|
344
|
+
if (result.success && result.programId && projectContext) {
|
|
345
|
+
try {
|
|
346
|
+
await updateLockfileExports(projectContext.rootDir, projectContext.config.name, result.programId, loadedFile.bytecode, deploymentOptions.exportMetadata);
|
|
347
|
+
}
|
|
348
|
+
catch (e) {
|
|
349
|
+
if (context.options.debug) {
|
|
350
|
+
logger.debug(`lockfile export cache update failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (options.namespace) {
|
|
354
|
+
if (namespaceManagerScript && !options.dryRun) {
|
|
355
|
+
const rpcUrl = config.networks[config.target].rpcUrl;
|
|
356
|
+
const connection = new Connection(rpcUrl, 'confirmed');
|
|
357
|
+
const signerKeypair = await loadKeypair(config.keypairPath, logger);
|
|
358
|
+
const bindResult = await FiveSDK.bindNamespaceOnChain(options.namespace, result.programId, {
|
|
359
|
+
managerScriptAccount: namespaceManagerScript,
|
|
360
|
+
connection,
|
|
361
|
+
signerKeypair,
|
|
362
|
+
fiveVMProgramId: options.programId || deploymentOptions.fiveVMProgramId,
|
|
363
|
+
debug: options.debug || context.options.debug || false,
|
|
364
|
+
});
|
|
365
|
+
if (context.options.verbose) {
|
|
366
|
+
logger.info(`Namespace bound on-chain via ${namespaceManagerScript}`);
|
|
367
|
+
if (bindResult.transactionId) {
|
|
368
|
+
logger.info(`Namespace bind tx: ${bindResult.transactionId}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
try {
|
|
373
|
+
await updateLockfileNamespace(projectContext.rootDir, options.namespace, result.programId);
|
|
374
|
+
if (namespaceManagerScript) {
|
|
375
|
+
await updateLockfileNamespaceManager(projectContext.rootDir, namespaceManagerScript);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
catch (e) {
|
|
379
|
+
if (context.options.debug) {
|
|
380
|
+
logger.debug(`lockfile namespace cache update failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
displayDeploymentResult(result, options, logger);
|
|
386
|
+
if (!result.success) {
|
|
387
|
+
logger.error('Deployment failed');
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
if (context.options.verbose) {
|
|
391
|
+
logger.info('Deploy command completed successfully');
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
logger.error('Deploy failed:', error);
|
|
396
|
+
throw error;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
/**
|
|
401
|
+
* Setup deployment options from CLI arguments
|
|
402
|
+
*/
|
|
403
|
+
async function setupDeploymentOptions(bytecode, abi, options, config, logger) {
|
|
404
|
+
const deploymentOptions = {
|
|
405
|
+
bytecode,
|
|
406
|
+
network: config.target,
|
|
407
|
+
maxDataSize: parseInt(options.maxDataSize),
|
|
408
|
+
computeBudget: parseInt(options.computeBudget),
|
|
409
|
+
fiveVMProgramId: options.programId, // Pass the programId
|
|
410
|
+
vmStateAccount: options.vmStateAccount,
|
|
411
|
+
exportMetadata: buildExportMetadataFromAbi(abi),
|
|
412
|
+
namespace: options.namespace,
|
|
413
|
+
namespaceManager: options.namespaceManager,
|
|
414
|
+
};
|
|
415
|
+
return deploymentOptions;
|
|
416
|
+
}
|
|
417
|
+
function canonicalizeNamespace(namespace) {
|
|
418
|
+
const trimmed = namespace.trim();
|
|
419
|
+
const symbol = trimmed[0];
|
|
420
|
+
const allowed = new Set(['!', '@', '#', '$', '%']);
|
|
421
|
+
if (!allowed.has(symbol)) {
|
|
422
|
+
throw new Error('namespace symbol must be one of ! @ # $ %');
|
|
423
|
+
}
|
|
424
|
+
const parts = trimmed.slice(1).split('/');
|
|
425
|
+
if (parts.length !== 2) {
|
|
426
|
+
throw new Error('namespace must be in format @domain/subprogram');
|
|
427
|
+
}
|
|
428
|
+
const normalize = (s) => s.toLowerCase();
|
|
429
|
+
const domain = normalize(parts[0]);
|
|
430
|
+
const subprogram = normalize(parts[1]);
|
|
431
|
+
const valid = (s) => /^[a-z0-9-]+$/.test(s) && s.length > 0;
|
|
432
|
+
if (!valid(domain) || !valid(subprogram)) {
|
|
433
|
+
throw new Error('namespace domain/subprogram must be lowercase alphanumeric + hyphen');
|
|
434
|
+
}
|
|
435
|
+
return { symbol, domain, subprogram, canonical: `${symbol}${domain}/${subprogram}` };
|
|
436
|
+
}
|
|
437
|
+
async function validateNamespaceOwnership(rootDir, namespace, keypairPath) {
|
|
438
|
+
const parsed = canonicalizeNamespace(namespace);
|
|
439
|
+
const lockPath = join(rootDir, 'five.lock');
|
|
440
|
+
let lockDoc = {};
|
|
441
|
+
try {
|
|
442
|
+
const content = await readFile(lockPath, 'utf8');
|
|
443
|
+
lockDoc = parseToml(content);
|
|
444
|
+
}
|
|
445
|
+
catch {
|
|
446
|
+
throw new Error(`namespace ownership check failed: missing ${lockPath}`);
|
|
447
|
+
}
|
|
448
|
+
const tlds = Array.isArray(lockDoc.namespace_tlds) ? lockDoc.namespace_tlds : [];
|
|
449
|
+
const tld = tlds.find((entry) => entry?.symbol === parsed.symbol && entry?.domain === parsed.domain);
|
|
450
|
+
if (!tld) {
|
|
451
|
+
throw new Error(`namespace ${parsed.symbol}${parsed.domain} is not registered in local lockfile`);
|
|
452
|
+
}
|
|
453
|
+
const expanded = keypairPath.startsWith('~/')
|
|
454
|
+
? keypairPath.replace('~', process.env.HOME || '')
|
|
455
|
+
: keypairPath;
|
|
456
|
+
const secret = JSON.parse(await readFile(expanded, 'utf8'));
|
|
457
|
+
const owner = Keypair.fromSecretKey(Uint8Array.from(secret)).publicKey.toBase58();
|
|
458
|
+
if (tld.owner !== owner) {
|
|
459
|
+
throw new Error(`namespace ownership mismatch: owner is ${tld.owner}, deployer is ${owner}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
export function buildExportMetadataFromAbi(abi) {
|
|
463
|
+
const methods = [];
|
|
464
|
+
const interfaces = [];
|
|
465
|
+
if (!abi) {
|
|
466
|
+
return { methods, interfaces };
|
|
467
|
+
}
|
|
468
|
+
if (Array.isArray(abi.functions)) {
|
|
469
|
+
for (const fn of abi.functions) {
|
|
470
|
+
if (!fn || typeof fn.name !== 'string')
|
|
471
|
+
continue;
|
|
472
|
+
const isPublic = fn.is_public === true || fn.visibility === 'public';
|
|
473
|
+
if (isPublic)
|
|
474
|
+
methods.push(fn.name);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
else if (abi.functions && typeof abi.functions === 'object') {
|
|
478
|
+
for (const name of Object.keys(abi.functions)) {
|
|
479
|
+
methods.push(name);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return { methods, interfaces };
|
|
483
|
+
}
|
|
484
|
+
export async function updateLockfileExports(rootDir, packageName, address, bytecode, exportMetadata) {
|
|
485
|
+
const lockPath = join(rootDir, 'five.lock');
|
|
486
|
+
let lockDoc = { version: 1, packages: [] };
|
|
487
|
+
try {
|
|
488
|
+
const content = await readFile(lockPath, 'utf8');
|
|
489
|
+
lockDoc = parseToml(content);
|
|
490
|
+
}
|
|
491
|
+
catch {
|
|
492
|
+
// No lockfile yet; create one.
|
|
493
|
+
}
|
|
494
|
+
if (!Array.isArray(lockDoc.packages)) {
|
|
495
|
+
lockDoc.packages = [];
|
|
496
|
+
}
|
|
497
|
+
const exportsPayload = {
|
|
498
|
+
methods: exportMetadata?.methods || [],
|
|
499
|
+
interfaces: (exportMetadata?.interfaces || []).map((iface) => ({
|
|
500
|
+
name: iface.name,
|
|
501
|
+
method_map: iface.methodMap || {},
|
|
502
|
+
})),
|
|
503
|
+
};
|
|
504
|
+
const entry = {
|
|
505
|
+
name: packageName,
|
|
506
|
+
version: '0.0.0',
|
|
507
|
+
address,
|
|
508
|
+
bytecode_hash: computeHash(bytecode),
|
|
509
|
+
deployed_at: new Date().toISOString(),
|
|
510
|
+
exports: exportsPayload,
|
|
511
|
+
};
|
|
512
|
+
const existingIndex = lockDoc.packages.findIndex((p) => p && (p.name === packageName || p.address === address));
|
|
513
|
+
if (existingIndex >= 0) {
|
|
514
|
+
lockDoc.packages[existingIndex] = {
|
|
515
|
+
...lockDoc.packages[existingIndex],
|
|
516
|
+
...entry,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
lockDoc.packages.push(entry);
|
|
521
|
+
}
|
|
522
|
+
await writeFile(lockPath, stringifyToml(lockDoc), 'utf8');
|
|
523
|
+
}
|
|
524
|
+
export async function updateLockfileNamespace(rootDir, namespace, address) {
|
|
525
|
+
const lockPath = join(rootDir, 'five.lock');
|
|
526
|
+
let lockDoc = { version: 1, packages: [], namespaces: [] };
|
|
527
|
+
try {
|
|
528
|
+
const content = await readFile(lockPath, 'utf8');
|
|
529
|
+
lockDoc = parseToml(content);
|
|
530
|
+
}
|
|
531
|
+
catch {
|
|
532
|
+
// No lockfile yet; create one.
|
|
533
|
+
}
|
|
534
|
+
if (!Array.isArray(lockDoc.namespaces)) {
|
|
535
|
+
lockDoc.namespaces = [];
|
|
536
|
+
}
|
|
537
|
+
const idx = lockDoc.namespaces.findIndex((item) => item && item.namespace === namespace);
|
|
538
|
+
const value = {
|
|
539
|
+
namespace,
|
|
540
|
+
address,
|
|
541
|
+
updated_at: new Date().toISOString(),
|
|
542
|
+
};
|
|
543
|
+
if (idx >= 0) {
|
|
544
|
+
lockDoc.namespaces[idx] = value;
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
lockDoc.namespaces.push(value);
|
|
548
|
+
}
|
|
549
|
+
await writeFile(lockPath, stringifyToml(lockDoc), 'utf8');
|
|
550
|
+
}
|
|
551
|
+
async function resolveNamespaceManagerScript(rootDir, projectManager, cliOverride) {
|
|
552
|
+
if (cliOverride)
|
|
553
|
+
return cliOverride;
|
|
554
|
+
if (projectManager)
|
|
555
|
+
return projectManager;
|
|
556
|
+
if (process.env.FIVE_NAMESPACE_MANAGER)
|
|
557
|
+
return process.env.FIVE_NAMESPACE_MANAGER;
|
|
558
|
+
const lockPath = join(rootDir, 'five.lock');
|
|
559
|
+
try {
|
|
560
|
+
const content = await readFile(lockPath, 'utf8');
|
|
561
|
+
const lockDoc = parseToml(content);
|
|
562
|
+
return lockDoc?.namespace_manager?.script_account;
|
|
563
|
+
}
|
|
564
|
+
catch {
|
|
565
|
+
return undefined;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
async function updateLockfileNamespaceManager(rootDir, scriptAccount) {
|
|
569
|
+
const lockPath = join(rootDir, 'five.lock');
|
|
570
|
+
let lockDoc = { version: 1, packages: [], namespaces: [] };
|
|
571
|
+
try {
|
|
572
|
+
const content = await readFile(lockPath, 'utf8');
|
|
573
|
+
lockDoc = parseToml(content);
|
|
574
|
+
}
|
|
575
|
+
catch {
|
|
576
|
+
// No lockfile yet; create one.
|
|
577
|
+
}
|
|
578
|
+
lockDoc.namespace_manager = {
|
|
579
|
+
script_account: scriptAccount,
|
|
580
|
+
updated_at: new Date().toISOString(),
|
|
581
|
+
};
|
|
582
|
+
await writeFile(lockPath, stringifyToml(lockDoc), 'utf8');
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Execute actual deployment to Solana network
|
|
586
|
+
*/
|
|
587
|
+
async function executeDeployment(deploymentOptions, options, context, config) {
|
|
588
|
+
const { logger } = context;
|
|
589
|
+
if (context.options.debug) {
|
|
590
|
+
logger.debug(`deployment options: ${JSON.stringify(deploymentOptions)}`);
|
|
591
|
+
logger.debug(`options: ${JSON.stringify(options)}`);
|
|
592
|
+
logger.debug(`config: ${JSON.stringify(config)}`);
|
|
593
|
+
}
|
|
594
|
+
const targetPrefix = ConfigManager.getTargetPrefix(config.target);
|
|
595
|
+
if (context.options.verbose) {
|
|
596
|
+
logger.info(`${targetPrefix} Deploying to ${deploymentOptions.network}...`);
|
|
597
|
+
}
|
|
598
|
+
try {
|
|
599
|
+
// Get network RPC endpoint from config
|
|
600
|
+
const rpcUrl = config.networks[config.target].rpcUrl;
|
|
601
|
+
if (context.options.debug) {
|
|
602
|
+
logger.debug(`rpc url: ${rpcUrl}`);
|
|
603
|
+
}
|
|
604
|
+
const connection = new Connection(rpcUrl, 'confirmed');
|
|
605
|
+
// Load deployer keypair from config
|
|
606
|
+
const deployerKeypair = await loadKeypair(config.keypairPath, logger);
|
|
607
|
+
if (context.options.debug) {
|
|
608
|
+
logger.debug(`deployer: ${deployerKeypair.publicKey.toString()}`);
|
|
609
|
+
}
|
|
610
|
+
// Deploy using Five SDK
|
|
611
|
+
const spinner = isTTY() ? ora('Deploying via Five SDK...').start() : null;
|
|
612
|
+
// Auto-detect large programs and use appropriate deployment method
|
|
613
|
+
const bytecodeArray = new Uint8Array(deploymentOptions.bytecode);
|
|
614
|
+
const isLargeProgram = bytecodeArray.length > 800 || options.forceChunked;
|
|
615
|
+
if (isLargeProgram) {
|
|
616
|
+
if (options.optimized) {
|
|
617
|
+
if (spinner) {
|
|
618
|
+
spinner.text = 'Deploying large program via optimized chunked deployment...';
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
if (spinner) {
|
|
623
|
+
spinner.text = 'Deploying large program via chunked deployment...';
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
let result;
|
|
628
|
+
if (isLargeProgram) {
|
|
629
|
+
if (options.optimized) {
|
|
630
|
+
// Use the new optimized deployment method
|
|
631
|
+
result = await FiveSDK.deployLargeProgramOptimizedToSolana(bytecodeArray, connection, deployerKeypair, {
|
|
632
|
+
debug: options.debug || false,
|
|
633
|
+
network: deploymentOptions.network,
|
|
634
|
+
fiveVMProgramId: deploymentOptions.fiveVMProgramId,
|
|
635
|
+
// vmStateAccount: deploymentOptions.vmStateAccount, // Optimized method does not support this yet
|
|
636
|
+
maxRetries: 3,
|
|
637
|
+
chunkSize: options.chunkSize || 950, // Higher default for optimized method
|
|
638
|
+
progressCallback: options.progress ? (transaction, total) => {
|
|
639
|
+
if (spinner) {
|
|
640
|
+
spinner.text = `Optimized deployment: transaction ${transaction}/${total}...`;
|
|
641
|
+
}
|
|
642
|
+
} : undefined
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
// Use traditional large program deployment
|
|
647
|
+
result = await FiveSDK.deployLargeProgramToSolana(bytecodeArray, connection, deployerKeypair, {
|
|
648
|
+
debug: options.debug || false,
|
|
649
|
+
network: deploymentOptions.network,
|
|
650
|
+
fiveVMProgramId: deploymentOptions.fiveVMProgramId,
|
|
651
|
+
vmStateAccount: deploymentOptions.vmStateAccount,
|
|
652
|
+
maxRetries: 3,
|
|
653
|
+
chunkSize: options.chunkSize || 750,
|
|
654
|
+
progressCallback: options.progress ? (chunk, total) => {
|
|
655
|
+
if (spinner) {
|
|
656
|
+
spinner.text = `Deploying chunk ${chunk}/${total}...`;
|
|
657
|
+
}
|
|
658
|
+
} : undefined
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
// Use regular deployment for small programs
|
|
664
|
+
result = await FiveSDK.deployToSolana(bytecodeArray, connection, deployerKeypair, {
|
|
665
|
+
debug: options.debug || false,
|
|
666
|
+
network: deploymentOptions.network,
|
|
667
|
+
computeBudget: deploymentOptions.computeBudget,
|
|
668
|
+
fiveVMProgramId: deploymentOptions.fiveVMProgramId, // Pass programId
|
|
669
|
+
exportMetadata: deploymentOptions.exportMetadata,
|
|
670
|
+
maxRetries: 3
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
if (result.success) {
|
|
674
|
+
if (isLargeProgram && 'chunksUsed' in result && result.chunksUsed) {
|
|
675
|
+
const largeResult = result; // Type assertion for large deployment result
|
|
676
|
+
if (options.optimized && 'optimizationSavings' in result) {
|
|
677
|
+
const optimizedResult = result;
|
|
678
|
+
const savingsPercent = Math.round(optimizedResult.optimizationSavings.transactionsSaved / (optimizedResult.optimizationSavings.transactionsSaved + optimizedResult.totalTransactions) * 100);
|
|
679
|
+
if (spinner) {
|
|
680
|
+
spinner.succeed(`Optimized deployment completed (${largeResult.chunksUsed} chunks, ${largeResult.totalTransactions} transactions, ${savingsPercent}% reduction)`);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
if (spinner) {
|
|
685
|
+
spinner.succeed(`Large program deployment completed (${largeResult.chunksUsed} chunks, ${largeResult.totalTransactions} transactions)`);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
if (spinner) {
|
|
691
|
+
spinner.succeed('Deployment completed');
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
if (spinner) {
|
|
697
|
+
spinner.fail('Deployment failed');
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
if (context.options.debug) {
|
|
701
|
+
logger.debug(`deploy result: ${JSON.stringify(result, null, 2)}`);
|
|
702
|
+
}
|
|
703
|
+
return result;
|
|
704
|
+
}
|
|
705
|
+
catch (error) {
|
|
706
|
+
logger.error('Deployment failed:', error);
|
|
707
|
+
const errorResult = {
|
|
708
|
+
success: false,
|
|
709
|
+
error: error instanceof Error ? error.message : 'Unknown deployment error',
|
|
710
|
+
logs: []
|
|
711
|
+
};
|
|
712
|
+
if (context.options.debug) {
|
|
713
|
+
logger.debug(`deploy error result: ${JSON.stringify(errorResult, null, 2)}`);
|
|
714
|
+
}
|
|
715
|
+
return errorResult;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Simulate deployment without executing
|
|
720
|
+
*/
|
|
721
|
+
async function simulateDeployment(deploymentOptions, options, context) {
|
|
722
|
+
const { logger } = context;
|
|
723
|
+
if (context.options.verbose) {
|
|
724
|
+
logger.info('Simulating deployment...');
|
|
725
|
+
}
|
|
726
|
+
// Simulate validation and cost calculation
|
|
727
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
728
|
+
const estimatedCost = Math.ceil(deploymentOptions.bytecode.length / 1000) * 1000000; // Rough estimate
|
|
729
|
+
return {
|
|
730
|
+
success: true,
|
|
731
|
+
programId: 'SIMULATED_PROGRAM_ID_' + Date.now(),
|
|
732
|
+
transactionId: 'SIMULATED_TX_' + Date.now(),
|
|
733
|
+
deploymentCost: estimatedCost,
|
|
734
|
+
logs: [
|
|
735
|
+
'Deployment simulation completed',
|
|
736
|
+
`Estimated cost: ${estimatedCost / 1e9} SOL`,
|
|
737
|
+
`Bytecode size: ${deploymentOptions.bytecode.length} bytes`,
|
|
738
|
+
`Target network: ${deploymentOptions.network}`
|
|
739
|
+
]
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Display deployment result in specified format
|
|
744
|
+
*/
|
|
745
|
+
function displayDeploymentResult(result, options, logger) {
|
|
746
|
+
if (options.format === 'json') {
|
|
747
|
+
console.log(JSON.stringify(result, null, 2));
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
console.log('\n' + section('Deployment'));
|
|
751
|
+
if (result.success) {
|
|
752
|
+
console.log(uiSuccess('Deployment succeeded'));
|
|
753
|
+
if (result.programId) {
|
|
754
|
+
console.log(keyValue('Program', result.programId));
|
|
755
|
+
}
|
|
756
|
+
if (result.transactionId) {
|
|
757
|
+
console.log(keyValue('Transaction', result.transactionId));
|
|
758
|
+
}
|
|
759
|
+
if (result.deploymentCost !== undefined) {
|
|
760
|
+
const costSOL = result.deploymentCost / 1e9;
|
|
761
|
+
console.log(keyValue('Cost', `${costSOL.toFixed(6)} SOL`));
|
|
762
|
+
}
|
|
763
|
+
if (result.logs && result.logs.length > 0 && options.verbose) {
|
|
764
|
+
console.log(section('Logs'));
|
|
765
|
+
result.logs.forEach((log) => {
|
|
766
|
+
console.log(` ${log}`);
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
console.log(uiError(result.error || 'Deployment failed'));
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Get RPC URL for network
|
|
776
|
+
*/
|
|
777
|
+
function getNetworkRpcUrl(network) {
|
|
778
|
+
const endpoints = {
|
|
779
|
+
'devnet': 'https://api.devnet.solana.com',
|
|
780
|
+
'testnet': 'https://api.testnet.solana.com',
|
|
781
|
+
'mainnet': 'https://api.mainnet-beta.solana.com',
|
|
782
|
+
'local': 'http://127.0.0.1:8899'
|
|
783
|
+
};
|
|
784
|
+
return endpoints[network] || endpoints['devnet'];
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Load Solana keypair from file
|
|
788
|
+
*/
|
|
789
|
+
async function loadKeypair(keypairPath, logger) {
|
|
790
|
+
// Expand tilde in path
|
|
791
|
+
const path = keypairPath.startsWith('~/')
|
|
792
|
+
? keypairPath.replace('~', process.env.HOME || '')
|
|
793
|
+
: keypairPath;
|
|
794
|
+
try {
|
|
795
|
+
const keypairData = await readFile(path, 'utf8');
|
|
796
|
+
const secretKey = Uint8Array.from(JSON.parse(keypairData));
|
|
797
|
+
const keypair = Keypair.fromSecretKey(secretKey);
|
|
798
|
+
logger.debug(`Loaded keypair from: ${path}`);
|
|
799
|
+
logger.debug(`Public key: ${keypair.publicKey.toString()}`);
|
|
800
|
+
return keypair;
|
|
801
|
+
}
|
|
802
|
+
catch (error) {
|
|
803
|
+
throw new Error(`Failed to load keypair from ${path}: ${error}`);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
//# sourceMappingURL=deploy.js.map
|