@sentio/cli 3.5.1-rc.2 → 3.6.0-rc.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentio/cli",
3
- "version": "3.5.1-rc.2",
3
+ "version": "3.6.0-rc.1",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -337,7 +337,7 @@ async function runProcessorResume(processorId: string | undefined, options: Proc
337
337
  })
338
338
  }
339
339
 
340
- async function runProcessorStop(processorId: string | undefined, options: ProcessorOptions & { yes?: boolean }) {
340
+ export async function runProcessorStop(processorId: string | undefined, options: ProcessorOptions & { yes?: boolean }) {
341
341
  const resolvedProcessorId = await resolveAndConfirmProcessor('stop', processorId, options)
342
342
  if (!resolvedProcessorId) return
343
343
  const context = createApiContext(options)
@@ -0,0 +1,63 @@
1
+ import { Command } from '@commander-js/extra-typings'
2
+ import chalk from 'chalk'
3
+ import { handleCommandError } from '../api.js'
4
+ import { confirm } from './upload.js'
5
+ import { runProcessorStop } from './processor.js'
6
+ import {
7
+ getSentioNetworkConfig,
8
+ resolveNetworkAddresses,
9
+ getWalletFromPrivateKey,
10
+ requirePrivateKey,
11
+ stopProcessorOnChain
12
+ } from '../network.js'
13
+
14
+ export function createStopProcessorCommand() {
15
+ return new Command('stop')
16
+ .description('Stop a processor. Uses Sentio Network contract when --sentio-network is specified.')
17
+ .argument('<processorId>', 'ID of the processor to stop')
18
+ .option('--sentio-network <network>', 'Stop via Sentio Network contract (only "testnet" supported)')
19
+ .option('--host <host>', 'Override Sentio host')
20
+ .option('--api-key <key>', 'Use an explicit API key instead of saved credentials')
21
+ .option('--token <token>', 'Use an explicit bearer token instead of saved credentials')
22
+ .option('--project <owner/slug>', 'Sentio project in <owner>/<slug> format')
23
+ .option('--owner <owner>', 'Sentio project owner')
24
+ .option('--name <name>', 'Sentio project name')
25
+ .option('-y, --yes', 'Bypass confirmation')
26
+ .showHelpAfterError()
27
+ .action(async (processorId, options) => {
28
+ try {
29
+ if (options.sentioNetwork) {
30
+ await runStopProcessorOnChain(processorId, options)
31
+ } else {
32
+ await runProcessorStop(processorId, options)
33
+ }
34
+ } catch (error) {
35
+ handleCommandError(error)
36
+ }
37
+ })
38
+ }
39
+
40
+ async function runStopProcessorOnChain(processorId: string, options: { sentioNetwork?: string; yes?: boolean }) {
41
+ const network = options.sentioNetwork!
42
+ const networkConfig = getSentioNetworkConfig(network)
43
+
44
+ const privateKey = requirePrivateKey()
45
+ const wallet = getWalletFromPrivateKey(privateKey)
46
+
47
+ console.log(chalk.blue(`Wallet address: ${wallet.address}`))
48
+ console.log(chalk.blue('Resolving contract addresses from AddressBook...'))
49
+ const addresses = await resolveNetworkAddresses(networkConfig)
50
+
51
+ if (!options.yes) {
52
+ const confirmed = await confirm(`Stop processor "${processorId}" on Sentio Network ${network}?`)
53
+ if (!confirmed) {
54
+ console.log('Stop cancelled.')
55
+ return
56
+ }
57
+ }
58
+
59
+ await stopProcessorOnChain(networkConfig, addresses, wallet, processorId)
60
+
61
+ console.log()
62
+ console.log(chalk.green(`Processor "${processorId}" stopped on Sentio Network ${network}.`))
63
+ }
@@ -13,7 +13,22 @@ import readline from 'readline'
13
13
  import JSZip from 'jszip'
14
14
  import { UserInfo } from '../../../protos/lib/service/common/protos/common.js'
15
15
  import { CommandOptionsType } from './types.js'
16
- import { Auth, DefaultBatchUploader, IPFSBatchUploader, WalrusBatchUploader } from '../uploader.js'
16
+ import {
17
+ getSentioNetworkConfig,
18
+ resolveNetworkAddresses,
19
+ getWalletFromPrivateKey,
20
+ requirePrivateKey,
21
+ checkSTBalance,
22
+ checkBillingBalance,
23
+ uploadToIPFS,
24
+ createProcessorOnChain,
25
+ startProcessorOnChain,
26
+ stopProcessorOnChain,
27
+ confirmNoPlatformUpload,
28
+ getProcessorOnChain,
29
+ deleteProcessorOnChain
30
+ } from '../network.js'
31
+ import { Auth, DefaultBatchUploader, FileType, IPFSBatchUploader, WalrusBatchUploader } from '../uploader.js'
17
32
  export { type Auth } from '../uploader.js'
18
33
 
19
34
  function myParseInt(value: string, dummyPrevious: number): number {
@@ -56,16 +71,164 @@ export function createUploadCommand() {
56
71
  '--required-chain-id <chain_id...>',
57
72
  '(Optional) Specify chain IDs required for the Sentio network. This option is only available when --sentio-network is used. If omitted, all chain IDs from the project configuration (contracts or network overrides) will be used.'
58
73
  )
74
+ .option(
75
+ '--no-platform',
76
+ 'Upload processor directly to Sentio Network without platform support. Requires $PRIVATE_KEY env var. Only testnet is supported.'
77
+ )
78
+ .option(
79
+ '--ipfs-put-url <url>',
80
+ 'IPFS upload endpoint (used with --no-platform)',
81
+ 'https://api.sentio.xyz/v1/ipfs/add'
82
+ )
59
83
  .action(async (options, command) => {
60
84
  const processorConfig = loadProcessorConfig(options.path)
61
85
  overrideConfigWithOptions(processorConfig, options)
62
86
  if (options.path) {
63
87
  process.chdir(options.path)
64
88
  }
65
- await runUploadInternal(processorConfig, options, command)
89
+ if (options.platform === false) {
90
+ await runNoPlatformUpload(processorConfig, options)
91
+ } else {
92
+ await runUploadInternal(processorConfig, options, command)
93
+ }
66
94
  })
67
95
  }
68
96
 
97
+ async function runNoPlatformUpload(
98
+ processorConfig: YamlProjectConfig,
99
+ options: CommandOptionsType<typeof createUploadCommand>
100
+ ) {
101
+ // Determine network - default to testnet for --no-platform
102
+ const network = options.sentioNetwork || 'testnet'
103
+ const networkConfig = getSentioNetworkConfig(network)
104
+
105
+ // Step 1: Require PRIVATE_KEY
106
+ const privateKey = requirePrivateKey()
107
+ const wallet = getWalletFromPrivateKey(privateKey)
108
+ const walletAddress = wallet.address
109
+
110
+ // Step 2: Resolve contract addresses via AddressBook
111
+ console.log(chalk.blue('Resolving contract addresses from AddressBook...'))
112
+ const addresses = await resolveNetworkAddresses(networkConfig)
113
+
114
+ // Step 3: Check ST balance and Billing balance, then confirm
115
+ const [stBalance, billingBalance] = await Promise.all([
116
+ checkSTBalance(networkConfig, addresses, walletAddress),
117
+ checkBillingBalance(networkConfig, addresses, walletAddress)
118
+ ])
119
+ const confirmed = await confirmNoPlatformUpload(walletAddress, stBalance, billingBalance, addresses, networkConfig)
120
+ if (!confirmed) {
121
+ console.log('Upload cancelled.')
122
+ process.exit(0)
123
+ }
124
+
125
+ // Step 4: Build processor
126
+ if (!options.skipBuild) {
127
+ await buildProcessor(false, options)
128
+ }
129
+
130
+ const processorFile = path.join(process.cwd(), 'dist/lib.js')
131
+ if (!fs.existsSync(processorFile)) {
132
+ console.error(chalk.red('File not found:', processorFile, "- don't use --skip-build"))
133
+ process.exit(1)
134
+ }
135
+
136
+ const codeBuffer = fs.readFileSync(processorFile)
137
+ const stat = fs.statSync(processorFile)
138
+ console.log('Packed processor file size', Math.floor(stat.size / 1024) + 'K, last modified', stat.mtime)
139
+
140
+ // Step 5: Upload to IPFS
141
+ const ipfsPutUrl = options.ipfsPutUrl || 'https://api.sentio.xyz/v1/ipfs/add'
142
+ console.log(chalk.blue(`Uploading processor to IPFS (${ipfsPutUrl})...`))
143
+ const cid = await uploadToIPFS(codeBuffer, ipfsPutUrl)
144
+ console.log(chalk.green(`IPFS upload complete.`))
145
+ console.log('\t', chalk.blue('CID:'), cid)
146
+
147
+ // Step 6: Collect required chain IDs
148
+ let requiredChainIds = options.requiredChainId || processorConfig.requiredChainIds || []
149
+ if (requiredChainIds.length === 0) {
150
+ const chainIds = new Set<string>()
151
+ if (processorConfig.contracts) {
152
+ for (const contract of processorConfig.contracts) {
153
+ chainIds.add(String(contract.chain))
154
+ }
155
+ }
156
+ if (processorConfig.networkOverrides) {
157
+ for (const override of processorConfig.networkOverrides) {
158
+ chainIds.add(String(override.chain))
159
+ }
160
+ }
161
+ requiredChainIds = Array.from(chainIds)
162
+ }
163
+ if (requiredChainIds.length === 0) {
164
+ console.error(
165
+ chalk.red(
166
+ 'Error: No chain IDs found. Specify --required-chain-id or define contracts/networkOverrides in sentio.yaml.'
167
+ )
168
+ )
169
+ process.exit(1)
170
+ }
171
+
172
+ // Step 7: Derive processor ID from project name
173
+ let processorId = processorConfig.project.replace('/', '_')
174
+ const sdkVersion = getSdkVersion() || 'unknown'
175
+
176
+ // Step 7.5: Check if processor already exists on-chain
177
+ console.log(chalk.blue('Checking if processor already exists on-chain...'))
178
+ const existingProcessor = await getProcessorOnChain(networkConfig, addresses, processorId)
179
+
180
+ if (existingProcessor) {
181
+ const existingOwner = existingProcessor.owner.toLowerCase()
182
+ const currentWallet = wallet.address.toLowerCase()
183
+
184
+ if (existingOwner === currentWallet) {
185
+ // Same owner — prompt to replace
186
+ console.log(chalk.yellow(`Processor "${processorId}" already exists (owned by you).`))
187
+ const shouldReplace = await confirm(`Replace existing processor "${processorId}"?`)
188
+ if (!shouldReplace) {
189
+ console.log('Upload cancelled.')
190
+ process.exit(0)
191
+ }
192
+ // Stop and delete existing processor before re-creating
193
+ await stopProcessorOnChain(networkConfig, addresses, wallet, processorId)
194
+ await deleteProcessorOnChain(networkConfig, addresses, wallet, processorId)
195
+ } else {
196
+ // Different owner — prompt to rename
197
+ console.log(
198
+ chalk.yellow(
199
+ `Processor "${processorId}" already exists and is owned by ${existingProcessor.owner} (not your wallet ${wallet.address}).`
200
+ )
201
+ )
202
+ const randomSuffix = Math.random().toString(36).substring(2, 8)
203
+ const defaultNewId = `${processorId}-${randomSuffix}`
204
+
205
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
206
+ const newId: string = await new Promise((resolve) =>
207
+ rl.question(`Enter a new processor ID [${defaultNewId}]: `, (answer) => {
208
+ rl.close()
209
+ resolve(answer.trim() || defaultNewId)
210
+ })
211
+ )
212
+ processorId = newId
213
+ console.log(chalk.blue(`Using processor ID: ${processorId}`))
214
+ }
215
+ }
216
+
217
+ // Step 8: Create processor on-chain
218
+ await createProcessorOnChain(networkConfig, addresses, wallet, processorId, cid, requiredChainIds, sdkVersion)
219
+
220
+ // Step 9: Start processor on-chain
221
+ await startProcessorOnChain(networkConfig, addresses, wallet, processorId)
222
+
223
+ console.log()
224
+ console.log(chalk.green('=== Upload Complete ==='))
225
+ console.log('\t', chalk.blue('Processor ID:'), processorId)
226
+ console.log('\t', chalk.blue('IPFS CID:'), cid)
227
+ console.log('\t', chalk.blue('Network:'), network)
228
+ const codeHash = createHash('sha256').update(codeBuffer).digest('hex')
229
+ console.log('\t', chalk.blue('sha256:'), codeHash)
230
+ }
231
+
69
232
  function parseCheckpoints(
70
233
  checkpoints: string[] | undefined,
71
234
  continueFrom: number | undefined,
@@ -322,6 +485,15 @@ async function checkOrCreateProject(options: YamlProjectConfig, auth: Auth) {
322
485
  )
323
486
  process.exit(1)
324
487
  }
488
+ if (!options.sentioNetwork && project.sentioNetwork === true) {
489
+ console.error(
490
+ chalk.red(
491
+ `Project ${project?.slug} is a Sentio Network project. Please add the --sentio-network flag when uploading.\n` +
492
+ `Example: sentio upload --sentio-network testnet`
493
+ )
494
+ )
495
+ process.exit(1)
496
+ }
325
497
  return project?.id
326
498
  }
327
499
 
@@ -421,6 +593,26 @@ export async function uploadFile(
421
593
  ? new IPFSBatchUploader(config, auth)
422
594
  : new DefaultBatchUploader(config, auth)
423
595
 
596
+ // Initialize upload and handle confirmation
597
+ const fileTypes: Record<string, number> = {
598
+ source: FileType.SOURCE,
599
+ code: FileType.PROCESSOR
600
+ }
601
+ const initResponse = await uploader.initUpload(fileTypes)
602
+
603
+ if (initResponse.warning) {
604
+ console.log(chalk.yellow(initResponse.warning))
605
+ }
606
+
607
+ if (initResponse.replacing_version && !config.silentOverwrite) {
608
+ const confirmed = await confirm(
609
+ `This will replace processor version ${initResponse.replacing_version}. Continue?`
610
+ )
611
+ if (!confirmed) {
612
+ process.exit(0)
613
+ }
614
+ }
615
+
424
616
  // Handle variables update if needed
425
617
  if (config.variables && config.variables.length > 0) {
426
618
  const ret = await updateVariables(projectId, config, auth)
@@ -439,7 +631,8 @@ export async function uploadFile(
439
631
  config.debug || options.debug,
440
632
  continueFrom,
441
633
  config.networkOverrides,
442
- rollbackMap
634
+ rollbackMap,
635
+ initResponse
443
636
  )
444
637
 
445
638
  console.log(chalk.green('Upload success: '))
package/src/index.ts CHANGED
@@ -19,6 +19,7 @@ import { createPriceCommand } from './commands/price.js'
19
19
  import { createSimulationCommand } from './commands/simulation.js'
20
20
  import { createEndpointCommand } from './commands/endpoint.js'
21
21
  import { createDashboardCommand } from './commands/dashboard.js'
22
+ import { createStopProcessorCommand } from './commands/stop-processor.js'
22
23
  import { enableApiDebug } from './api.js'
23
24
  import { printVersions } from './utils.js'
24
25
 
@@ -52,5 +53,6 @@ program.addCommand(createPriceCommand())
52
53
  program.addCommand(createSimulationCommand())
53
54
  program.addCommand(createEndpointCommand())
54
55
  program.addCommand(createDashboardCommand())
56
+ program.addCommand(createStopProcessorCommand())
55
57
 
56
58
  program.parse()