@sentio/cli 3.6.3-rc.1 → 3.7.0-rc.2

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 CHANGED
@@ -139190,7 +139190,7 @@ var TESTNET_CONFIG = {
139190
139190
  chainId: 7892101,
139191
139191
  rpcUrl: "https://sentio-testnet.rpc.sentio.xyz",
139192
139192
  explorerUrl: "https://testnet-explorer.sentio.xyz",
139193
- addressBookAddress: "0x11cDDF46f16925aa630Af9D5158028E56309868f"
139193
+ addressBookAddress: "0x94579F0e7873097279B48d7b15043698c522e47c"
139194
139194
  };
139195
139195
  function getSentioNetworkConfig(network) {
139196
139196
  if (network === "testnet" || network === "7892101") {
@@ -139214,6 +139214,13 @@ var PROCESSOR_REGISTRY_ABI = [
139214
139214
  tuple(string chainId, bool enableRpc, bool enableTrace)[] requireChains,
139215
139215
  string sdkVersion
139216
139216
  ) returns (string)`,
139217
+ `function createProcessor(
139218
+ address owner,
139219
+ string id,
139220
+ tuple(uint8 sourceType, string ipfsCid) source,
139221
+ tuple(string chainId, bool enableRpc, bool enableTrace)[] requireChains,
139222
+ string sdkVersion
139223
+ ) returns (string)`,
139217
139224
  "event ProcessorCreated(string indexed processorId)",
139218
139225
  `function getProcessor(string processorId) view returns (
139219
139226
  tuple(
@@ -139235,6 +139242,7 @@ var CONTROLLER_ABI = [
139235
139242
  "function getAllocations(string processorId) view returns (tuple(uint256 indexerId, uint256 allocationTime, bool indexerReady, uint256 replicaIndex)[])"
139236
139243
  ];
139237
139244
  var DATABASES_ABI = ["function getProcessorDatabases(string processorId) view returns (string[])"];
139245
+ var PERMISSIONS_ABI = ["function isOperator(address account, address operator) view returns (bool)"];
139238
139246
  var ERC20_ABI = [
139239
139247
  "function balanceOf(address account) view returns (uint256)",
139240
139248
  "function approve(address spender, uint256 amount) returns (bool)"
@@ -139245,7 +139253,8 @@ var ADDRESS_BOOK_KEYS = {
139245
139253
  controller: "controller",
139246
139254
  token: "sentio_token",
139247
139255
  billing: "billing",
139248
- databases: "databases"
139256
+ databases: "databases",
139257
+ permissions: "permissions"
139249
139258
  };
139250
139259
  var cachedAddresses;
139251
139260
  async function resolveNetworkAddresses(config) {
@@ -139266,19 +139275,29 @@ async function resolveNetworkAddresses(config) {
139266
139275
  }
139267
139276
  }
139268
139277
  };
139269
- const [processorRegistry, controller, token, billing, databases] = await Promise.all([
139278
+ const [processorRegistry, controller, token, billing, databases, permissions] = await Promise.all([
139270
139279
  resolveAddress2(ADDRESS_BOOK_KEYS.processorRegistry),
139271
139280
  resolveAddress2(ADDRESS_BOOK_KEYS.controller),
139272
139281
  resolveAddress2(ADDRESS_BOOK_KEYS.token),
139273
139282
  resolveAddress2(ADDRESS_BOOK_KEYS.billing),
139274
- resolveAddress2(ADDRESS_BOOK_KEYS.databases)
139283
+ resolveAddress2(ADDRESS_BOOK_KEYS.databases),
139284
+ resolveAddress2(ADDRESS_BOOK_KEYS.permissions)
139275
139285
  ]);
139276
139286
  console.log(source_default.gray(`ProcessorRegistry: ${processorRegistry}`));
139277
139287
  console.log(source_default.gray(`Controller: ${controller}`));
139278
139288
  console.log(source_default.gray(`ST Token: ${token}`));
139279
139289
  console.log(source_default.gray(`Billing: ${billing}`));
139280
139290
  console.log(source_default.gray(`Databases: ${databases}`));
139281
- cachedAddresses = { addressBook: addressBookAddr, processorRegistry, controller, token, billing, databases };
139291
+ console.log(source_default.gray(`Permissions: ${permissions}`));
139292
+ cachedAddresses = {
139293
+ addressBook: addressBookAddr,
139294
+ processorRegistry,
139295
+ controller,
139296
+ token,
139297
+ billing,
139298
+ databases,
139299
+ permissions
139300
+ };
139282
139301
  return cachedAddresses;
139283
139302
  }
139284
139303
  function getWalletFromPrivateKey(privateKey) {
@@ -139314,6 +139333,11 @@ async function checkBillingBalance(config, addresses, walletAddress) {
139314
139333
  return 0n;
139315
139334
  }
139316
139335
  }
139336
+ async function isOperatorOnChain(config, addresses, owner, operator) {
139337
+ const provider = new ethers_exports.JsonRpcProvider(config.rpcUrl);
139338
+ const permissions = new ethers_exports.Contract(addresses.permissions, PERMISSIONS_ABI, provider);
139339
+ return await permissions.isOperator(owner, operator);
139340
+ }
139317
139341
  async function uploadToIPFS(fileBuffer, ipfsUrl) {
139318
139342
  const formData = new FormData();
139319
139343
  const blob = new Blob([fileBuffer], { type: "application/octet-stream" });
@@ -139398,7 +139422,7 @@ async function waitForProcessorDatabaseCleanup(addresses, processorId, provider,
139398
139422
  }
139399
139423
  throw new Error(`Timeout: processor databases not cleaned up within ${timeoutMs / 1e3}s`);
139400
139424
  }
139401
- async function createProcessorOnChain(config, addresses, wallet, processorId, ipfsCid, requiredChainIds, sdkVersion) {
139425
+ async function createProcessorOnChain(config, addresses, wallet, processorId, ipfsCid, requiredChainIds, sdkVersion, ownerOverride) {
139402
139426
  const provider = new ethers_exports.JsonRpcProvider(config.rpcUrl);
139403
139427
  const signer = wallet.connect(provider);
139404
139428
  const registry = new ethers_exports.Contract(addresses.processorRegistry, PROCESSOR_REGISTRY_ABI, signer);
@@ -139417,11 +139441,22 @@ async function createProcessorOnChain(config, addresses, wallet, processorId, ip
139417
139441
  console.log(source_default.gray(` IPFS CID: ${ipfsCid}`));
139418
139442
  console.log(source_default.gray(` Chains: ${requiredChainIds.join(", ")}`));
139419
139443
  console.log(source_default.gray(` SDK Version: ${sdkVersion}`));
139420
- const tx = await submitAndWait(
139421
- config.explorerUrl,
139422
- "createProcessor",
139423
- registry.createProcessor(processorId, source, requireChains, sdkVersion)
139444
+ if (ownerOverride) {
139445
+ console.log(source_default.gray(` Owner: ${ownerOverride} (operator: ${wallet.address})`));
139446
+ }
139447
+ const txPromise = ownerOverride ? registry["createProcessor(address,string,(uint8,string),(string,bool,bool)[],string)"](
139448
+ ownerOverride,
139449
+ processorId,
139450
+ source,
139451
+ requireChains,
139452
+ sdkVersion
139453
+ ) : registry["createProcessor(string,(uint8,string),(string,bool,bool)[],string)"](
139454
+ processorId,
139455
+ source,
139456
+ requireChains,
139457
+ sdkVersion
139424
139458
  );
139459
+ const tx = await submitAndWait(config.explorerUrl, "createProcessor", txPromise);
139425
139460
  console.log(source_default.green(`Processor created. Tx: ${config.explorerUrl}/tx/${tx.hash}`));
139426
139461
  return tx.hash;
139427
139462
  }
@@ -139448,24 +139483,31 @@ async function stopProcessorOnChain(config, addresses, wallet, processorId) {
139448
139483
  console.log(source_default.green(`Processor stopped. Tx: ${config.explorerUrl}/tx/${tx.hash}`));
139449
139484
  return tx.hash;
139450
139485
  }
139451
- async function confirmNoPlatformUpload(walletAddress, stBalance, billingBalance, addresses, networkConfig) {
139486
+ async function confirmNoPlatformUpload(walletAddress, stBalance, billingBalance, addresses, networkConfig, ownerOverride) {
139452
139487
  const formattedST = ethers_exports.formatEther(stBalance);
139453
139488
  const formattedBilling = ethers_exports.formatEther(billingBalance);
139454
139489
  console.log();
139455
139490
  console.log(source_default.blue("=== Sentio Network Direct Upload (No Platform) ==="));
139456
- console.log(source_default.white(` Address: ${walletAddress}`));
139491
+ if (ownerOverride) {
139492
+ console.log(source_default.white(` Owner: ${ownerOverride}`));
139493
+ console.log(source_default.white(` Operator: ${walletAddress}`));
139494
+ } else {
139495
+ console.log(source_default.white(` Address: ${walletAddress}`));
139496
+ }
139457
139497
  console.log(source_default.white(` ST Balance: ${formattedST} ST`));
139458
139498
  console.log(source_default.white(` Billing Balance: ${formattedBilling} ST`));
139459
139499
  if (billingBalance === 0n) {
139500
+ const who = ownerOverride ? `the owner (${ownerOverride})` : "you";
139501
+ const keyHint = ownerOverride ? "<owner's private key>" : "$PRIVATE_KEY";
139460
139502
  console.log();
139461
139503
  console.log(
139462
139504
  source_default.yellow(
139463
- ` \u26A0 Your Billing balance is 0. Indexing fees are charged from the Billing contract.
139464
- You must deposit ST tokens into the Billing contract before your processor can run.
139505
+ ` \u26A0 ${ownerOverride ? "Owner's" : "Your"} Billing balance is 0. Indexing fees are charged from the Billing contract.
139506
+ ${who} must deposit ST tokens into the Billing contract before the processor can run.
139465
139507
  Example (replace 100 with your desired ST amount):
139466
139508
  export AMOUNT=$(cast to-wei 100 ether)
139467
- cast send ${addresses.token} "approve(address,uint256)" ${addresses.billing} $AMOUNT --rpc-url ${networkConfig.rpcUrl} --private-key $PRIVATE_KEY
139468
- cast send ${addresses.billing} "deposit(uint256)" $AMOUNT --rpc-url ${networkConfig.rpcUrl} --private-key $PRIVATE_KEY`
139509
+ cast send ${addresses.token} "approve(address,uint256)" ${addresses.billing} $AMOUNT --rpc-url ${networkConfig.rpcUrl} --private-key ${keyHint}
139510
+ cast send ${addresses.billing} "deposit(uint256)" $AMOUNT --rpc-url ${networkConfig.rpcUrl} --private-key ${keyHint}`
139469
139511
  )
139470
139512
  );
139471
139513
  }
@@ -139763,7 +139805,10 @@ function myParseInt(value, dummyPrevious) {
139763
139805
  return parsedValue;
139764
139806
  }
139765
139807
  function createUploadCommand() {
139766
- return new Command("upload").description("Upload processor to Sentio").option("--owner <owner>", "(Optional) Override Project owner").option("--name <name>", "(Optional) Override Project name").option(
139808
+ return new Command("upload").description("Upload processor to Sentio").option(
139809
+ "--owner <owner>",
139810
+ "(Optional) Project owner. Platform mode: project owner username. --no-platform mode: on-chain owner address (0x...) \u2014 $PRIVATE_KEY then signs as an operator on the owner's behalf, requires Permissions.addOperator beforehand."
139811
+ ).option("--name <name>", "(Optional) Override Project name").option(
139767
139812
  "--continue-from <version>",
139768
139813
  "(Optional) Continue processing data from the specific processor version which will keeping the old data from previous version and will STOP that version IMMEDIATELY.",
139769
139814
  myParseInt
@@ -139799,13 +139844,41 @@ async function runNoPlatformUpload(processorConfig, options) {
139799
139844
  const privateKey = requirePrivateKey();
139800
139845
  const wallet = getWalletFromPrivateKey(privateKey);
139801
139846
  const walletAddress = wallet.address;
139847
+ let ownerOverride;
139848
+ if (options.owner) {
139849
+ if (!ethers_exports.isAddress(options.owner)) {
139850
+ console.error(source_default.red(`Invalid --owner: "${options.owner}" is not a valid 0x-prefixed Ethereum address.`));
139851
+ process.exit(1);
139852
+ }
139853
+ ownerOverride = ethers_exports.getAddress(options.owner);
139854
+ }
139855
+ const effectiveOwner = ownerOverride ?? walletAddress;
139802
139856
  console.log(source_default.blue("Resolving contract addresses from AddressBook..."));
139803
139857
  const addresses = await resolveNetworkAddresses(networkConfig);
139858
+ if (ownerOverride) {
139859
+ const isOp = await isOperatorOnChain(networkConfig, addresses, ownerOverride, walletAddress);
139860
+ if (!isOp) {
139861
+ console.error(
139862
+ source_default.red(
139863
+ `Operator ${walletAddress} is not authorized for owner ${ownerOverride}.
139864
+ The owner must call Permissions.addOperator(${walletAddress}) first.`
139865
+ )
139866
+ );
139867
+ process.exit(1);
139868
+ }
139869
+ }
139804
139870
  const [stBalance, billingBalance] = await Promise.all([
139805
- checkSTBalance(networkConfig, addresses, walletAddress),
139806
- checkBillingBalance(networkConfig, addresses, walletAddress)
139871
+ checkSTBalance(networkConfig, addresses, effectiveOwner),
139872
+ checkBillingBalance(networkConfig, addresses, effectiveOwner)
139807
139873
  ]);
139808
- const confirmed = await confirmNoPlatformUpload(walletAddress, stBalance, billingBalance, addresses, networkConfig);
139874
+ const confirmed = await confirmNoPlatformUpload(
139875
+ walletAddress,
139876
+ stBalance,
139877
+ billingBalance,
139878
+ addresses,
139879
+ networkConfig,
139880
+ ownerOverride
139881
+ );
139809
139882
  if (!confirmed) {
139810
139883
  console.log("Upload cancelled.");
139811
139884
  process.exit(0);
@@ -139855,9 +139928,10 @@ async function runNoPlatformUpload(processorConfig, options) {
139855
139928
  const existingProcessor = await getProcessorOnChain(networkConfig, addresses, processorId);
139856
139929
  if (existingProcessor) {
139857
139930
  const existingOwner = existingProcessor.owner.toLowerCase();
139858
- const currentWallet = wallet.address.toLowerCase();
139859
- if (existingOwner === currentWallet) {
139860
- console.log(source_default.yellow(`Processor "${processorId}" already exists (owned by you).`));
139931
+ const expectedOwner = effectiveOwner.toLowerCase();
139932
+ if (existingOwner === expectedOwner) {
139933
+ const ownedBy = ownerOverride ? `owned by ${ownerOverride}` : "owned by you";
139934
+ console.log(source_default.yellow(`Processor "${processorId}" already exists (${ownedBy}).`));
139861
139935
  const shouldReplace = await confirm(`Replace existing processor "${processorId}"?`);
139862
139936
  if (!shouldReplace) {
139863
139937
  console.log("Upload cancelled.");
@@ -139866,9 +139940,10 @@ async function runNoPlatformUpload(processorConfig, options) {
139866
139940
  await stopProcessorOnChain(networkConfig, addresses, wallet, processorId);
139867
139941
  await deleteProcessorOnChain(networkConfig, addresses, wallet, processorId);
139868
139942
  } else {
139943
+ const expectedLabel = ownerOverride ? `expected owner ${ownerOverride}` : `your wallet ${wallet.address}`;
139869
139944
  console.log(
139870
139945
  source_default.yellow(
139871
- `Processor "${processorId}" already exists and is owned by ${existingProcessor.owner} (not your wallet ${wallet.address}).`
139946
+ `Processor "${processorId}" already exists and is owned by ${existingProcessor.owner} (not ${expectedLabel}).`
139872
139947
  )
139873
139948
  );
139874
139949
  const randomSuffix = Math.random().toString(36).substring(2, 8);
@@ -139884,7 +139959,16 @@ async function runNoPlatformUpload(processorConfig, options) {
139884
139959
  console.log(source_default.blue(`Using processor ID: ${processorId}`));
139885
139960
  }
139886
139961
  }
139887
- await createProcessorOnChain(networkConfig, addresses, wallet, processorId, cid, requiredChainIds, sdkVersion);
139962
+ await createProcessorOnChain(
139963
+ networkConfig,
139964
+ addresses,
139965
+ wallet,
139966
+ processorId,
139967
+ cid,
139968
+ requiredChainIds,
139969
+ sdkVersion,
139970
+ ownerOverride
139971
+ );
139888
139972
  await startProcessorOnChain(networkConfig, addresses, wallet, processorId);
139889
139973
  console.log();
139890
139974
  console.log(source_default.green("=== Upload Complete ==="));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentio/cli",
3
- "version": "3.6.3-rc.1",
3
+ "version": "3.7.0-rc.2",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -20,6 +20,7 @@ import {
20
20
  requirePrivateKey,
21
21
  checkSTBalance,
22
22
  checkBillingBalance,
23
+ isOperatorOnChain,
23
24
  uploadToIPFS,
24
25
  createProcessorOnChain,
25
26
  startProcessorOnChain,
@@ -28,6 +29,7 @@ import {
28
29
  getProcessorOnChain,
29
30
  deleteProcessorOnChain
30
31
  } from '../network.js'
32
+ import { ethers } from 'ethers'
31
33
  import { Auth, DefaultBatchUploader, FileType, IPFSBatchUploader, WalrusBatchUploader } from '../uploader.js'
32
34
  export { type Auth } from '../uploader.js'
33
35
 
@@ -43,7 +45,10 @@ function myParseInt(value: string, dummyPrevious: number): number {
43
45
  export function createUploadCommand() {
44
46
  return new Command('upload')
45
47
  .description('Upload processor to Sentio')
46
- .option('--owner <owner>', '(Optional) Override Project owner')
48
+ .option(
49
+ '--owner <owner>',
50
+ "(Optional) Project owner. Platform mode: project owner username. --no-platform mode: on-chain owner address (0x...) — $PRIVATE_KEY then signs as an operator on the owner's behalf, requires Permissions.addOperator beforehand."
51
+ )
47
52
  .option('--name <name>', '(Optional) Override Project name')
48
53
  .option(
49
54
  '--continue-from <version>',
@@ -102,21 +107,53 @@ async function runNoPlatformUpload(
102
107
  const network = options.sentioNetwork || 'testnet'
103
108
  const networkConfig = getSentioNetworkConfig(network)
104
109
 
105
- // Step 1: Require PRIVATE_KEY
110
+ // Step 1: Require PRIVATE_KEY (operator key when --owner is set, otherwise the owner's own key)
106
111
  const privateKey = requirePrivateKey()
107
112
  const wallet = getWalletFromPrivateKey(privateKey)
108
113
  const walletAddress = wallet.address
109
114
 
115
+ // Operator mode: --owner is the on-chain owner address; PRIVATE_KEY signs as operator on its behalf.
116
+ let ownerOverride: string | undefined
117
+ if (options.owner) {
118
+ if (!ethers.isAddress(options.owner)) {
119
+ console.error(chalk.red(`Invalid --owner: "${options.owner}" is not a valid 0x-prefixed Ethereum address.`))
120
+ process.exit(1)
121
+ }
122
+ ownerOverride = ethers.getAddress(options.owner)
123
+ }
124
+ const effectiveOwner = ownerOverride ?? walletAddress
125
+
110
126
  // Step 2: Resolve contract addresses via AddressBook
111
127
  console.log(chalk.blue('Resolving contract addresses from AddressBook...'))
112
128
  const addresses = await resolveNetworkAddresses(networkConfig)
113
129
 
114
- // Step 3: Check ST balance and Billing balance, then confirm
130
+ // Step 2.5: Pre-flight check that operator is registered for this owner.
131
+ if (ownerOverride) {
132
+ const isOp = await isOperatorOnChain(networkConfig, addresses, ownerOverride, walletAddress)
133
+ if (!isOp) {
134
+ console.error(
135
+ chalk.red(
136
+ `Operator ${walletAddress} is not authorized for owner ${ownerOverride}.\n` +
137
+ `The owner must call Permissions.addOperator(${walletAddress}) first.`
138
+ )
139
+ )
140
+ process.exit(1)
141
+ }
142
+ }
143
+
144
+ // Step 3: Check ST balance and Billing balance (always against the owner — operator just submits txs)
115
145
  const [stBalance, billingBalance] = await Promise.all([
116
- checkSTBalance(networkConfig, addresses, walletAddress),
117
- checkBillingBalance(networkConfig, addresses, walletAddress)
146
+ checkSTBalance(networkConfig, addresses, effectiveOwner),
147
+ checkBillingBalance(networkConfig, addresses, effectiveOwner)
118
148
  ])
119
- const confirmed = await confirmNoPlatformUpload(walletAddress, stBalance, billingBalance, addresses, networkConfig)
149
+ const confirmed = await confirmNoPlatformUpload(
150
+ walletAddress,
151
+ stBalance,
152
+ billingBalance,
153
+ addresses,
154
+ networkConfig,
155
+ ownerOverride
156
+ )
120
157
  if (!confirmed) {
121
158
  console.log('Upload cancelled.')
122
159
  process.exit(0)
@@ -179,11 +216,13 @@ async function runNoPlatformUpload(
179
216
 
180
217
  if (existingProcessor) {
181
218
  const existingOwner = existingProcessor.owner.toLowerCase()
182
- const currentWallet = wallet.address.toLowerCase()
219
+ const expectedOwner = effectiveOwner.toLowerCase()
183
220
 
184
- if (existingOwner === currentWallet) {
185
- // Same owner — prompt to replace
186
- console.log(chalk.yellow(`Processor "${processorId}" already exists (owned by you).`))
221
+ if (existingOwner === expectedOwner) {
222
+ // Same owner — prompt to replace. In operator mode the operator is allowed to
223
+ // stop+delete via the contract's isProcessorAdmin check.
224
+ const ownedBy = ownerOverride ? `owned by ${ownerOverride}` : 'owned by you'
225
+ console.log(chalk.yellow(`Processor "${processorId}" already exists (${ownedBy}).`))
187
226
  const shouldReplace = await confirm(`Replace existing processor "${processorId}"?`)
188
227
  if (!shouldReplace) {
189
228
  console.log('Upload cancelled.')
@@ -194,9 +233,10 @@ async function runNoPlatformUpload(
194
233
  await deleteProcessorOnChain(networkConfig, addresses, wallet, processorId)
195
234
  } else {
196
235
  // Different owner — prompt to rename
236
+ const expectedLabel = ownerOverride ? `expected owner ${ownerOverride}` : `your wallet ${wallet.address}`
197
237
  console.log(
198
238
  chalk.yellow(
199
- `Processor "${processorId}" already exists and is owned by ${existingProcessor.owner} (not your wallet ${wallet.address}).`
239
+ `Processor "${processorId}" already exists and is owned by ${existingProcessor.owner} (not ${expectedLabel}).`
200
240
  )
201
241
  )
202
242
  const randomSuffix = Math.random().toString(36).substring(2, 8)
@@ -215,7 +255,16 @@ async function runNoPlatformUpload(
215
255
  }
216
256
 
217
257
  // Step 8: Create processor on-chain
218
- await createProcessorOnChain(networkConfig, addresses, wallet, processorId, cid, requiredChainIds, sdkVersion)
258
+ await createProcessorOnChain(
259
+ networkConfig,
260
+ addresses,
261
+ wallet,
262
+ processorId,
263
+ cid,
264
+ requiredChainIds,
265
+ sdkVersion,
266
+ ownerOverride
267
+ )
219
268
 
220
269
  // Step 9: Start processor on-chain
221
270
  await startProcessorOnChain(networkConfig, addresses, wallet, processorId)
package/src/network.ts CHANGED
@@ -17,7 +17,7 @@ const TESTNET_CONFIG: SentioNetworkConfig = {
17
17
  chainId: 7892101,
18
18
  rpcUrl: 'https://sentio-testnet.rpc.sentio.xyz',
19
19
  explorerUrl: 'https://testnet-explorer.sentio.xyz',
20
- addressBookAddress: '0x11cDDF46f16925aa630Af9D5158028E56309868f'
20
+ addressBookAddress: '0x94579F0e7873097279B48d7b15043698c522e47c'
21
21
  }
22
22
 
23
23
  export function getSentioNetworkConfig(network: string): SentioNetworkConfig {
@@ -40,7 +40,7 @@ const ADDRESS_BOOK_ABI = [
40
40
  'function getAddress(bytes32 id) view returns (address)'
41
41
  ]
42
42
 
43
- // ProcessorRegistry: createProcessor, getProcessor, deleteProcessor
43
+ // ProcessorRegistry: createProcessor (self + operator overloads), getProcessor, deleteProcessor
44
44
  const PROCESSOR_REGISTRY_ABI = [
45
45
  `function createProcessor(
46
46
  string id,
@@ -48,6 +48,13 @@ const PROCESSOR_REGISTRY_ABI = [
48
48
  tuple(string chainId, bool enableRpc, bool enableTrace)[] requireChains,
49
49
  string sdkVersion
50
50
  ) returns (string)`,
51
+ `function createProcessor(
52
+ address owner,
53
+ string id,
54
+ tuple(uint8 sourceType, string ipfsCid) source,
55
+ tuple(string chainId, bool enableRpc, bool enableTrace)[] requireChains,
56
+ string sdkVersion
57
+ ) returns (string)`,
51
58
  'event ProcessorCreated(string indexed processorId)',
52
59
  `function getProcessor(string processorId) view returns (
53
60
  tuple(
@@ -74,6 +81,9 @@ const CONTROLLER_ABI = [
74
81
  // Databases: getProcessorDatabases
75
82
  const DATABASES_ABI = ['function getProcessorDatabases(string processorId) view returns (string[])']
76
83
 
84
+ // Permissions: isOperator (used to pre-check operator delegation before submitting tx)
85
+ const PERMISSIONS_ABI = ['function isOperator(address account, address operator) view returns (bool)']
86
+
77
87
  // ERC20: balanceOf + approve
78
88
  const ERC20_ABI = [
79
89
  'function balanceOf(address account) view returns (uint256)',
@@ -91,7 +101,8 @@ const ADDRESS_BOOK_KEYS = {
91
101
  controller: 'controller',
92
102
  token: 'sentio_token',
93
103
  billing: 'billing',
94
- databases: 'databases'
104
+ databases: 'databases',
105
+ permissions: 'permissions'
95
106
  } as const
96
107
 
97
108
  interface ResolvedAddresses {
@@ -101,6 +112,7 @@ interface ResolvedAddresses {
101
112
  token: string
102
113
  billing: string
103
114
  databases: string
115
+ permissions: string
104
116
  }
105
117
 
106
118
  let cachedAddresses: ResolvedAddresses | undefined
@@ -130,12 +142,13 @@ export async function resolveNetworkAddresses(config: SentioNetworkConfig): Prom
130
142
  }
131
143
  }
132
144
 
133
- const [processorRegistry, controller, token, billing, databases] = await Promise.all([
145
+ const [processorRegistry, controller, token, billing, databases, permissions] = await Promise.all([
134
146
  resolveAddress(ADDRESS_BOOK_KEYS.processorRegistry),
135
147
  resolveAddress(ADDRESS_BOOK_KEYS.controller),
136
148
  resolveAddress(ADDRESS_BOOK_KEYS.token),
137
149
  resolveAddress(ADDRESS_BOOK_KEYS.billing),
138
- resolveAddress(ADDRESS_BOOK_KEYS.databases)
150
+ resolveAddress(ADDRESS_BOOK_KEYS.databases),
151
+ resolveAddress(ADDRESS_BOOK_KEYS.permissions)
139
152
  ])
140
153
 
141
154
  console.log(chalk.gray(`ProcessorRegistry: ${processorRegistry}`))
@@ -143,8 +156,17 @@ export async function resolveNetworkAddresses(config: SentioNetworkConfig): Prom
143
156
  console.log(chalk.gray(`ST Token: ${token}`))
144
157
  console.log(chalk.gray(`Billing: ${billing}`))
145
158
  console.log(chalk.gray(`Databases: ${databases}`))
146
-
147
- cachedAddresses = { addressBook: addressBookAddr, processorRegistry, controller, token, billing, databases }
159
+ console.log(chalk.gray(`Permissions: ${permissions}`))
160
+
161
+ cachedAddresses = {
162
+ addressBook: addressBookAddr,
163
+ processorRegistry,
164
+ controller,
165
+ token,
166
+ billing,
167
+ databases,
168
+ permissions
169
+ }
148
170
  return cachedAddresses
149
171
  }
150
172
 
@@ -196,6 +218,17 @@ export async function checkBillingBalance(
196
218
  }
197
219
  }
198
220
 
221
+ export async function isOperatorOnChain(
222
+ config: SentioNetworkConfig,
223
+ addresses: ResolvedAddresses,
224
+ owner: string,
225
+ operator: string
226
+ ): Promise<boolean> {
227
+ const provider = new ethers.JsonRpcProvider(config.rpcUrl)
228
+ const permissions = new ethers.Contract(addresses.permissions, PERMISSIONS_ABI, provider)
229
+ return await permissions.isOperator(owner, operator)
230
+ }
231
+
199
232
  // --- IPFS Upload ---
200
233
 
201
234
  export async function uploadToIPFS(fileBuffer: Buffer, ipfsUrl: string): Promise<string> {
@@ -349,7 +382,8 @@ export async function createProcessorOnChain(
349
382
  processorId: string,
350
383
  ipfsCid: string,
351
384
  requiredChainIds: string[],
352
- sdkVersion: string
385
+ sdkVersion: string,
386
+ ownerOverride?: string
353
387
  ): Promise<string> {
354
388
  const provider = new ethers.JsonRpcProvider(config.rpcUrl)
355
389
  const signer = wallet.connect(provider)
@@ -372,12 +406,26 @@ export async function createProcessorOnChain(
372
406
  console.log(chalk.gray(` IPFS CID: ${ipfsCid}`))
373
407
  console.log(chalk.gray(` Chains: ${requiredChainIds.join(', ')}`))
374
408
  console.log(chalk.gray(` SDK Version: ${sdkVersion}`))
409
+ if (ownerOverride) {
410
+ console.log(chalk.gray(` Owner: ${ownerOverride} (operator: ${wallet.address})`))
411
+ }
375
412
 
376
- const tx = await submitAndWait(
377
- config.explorerUrl,
378
- 'createProcessor',
379
- registry.createProcessor(processorId, source, requireChains, sdkVersion)
380
- )
413
+ // ethers v6 disambiguates overloads by full signature when both are present.
414
+ const txPromise: Promise<ethers.TransactionResponse> = ownerOverride
415
+ ? registry['createProcessor(address,string,(uint8,string),(string,bool,bool)[],string)'](
416
+ ownerOverride,
417
+ processorId,
418
+ source,
419
+ requireChains,
420
+ sdkVersion
421
+ )
422
+ : registry['createProcessor(string,(uint8,string),(string,bool,bool)[],string)'](
423
+ processorId,
424
+ source,
425
+ requireChains,
426
+ sdkVersion
427
+ )
428
+ const tx = await submitAndWait(config.explorerUrl, 'createProcessor', txPromise)
381
429
  console.log(chalk.green(`Processor created. Tx: ${config.explorerUrl}/tx/${tx.hash}`))
382
430
  return tx.hash
383
431
  }
@@ -430,26 +478,34 @@ export async function confirmNoPlatformUpload(
430
478
  stBalance: bigint,
431
479
  billingBalance: bigint,
432
480
  addresses: ResolvedAddresses,
433
- networkConfig: SentioNetworkConfig
481
+ networkConfig: SentioNetworkConfig,
482
+ ownerOverride?: string
434
483
  ): Promise<boolean> {
435
484
  const formattedST = ethers.formatEther(stBalance)
436
485
  const formattedBilling = ethers.formatEther(billingBalance)
437
486
  console.log()
438
487
  console.log(chalk.blue('=== Sentio Network Direct Upload (No Platform) ==='))
439
- console.log(chalk.white(` Address: ${walletAddress}`))
488
+ if (ownerOverride) {
489
+ console.log(chalk.white(` Owner: ${ownerOverride}`))
490
+ console.log(chalk.white(` Operator: ${walletAddress}`))
491
+ } else {
492
+ console.log(chalk.white(` Address: ${walletAddress}`))
493
+ }
440
494
  console.log(chalk.white(` ST Balance: ${formattedST} ST`))
441
495
  console.log(chalk.white(` Billing Balance: ${formattedBilling} ST`))
442
496
 
443
497
  if (billingBalance === 0n) {
498
+ const who = ownerOverride ? `the owner (${ownerOverride})` : 'you'
499
+ const keyHint = ownerOverride ? "<owner's private key>" : '$PRIVATE_KEY'
444
500
  console.log()
445
501
  console.log(
446
502
  chalk.yellow(
447
- ' ⚠ Your Billing balance is 0. Indexing fees are charged from the Billing contract.\n' +
448
- ' You must deposit ST tokens into the Billing contract before your processor can run.\n' +
449
- ' Example (replace 100 with your desired ST amount):\n' +
503
+ `${ownerOverride ? "Owner's" : 'Your'} Billing balance is 0. Indexing fees are charged from the Billing contract.\n` +
504
+ ` ${who} must deposit ST tokens into the Billing contract before the processor can run.\n` +
505
+ ` Example (replace 100 with your desired ST amount):\n` +
450
506
  ` export AMOUNT=$(cast to-wei 100 ether)\n` +
451
- ` cast send ${addresses.token} "approve(address,uint256)" ${addresses.billing} $AMOUNT --rpc-url ${networkConfig.rpcUrl} --private-key $PRIVATE_KEY\n` +
452
- ` cast send ${addresses.billing} "deposit(uint256)" $AMOUNT --rpc-url ${networkConfig.rpcUrl} --private-key $PRIVATE_KEY`
507
+ ` cast send ${addresses.token} "approve(address,uint256)" ${addresses.billing} $AMOUNT --rpc-url ${networkConfig.rpcUrl} --private-key ${keyHint}\n` +
508
+ ` cast send ${addresses.billing} "deposit(uint256)" $AMOUNT --rpc-url ${networkConfig.rpcUrl} --private-key ${keyHint}`
453
509
  )
454
510
  )
455
511
  }