@sentio/cli 3.5.1-rc.3 → 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/lib/index.js +38369 -11012
- package/package.json +1 -1
- package/src/commands/processor.ts +1 -1
- package/src/commands/stop-processor.ts +63 -0
- package/src/commands/upload.ts +164 -1
- package/src/index.ts +2 -0
- package/src/network.ts +407 -0
package/package.json
CHANGED
|
@@ -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
|
+
}
|
package/src/commands/upload.ts
CHANGED
|
@@ -13,6 +13,21 @@ 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 {
|
|
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'
|
|
16
31
|
import { Auth, DefaultBatchUploader, FileType, IPFSBatchUploader, WalrusBatchUploader } from '../uploader.js'
|
|
17
32
|
export { type Auth } from '../uploader.js'
|
|
18
33
|
|
|
@@ -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
|
-
|
|
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,
|
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()
|
package/src/network.ts
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import { ethers } from 'ethers'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import fetch from 'node-fetch'
|
|
4
|
+
import readline from 'readline'
|
|
5
|
+
|
|
6
|
+
// --- Network Configuration ---
|
|
7
|
+
|
|
8
|
+
interface SentioNetworkConfig {
|
|
9
|
+
chainId: number
|
|
10
|
+
rpcUrl: string
|
|
11
|
+
explorerUrl: string
|
|
12
|
+
// AddressBook proxy that resolves all contract names to addresses
|
|
13
|
+
addressBookAddress: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const TESTNET_CONFIG: SentioNetworkConfig = {
|
|
17
|
+
chainId: 7892101,
|
|
18
|
+
rpcUrl: 'https://testnet.sentio.xyz',
|
|
19
|
+
explorerUrl: 'https://testnet-explorer.sentio.xyz',
|
|
20
|
+
addressBookAddress: '0x17d5aF5Ed9C2558B802bEfcCc5a94C36dE95BB0B'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getSentioNetworkConfig(network: string): SentioNetworkConfig {
|
|
24
|
+
if (network === 'testnet' || network === '7892101') {
|
|
25
|
+
return TESTNET_CONFIG
|
|
26
|
+
}
|
|
27
|
+
if (network === 'mainnet' || network === '789210') {
|
|
28
|
+
console.error(chalk.red('Sentio Network mainnet is not yet supported. Only testnet is available.'))
|
|
29
|
+
process.exit(1)
|
|
30
|
+
}
|
|
31
|
+
console.error(chalk.red(`Invalid sentio network: ${network}. Only "testnet" is supported.`))
|
|
32
|
+
process.exit(1)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// --- Contract ABIs ---
|
|
36
|
+
|
|
37
|
+
// AddressBook: resolves contract names to addresses
|
|
38
|
+
const ADDRESS_BOOK_ABI = [
|
|
39
|
+
'function getAddress(string name) view returns (address)',
|
|
40
|
+
'function getAddress(bytes32 id) view returns (address)'
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
// ProcessorRegistry: createProcessor, getProcessor, deleteProcessor
|
|
44
|
+
const PROCESSOR_REGISTRY_ABI = [
|
|
45
|
+
`function createProcessor(
|
|
46
|
+
string id,
|
|
47
|
+
tuple(uint8 sourceType, string ipfsCid) source,
|
|
48
|
+
tuple(string chainId, bool enableRpc, bool enableTrace)[] requireChains,
|
|
49
|
+
string sdkVersion
|
|
50
|
+
) returns (string)`,
|
|
51
|
+
'event ProcessorCreated(string indexed processorId)',
|
|
52
|
+
'function getAllocations(string processorId) view returns (tuple(uint256 indexerId, uint256 timestamp, bool indexerReady)[])',
|
|
53
|
+
`function getProcessor(string processorId) view returns (
|
|
54
|
+
tuple(
|
|
55
|
+
string id,
|
|
56
|
+
bool active,
|
|
57
|
+
uint256 createdAt,
|
|
58
|
+
address owner,
|
|
59
|
+
string sdkVersion,
|
|
60
|
+
tuple(string chainId, bool enableRpc, bool enableTrace)[] requireChains,
|
|
61
|
+
tuple(uint8 sourceType, string ipfsCid) source,
|
|
62
|
+
tuple(uint256 indexerId, uint256 timestamp, bool indexerReady)[] allocations
|
|
63
|
+
)
|
|
64
|
+
)`,
|
|
65
|
+
'function deleteProcessor(string processorId)'
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
// Controller: startProcessor / stopProcessor
|
|
69
|
+
const CONTROLLER_ABI = ['function startProcessor(string processorId)', 'function stopProcessor(string processorId)']
|
|
70
|
+
|
|
71
|
+
// ERC20: balanceOf + approve
|
|
72
|
+
const ERC20_ABI = [
|
|
73
|
+
'function balanceOf(address account) view returns (uint256)',
|
|
74
|
+
'function approve(address spender, uint256 amount) returns (bool)'
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
// Billing: deposit / depositTo / withdraw + balanceOf
|
|
78
|
+
const BILLING_ABI = ['function balances(address account) view returns (uint256)']
|
|
79
|
+
|
|
80
|
+
// --- Address Resolution via AddressBook ---
|
|
81
|
+
|
|
82
|
+
// Known address book key names for the contracts we need
|
|
83
|
+
const ADDRESS_BOOK_KEYS = {
|
|
84
|
+
processorRegistry: 'processor_registry',
|
|
85
|
+
controller: 'controller',
|
|
86
|
+
token: 'sentio_token',
|
|
87
|
+
billing: 'billing'
|
|
88
|
+
} as const
|
|
89
|
+
|
|
90
|
+
interface ResolvedAddresses {
|
|
91
|
+
addressBook: string
|
|
92
|
+
processorRegistry: string
|
|
93
|
+
controller: string
|
|
94
|
+
token: string
|
|
95
|
+
billing: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let cachedAddresses: ResolvedAddresses | undefined
|
|
99
|
+
|
|
100
|
+
export async function resolveNetworkAddresses(config: SentioNetworkConfig): Promise<ResolvedAddresses> {
|
|
101
|
+
if (cachedAddresses) return cachedAddresses
|
|
102
|
+
|
|
103
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl)
|
|
104
|
+
const addressBookAddr = config.addressBookAddress
|
|
105
|
+
|
|
106
|
+
console.log(chalk.gray(`AddressBook: ${addressBookAddr}`))
|
|
107
|
+
|
|
108
|
+
const addressBook = new ethers.Contract(addressBookAddr, ADDRESS_BOOK_ABI, provider)
|
|
109
|
+
|
|
110
|
+
const resolveAddress = async (name: string): Promise<string> => {
|
|
111
|
+
try {
|
|
112
|
+
// Try string-based lookup first
|
|
113
|
+
return await addressBook['getAddress(string)'](name)
|
|
114
|
+
} catch {
|
|
115
|
+
try {
|
|
116
|
+
// Fallback to bytes32-based lookup (keccak256 of name)
|
|
117
|
+
const id = ethers.id(name)
|
|
118
|
+
return await addressBook['getAddress(bytes32)'](id)
|
|
119
|
+
} catch (e) {
|
|
120
|
+
throw new Error(`Failed to resolve "${name}" from AddressBook (${addressBookAddr}): ${e.message}`)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const [processorRegistry, controller, token, billing] = await Promise.all([
|
|
126
|
+
resolveAddress(ADDRESS_BOOK_KEYS.processorRegistry),
|
|
127
|
+
resolveAddress(ADDRESS_BOOK_KEYS.controller),
|
|
128
|
+
resolveAddress(ADDRESS_BOOK_KEYS.token),
|
|
129
|
+
resolveAddress(ADDRESS_BOOK_KEYS.billing)
|
|
130
|
+
])
|
|
131
|
+
|
|
132
|
+
console.log(chalk.gray(`ProcessorRegistry: ${processorRegistry}`))
|
|
133
|
+
console.log(chalk.gray(`Controller: ${controller}`))
|
|
134
|
+
console.log(chalk.gray(`ST Token: ${token}`))
|
|
135
|
+
console.log(chalk.gray(`Billing: ${billing}`))
|
|
136
|
+
|
|
137
|
+
cachedAddresses = { addressBook: addressBookAddr, processorRegistry, controller, token, billing }
|
|
138
|
+
return cachedAddresses
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// --- Wallet Utilities ---
|
|
142
|
+
|
|
143
|
+
export function getWalletFromPrivateKey(privateKey: string): ethers.Wallet {
|
|
144
|
+
if (!privateKey.startsWith('0x')) {
|
|
145
|
+
privateKey = '0x' + privateKey
|
|
146
|
+
}
|
|
147
|
+
return new ethers.Wallet(privateKey)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function requirePrivateKey(): string {
|
|
151
|
+
const pk = process.env.PRIVATE_KEY
|
|
152
|
+
if (!pk) {
|
|
153
|
+
console.error(
|
|
154
|
+
chalk.red('Error: $PRIVATE_KEY environment variable is required for Sentio Network direct transactions.')
|
|
155
|
+
)
|
|
156
|
+
console.error(chalk.red('Set it with: export PRIVATE_KEY=0x...'))
|
|
157
|
+
process.exit(1)
|
|
158
|
+
}
|
|
159
|
+
return pk
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function checkSTBalance(
|
|
163
|
+
config: SentioNetworkConfig,
|
|
164
|
+
addresses: ResolvedAddresses,
|
|
165
|
+
walletAddress: string
|
|
166
|
+
): Promise<bigint> {
|
|
167
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl)
|
|
168
|
+
const token = new ethers.Contract(addresses.token, ERC20_ABI, provider)
|
|
169
|
+
const balance: bigint = await token.balanceOf(walletAddress)
|
|
170
|
+
return balance
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function checkBillingBalance(
|
|
174
|
+
config: SentioNetworkConfig,
|
|
175
|
+
addresses: ResolvedAddresses,
|
|
176
|
+
walletAddress: string
|
|
177
|
+
): Promise<bigint> {
|
|
178
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl)
|
|
179
|
+
const billing = new ethers.Contract(addresses.billing, BILLING_ABI, provider)
|
|
180
|
+
try {
|
|
181
|
+
const balance: bigint = await billing.balances(walletAddress)
|
|
182
|
+
return balance
|
|
183
|
+
} catch {
|
|
184
|
+
// Billing contract may revert for accounts that have never deposited
|
|
185
|
+
return 0n
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// --- IPFS Upload ---
|
|
190
|
+
|
|
191
|
+
export async function uploadToIPFS(fileBuffer: Buffer, ipfsUrl: string): Promise<string> {
|
|
192
|
+
const formData = new FormData()
|
|
193
|
+
const blob = new Blob([fileBuffer], { type: 'application/octet-stream' })
|
|
194
|
+
formData.append('file', blob, 'lib.js')
|
|
195
|
+
|
|
196
|
+
const response = await fetch(ipfsUrl, {
|
|
197
|
+
method: 'POST',
|
|
198
|
+
body: formData
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
if (!response.ok) {
|
|
202
|
+
const text = await response.text()
|
|
203
|
+
throw new Error(`IPFS upload failed: ${response.status} ${response.statusText} - ${text}`)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const result = (await response.json()) as { Hash: string }
|
|
207
|
+
return result.Hash
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// --- Contract Interactions ---
|
|
211
|
+
|
|
212
|
+
export interface OnChainProcessor {
|
|
213
|
+
id: string
|
|
214
|
+
active: boolean
|
|
215
|
+
createdAt: bigint
|
|
216
|
+
owner: string
|
|
217
|
+
sdkVersion: string
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Fetch processor info from on-chain registry. Returns null if the processor does not exist.
|
|
222
|
+
*/
|
|
223
|
+
export async function getProcessorOnChain(
|
|
224
|
+
config: SentioNetworkConfig,
|
|
225
|
+
addresses: ResolvedAddresses,
|
|
226
|
+
processorId: string
|
|
227
|
+
): Promise<OnChainProcessor | null> {
|
|
228
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl)
|
|
229
|
+
const registry = new ethers.Contract(addresses.processorRegistry, PROCESSOR_REGISTRY_ABI, provider)
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
const result = await registry.getProcessor(processorId)
|
|
233
|
+
// result is a tuple: (id, active, createdAt, owner, sdkVersion, requireChains, source, allocations)
|
|
234
|
+
const id: string = result[0]
|
|
235
|
+
if (!id || id === '') {
|
|
236
|
+
return null
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
id,
|
|
240
|
+
active: result[1],
|
|
241
|
+
createdAt: result[2],
|
|
242
|
+
owner: result[3],
|
|
243
|
+
sdkVersion: result[4]
|
|
244
|
+
}
|
|
245
|
+
} catch {
|
|
246
|
+
// Contract reverts if processor doesn't exist
|
|
247
|
+
return null
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export async function deleteProcessorOnChain(
|
|
252
|
+
config: SentioNetworkConfig,
|
|
253
|
+
addresses: ResolvedAddresses,
|
|
254
|
+
wallet: ethers.Wallet,
|
|
255
|
+
processorId: string
|
|
256
|
+
): Promise<string> {
|
|
257
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl)
|
|
258
|
+
const signer = wallet.connect(provider)
|
|
259
|
+
|
|
260
|
+
const registry = new ethers.Contract(addresses.processorRegistry, PROCESSOR_REGISTRY_ABI, signer)
|
|
261
|
+
|
|
262
|
+
console.log(chalk.blue('Deleting existing processor on-chain...'))
|
|
263
|
+
const tx = await registry.deleteProcessor(processorId)
|
|
264
|
+
console.log(chalk.gray(` Tx hash: ${tx.hash}`))
|
|
265
|
+
console.log(chalk.blue('Waiting for confirmation...'))
|
|
266
|
+
|
|
267
|
+
const receipt = await tx.wait()
|
|
268
|
+
if (receipt.status === 0) {
|
|
269
|
+
throw new Error(`deleteProcessor transaction failed. Tx: ${config.explorerUrl}/tx/${tx.hash}`)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
console.log(chalk.green(`Processor deleted. Tx: ${config.explorerUrl}/tx/${tx.hash}`))
|
|
273
|
+
return tx.hash
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export async function createProcessorOnChain(
|
|
277
|
+
config: SentioNetworkConfig,
|
|
278
|
+
addresses: ResolvedAddresses,
|
|
279
|
+
wallet: ethers.Wallet,
|
|
280
|
+
processorId: string,
|
|
281
|
+
ipfsCid: string,
|
|
282
|
+
requiredChainIds: string[],
|
|
283
|
+
sdkVersion: string
|
|
284
|
+
): Promise<string> {
|
|
285
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl)
|
|
286
|
+
const signer = wallet.connect(provider)
|
|
287
|
+
|
|
288
|
+
const registry = new ethers.Contract(addresses.processorRegistry, PROCESSOR_REGISTRY_ABI, signer)
|
|
289
|
+
|
|
290
|
+
const source = {
|
|
291
|
+
sourceType: 0, // IPFS
|
|
292
|
+
ipfsCid
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const requireChains = requiredChainIds.map((chainId) => ({
|
|
296
|
+
chainId,
|
|
297
|
+
enableRpc: true,
|
|
298
|
+
enableTrace: false
|
|
299
|
+
}))
|
|
300
|
+
|
|
301
|
+
console.log(chalk.blue('Creating processor on-chain...'))
|
|
302
|
+
console.log(chalk.gray(` Processor ID: ${processorId}`))
|
|
303
|
+
console.log(chalk.gray(` IPFS CID: ${ipfsCid}`))
|
|
304
|
+
console.log(chalk.gray(` Chains: ${requiredChainIds.join(', ')}`))
|
|
305
|
+
console.log(chalk.gray(` SDK Version: ${sdkVersion}`))
|
|
306
|
+
|
|
307
|
+
const tx = await registry.createProcessor(processorId, source, requireChains, sdkVersion)
|
|
308
|
+
console.log(chalk.gray(` Tx hash: ${tx.hash}`))
|
|
309
|
+
console.log(chalk.blue('Waiting for confirmation...'))
|
|
310
|
+
|
|
311
|
+
const receipt = await tx.wait()
|
|
312
|
+
if (receipt.status === 0) {
|
|
313
|
+
throw new Error(`createProcessor transaction failed. Tx: ${config.explorerUrl}/tx/${tx.hash}`)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
console.log(chalk.green(`Processor created. Tx: ${config.explorerUrl}/tx/${tx.hash}`))
|
|
317
|
+
return tx.hash
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export async function startProcessorOnChain(
|
|
321
|
+
config: SentioNetworkConfig,
|
|
322
|
+
addresses: ResolvedAddresses,
|
|
323
|
+
wallet: ethers.Wallet,
|
|
324
|
+
processorId: string
|
|
325
|
+
): Promise<string> {
|
|
326
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl)
|
|
327
|
+
const signer = wallet.connect(provider)
|
|
328
|
+
|
|
329
|
+
const controller = new ethers.Contract(addresses.controller, CONTROLLER_ABI, signer)
|
|
330
|
+
|
|
331
|
+
console.log(chalk.blue('Starting processor on-chain...'))
|
|
332
|
+
const tx = await controller.startProcessor(processorId)
|
|
333
|
+
console.log(chalk.gray(` Tx hash: ${tx.hash}`))
|
|
334
|
+
console.log(chalk.blue('Waiting for confirmation...'))
|
|
335
|
+
|
|
336
|
+
const receipt = await tx.wait()
|
|
337
|
+
if (receipt.status === 0) {
|
|
338
|
+
throw new Error(`startProcessor transaction failed. Tx: ${config.explorerUrl}/tx/${tx.hash}`)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
console.log(chalk.green(`Processor started. Tx: ${config.explorerUrl}/tx/${tx.hash}`))
|
|
342
|
+
return tx.hash
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export async function stopProcessorOnChain(
|
|
346
|
+
config: SentioNetworkConfig,
|
|
347
|
+
addresses: ResolvedAddresses,
|
|
348
|
+
wallet: ethers.Wallet,
|
|
349
|
+
processorId: string
|
|
350
|
+
): Promise<string> {
|
|
351
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl)
|
|
352
|
+
const signer = wallet.connect(provider)
|
|
353
|
+
|
|
354
|
+
const controller = new ethers.Contract(addresses.controller, CONTROLLER_ABI, signer)
|
|
355
|
+
|
|
356
|
+
console.log(chalk.blue('Stopping processor on-chain...'))
|
|
357
|
+
const tx = await controller.stopProcessor(processorId)
|
|
358
|
+
console.log(chalk.gray(` Tx hash: ${tx.hash}`))
|
|
359
|
+
console.log(chalk.blue('Waiting for confirmation...'))
|
|
360
|
+
|
|
361
|
+
const receipt = await tx.wait()
|
|
362
|
+
if (receipt.status === 0) {
|
|
363
|
+
throw new Error(`stopProcessor transaction failed. Tx: ${config.explorerUrl}/tx/${tx.hash}`)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
console.log(chalk.green(`Processor stopped. Tx: ${config.explorerUrl}/tx/${tx.hash}`))
|
|
367
|
+
return tx.hash
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// --- Confirmation Prompt ---
|
|
371
|
+
|
|
372
|
+
export async function confirmNoPlatformUpload(
|
|
373
|
+
walletAddress: string,
|
|
374
|
+
stBalance: bigint,
|
|
375
|
+
billingBalance: bigint,
|
|
376
|
+
addresses: ResolvedAddresses,
|
|
377
|
+
networkConfig: SentioNetworkConfig
|
|
378
|
+
): Promise<boolean> {
|
|
379
|
+
const formattedST = ethers.formatEther(stBalance)
|
|
380
|
+
const formattedBilling = ethers.formatEther(billingBalance)
|
|
381
|
+
console.log()
|
|
382
|
+
console.log(chalk.blue('=== Sentio Network Direct Upload (No Platform) ==='))
|
|
383
|
+
console.log(chalk.white(` Address: ${walletAddress}`))
|
|
384
|
+
console.log(chalk.white(` ST Balance: ${formattedST} ST`))
|
|
385
|
+
console.log(chalk.white(` Billing Balance: ${formattedBilling} ST`))
|
|
386
|
+
|
|
387
|
+
if (billingBalance === 0n) {
|
|
388
|
+
console.log()
|
|
389
|
+
console.log(
|
|
390
|
+
chalk.yellow(
|
|
391
|
+
' ⚠ Your Billing balance is 0. Indexing fees are charged from the Billing contract.\n' +
|
|
392
|
+
' You must deposit ST tokens into the Billing contract before your processor can run.\n' +
|
|
393
|
+
` Use: cast send ${addresses.token} "approve(address,uint256)" ${addresses.billing} <amount> --rpc-url ${networkConfig.rpcUrl} --private-key $PRIVATE_KEY\n` +
|
|
394
|
+
` cast send ${addresses.billing} "deposit(uint256)" <amount> --rpc-url ${networkConfig.rpcUrl} --private-key $PRIVATE_KEY`
|
|
395
|
+
)
|
|
396
|
+
)
|
|
397
|
+
}
|
|
398
|
+
console.log()
|
|
399
|
+
|
|
400
|
+
const rl = readline.createInterface({
|
|
401
|
+
input: process.stdin,
|
|
402
|
+
output: process.stdout
|
|
403
|
+
})
|
|
404
|
+
const answer: string = await new Promise((resolve) => rl.question('Continue with upload? (yes/no) ', resolve))
|
|
405
|
+
rl.close()
|
|
406
|
+
return ['y', 'yes'].includes(answer.toLowerCase())
|
|
407
|
+
}
|