sdk-triggerx 0.1.30 → 0.1.32

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 CHANGED
@@ -104,7 +104,7 @@ const jobInput = {
104
104
  isImua: false, // Optional feature flag
105
105
  arguments: ['3'], // Target function args as strings
106
106
  dynamicArgumentsScriptUrl: '', // If ArgType.Dynamic, provide IPFS/URL here
107
- autotopupTG: true, // Auto top-up TG if balance is low
107
+ autotopupETH: true, // Auto top-up ETH in GasRegistry if balance is low
108
108
  };
109
109
 
110
110
  const signer = /* ethers.Signer instance */;
@@ -140,8 +140,8 @@ const jobInput = {
140
140
  dynamicArgumentsScriptUrl: 'https://your-ipfs-gateway/ipfs/your-hash',
141
141
  language:'', //Your code language exmampel-> language:'go',
142
142
 
143
- // Optional helper to auto-top up TG if low
144
- autotopupTG: true,
143
+ // Optional helper to auto-top up ETH in GasRegistry if low
144
+ autotopupETH: true,
145
145
  };
146
146
 
147
147
  const result = await createJob(client, { jobInput, signer });
@@ -180,7 +180,7 @@ const jobInput = {
180
180
  dynamicArgumentsScriptUrl: 'https://your-ipfs-url', // Script URL for dynamic args
181
181
  language:'', //Your code language exmampel-> language:'go',
182
182
  isImua: false, // Optional feature flag
183
- autotopupTG: true, // Auto top-up TG if balance is low
183
+ autotopupETH: true, // Auto top-up ETH in GasRegistry if balance is low
184
184
  };
185
185
 
186
186
  const result = await createJob(client, { jobInput, signer });
@@ -215,7 +215,7 @@ const jobInput = {
215
215
  arguments: ['5'], // Target function args as strings
216
216
  dynamicArgumentsScriptUrl: '', // If ArgType.Dynamic, provide IPFS/URL here
217
217
  isImua: false, // Optional feature flag
218
- autotopupTG: true, // Auto top-up TG if balance is low
218
+ autotopupETH: true, // Auto top-up ETH in GasRegistry if balance is low
219
219
  };
220
220
 
221
221
  const result = await createJob(client, { jobInput, signer });
@@ -266,7 +266,7 @@ const jobInput = {
266
266
  data: '0x' // empty for simple ETH transfer
267
267
  }
268
268
  ],
269
- autotopupTG: true,
269
+ autotopupETH: true,
270
270
  };
271
271
 
272
272
  await createJob(client, { jobInput, signer });
@@ -330,7 +330,7 @@ const jobInput = {
330
330
  data: swapData
331
331
  }
332
332
  ],
333
- autotopupTG: true,
333
+ autotopupETH: true,
334
334
  };
335
335
 
336
336
  await createJob(client, { jobInput, signer });
@@ -359,7 +359,7 @@ const jobInput = {
359
359
  safeAddress: '0xYourSafeAddress',
360
360
  dynamicArgumentsScriptUrl: 'https://your-ipfs-gateway/ipfs/your-hash',
361
361
  language:'go', //Your code language exmampel-> language:'go',
362
- autotopupTG: true,
362
+ autotopupETH: true,
363
363
  };
364
364
 
365
365
  await createJob(client, { jobInput, signer });
@@ -456,6 +456,7 @@ Includes:
456
456
  data: string; // Encoded function call data (hex with 0x prefix)
457
457
  }
458
458
  ```
459
+ - `autotopupETH` boolean flag on job inputs to automatically ensure sufficient ETH is deposited in the GasRegistry for job execution (legacy `autotopupTG` is still accepted but deprecated).
459
460
 
460
461
  ---
461
462
 
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Check ETH balance for a given signer using SDK-provided RPC
3
+ * This function uses our own RPC provider to ensure reliable connection
4
+ * even if the user's RPC fails
5
+ * @param signer - ethers.Signer instance (used to get wallet address)
6
+ * @param chainId - Optional chain ID. If not provided, will try to get from signer's provider
7
+ * @returns Balance information or error response
8
+ */
9
+ export declare const checkEthBalance: (userAddress: string, chainId: string | number) => Promise<{
10
+ success: boolean;
11
+ data?: {
12
+ ethBalanceWei: bigint;
13
+ ethBalance: string;
14
+ };
15
+ error?: string;
16
+ errorCode?: string;
17
+ errorType?: string;
18
+ details?: any;
19
+ }>;
20
+ /**
21
+ * @deprecated Use checkEthBalance instead. This is an alias for backward compatibility.
22
+ */
23
+ export declare const checkTgBalance: (userAddress: string, chainId: string | number) => Promise<{
24
+ success: boolean;
25
+ data?: {
26
+ ethBalanceWei: bigint;
27
+ ethBalance: string;
28
+ };
29
+ error?: string;
30
+ errorCode?: string;
31
+ errorType?: string;
32
+ details?: any;
33
+ }>;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.checkTgBalance = exports.checkEthBalance = void 0;
7
+ const ethers_1 = require("ethers");
8
+ const GasRegistry_json_1 = __importDefault(require("../contracts/abi/GasRegistry.json"));
9
+ const contractUtils_1 = require("../contracts/contractUtils");
10
+ const errors_1 = require("../utils/errors");
11
+ /**
12
+ * Check ETH balance for a given signer using SDK-provided RPC
13
+ * This function uses our own RPC provider to ensure reliable connection
14
+ * even if the user's RPC fails
15
+ * @param signer - ethers.Signer instance (used to get wallet address)
16
+ * @param chainId - Optional chain ID. If not provided, will try to get from signer's provider
17
+ * @returns Balance information or error response
18
+ */
19
+ const checkEthBalance = async (userAddress, chainId) => {
20
+ // Validate inputs
21
+ if (!userAddress || typeof userAddress !== 'string') {
22
+ return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('userAddress', 'User address is required and must be a string'), 'Validation error');
23
+ }
24
+ if (!chainId) {
25
+ return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('chainId', 'Chain ID is required'), 'Validation error');
26
+ }
27
+ try {
28
+ // Resolve chain ID
29
+ const resolvedChainId = chainId.toString();
30
+ // Get contract address
31
+ let gasRegistryContractAddress;
32
+ try {
33
+ gasRegistryContractAddress = (0, contractUtils_1.getContractAddress)(resolvedChainId, 'gasRegistry');
34
+ }
35
+ catch (configError) {
36
+ if (configError instanceof errors_1.ConfigurationError) {
37
+ return (0, errors_1.createErrorResponse)(configError, 'Configuration error');
38
+ }
39
+ return (0, errors_1.createErrorResponse)(new errors_1.ConfigurationError('Failed to get contract address', { originalError: configError }), 'Configuration error');
40
+ }
41
+ // Create contract instance with SDK RPC provider (read-only)
42
+ // This ensures we can read balance even if user's RPC fails
43
+ const contract = await (0, contractUtils_1.createContractWithSdkRpc)(gasRegistryContractAddress, GasRegistry_json_1.default, resolvedChainId);
44
+ // Read balance using our RPC provider
45
+ const ethBalanceWei = await contract.getBalance(userAddress);
46
+ // Convert from wei to ETH
47
+ const ethBalance = ethers_1.ethers.formatEther(ethBalanceWei);
48
+ console.log('ethBalance', ethBalance);
49
+ return { success: true, data: { ethBalanceWei, ethBalance } };
50
+ }
51
+ catch (error) {
52
+ console.error('Error checking ETH balance:', error);
53
+ if (error instanceof Error) {
54
+ if (error.message.includes('network') || error.message.includes('timeout')) {
55
+ return (0, errors_1.createErrorResponse)(new errors_1.NetworkError('Network error during balance check', { originalError: error }), 'Network error');
56
+ }
57
+ else if (error.message.includes('contract') || error.message.includes('transaction')) {
58
+ return (0, errors_1.createErrorResponse)(new errors_1.ContractError('Contract error during balance check', { originalError: error }), 'Contract error');
59
+ }
60
+ }
61
+ return (0, errors_1.createErrorResponse)(error, 'Failed to check ETH balance');
62
+ }
63
+ };
64
+ exports.checkEthBalance = checkEthBalance;
65
+ /**
66
+ * @deprecated Use checkEthBalance instead. This is an alias for backward compatibility.
67
+ */
68
+ exports.checkTgBalance = exports.checkEthBalance;
@@ -1,5 +1,5 @@
1
1
  import { TriggerXClient } from '../client';
2
- import { TimeBasedJobInput, EventBasedJobInput, ConditionBasedJobInput, CreateJobData, JobResponse } from '../types';
2
+ import { TimeBasedJobInput, EventBasedJobInput, ConditionBasedJobInput, CreateJobData, JobResponse, CustomScriptJobInput } from '../types';
3
3
  import { Signer } from 'ethers';
4
4
  export declare function toCreateJobDataFromTime(input: TimeBasedJobInput, balances: {
5
5
  etherBalance: bigint;
@@ -13,8 +13,12 @@ export declare function toCreateJobDataFromCondition(input: ConditionBasedJobInp
13
13
  etherBalance: bigint;
14
14
  tokenBalanceWei: bigint;
15
15
  }, userAddress: string, jobCostPrediction: number): CreateJobData;
16
+ export declare function toCreateJobDataFromCustomScript(input: CustomScriptJobInput, balances: {
17
+ etherBalance: bigint;
18
+ tokenBalanceWei: bigint;
19
+ }, userAddress: string, jobCostPrediction: number): CreateJobData;
16
20
  export interface CreateJobParams {
17
- jobInput: TimeBasedJobInput | EventBasedJobInput | ConditionBasedJobInput;
21
+ jobInput: TimeBasedJobInput | EventBasedJobInput | ConditionBasedJobInput | CustomScriptJobInput;
18
22
  signer: Signer;
19
23
  encodedData?: string;
20
24
  }
package/dist/api/jobs.js CHANGED
@@ -6,16 +6,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.toCreateJobDataFromTime = toCreateJobDataFromTime;
7
7
  exports.toCreateJobDataFromEvent = toCreateJobDataFromEvent;
8
8
  exports.toCreateJobDataFromCondition = toCreateJobDataFromCondition;
9
+ exports.toCreateJobDataFromCustomScript = toCreateJobDataFromCustomScript;
9
10
  exports.createJob = createJob;
11
+ const types_1 = require("../types");
10
12
  const JobRegistry_1 = require("../contracts/JobRegistry");
11
13
  const ethers_1 = require("ethers");
12
14
  const JobRegistry_json_1 = __importDefault(require("../contracts/abi/JobRegistry.json"));
13
- const topupTg_1 = require("./topupTg");
14
- const checkTgBalance_1 = require("./checkTgBalance");
15
+ const topup_1 = require("./topup");
16
+ const checkBalance_1 = require("./checkBalance");
15
17
  const config_1 = require("../config");
16
18
  const validation_1 = require("../utils/validation");
17
19
  const errors_1 = require("../utils/errors");
18
20
  const SafeWallet_1 = require("../contracts/safe/SafeWallet");
21
+ const JOB_ID = '300949528249665590178224313442040528409305273634097553067152835846309150732';
22
+ const DYNAMIC_ARGS_URL = 'https://teal-random-koala-993.mypinata.cloud/ipfs/bafkreif426p7t7takzhw3g6we2h6wsvf27p5jxj3gaiynqf22p3jvhx4la';
19
23
  // Helper function to encode multisend batch transactions
20
24
  function encodeMultisendData(transactions) {
21
25
  // Multisend format: for each transaction, encode as:
@@ -53,7 +57,7 @@ function encodeMultisendData(transactions) {
53
57
  }
54
58
  function toCreateJobDataFromTime(input, balances, userAddress, jobCostPrediction) {
55
59
  return {
56
- job_id: "",
60
+ job_id: JOB_ID,
57
61
  user_address: userAddress,
58
62
  ether_balance: balances.etherBalance,
59
63
  token_balance: balances.tokenBalanceWei,
@@ -76,16 +80,16 @@ function toCreateJobDataFromTime(input, balances, userAddress, jobCostPrediction
76
80
  arg_type: input.dynamicArgumentsScriptUrl ? 2 : 1,
77
81
  arguments: input.arguments,
78
82
  dynamic_arguments_script_url: input.dynamicArgumentsScriptUrl,
79
- is_imua: input.isImua ?? false,
83
+ is_imua: input.isImua ?? true,
80
84
  is_safe: input.walletMode === 'safe',
81
85
  safe_name: input.safeName || '',
82
86
  safe_address: input.safeAddress || '',
83
- language: input.language || 'go',
87
+ language: input.language || '',
84
88
  };
85
89
  }
86
90
  function toCreateJobDataFromEvent(input, balances, userAddress, jobCostPrediction) {
87
91
  return {
88
- job_id: "",
92
+ job_id: JOB_ID,
89
93
  user_address: userAddress,
90
94
  ether_balance: balances.etherBalance,
91
95
  token_balance: balances.tokenBalanceWei,
@@ -107,16 +111,16 @@ function toCreateJobDataFromEvent(input, balances, userAddress, jobCostPredictio
107
111
  arg_type: input.dynamicArgumentsScriptUrl ? 2 : 1,
108
112
  arguments: input.arguments,
109
113
  dynamic_arguments_script_url: input.dynamicArgumentsScriptUrl,
110
- is_imua: input.isImua ?? false,
114
+ is_imua: input.isImua ?? true,
111
115
  is_safe: input.walletMode === 'safe',
112
116
  safe_name: input.safeName || '',
113
117
  safe_address: input.safeAddress || '',
114
- language: input.language || 'go',
118
+ language: input.language || '',
115
119
  };
116
120
  }
117
121
  function toCreateJobDataFromCondition(input, balances, userAddress, jobCostPrediction) {
118
122
  return {
119
- job_id: "",
123
+ job_id: JOB_ID,
120
124
  user_address: userAddress,
121
125
  ether_balance: balances.etherBalance,
122
126
  token_balance: balances.tokenBalanceWei,
@@ -140,11 +144,40 @@ function toCreateJobDataFromCondition(input, balances, userAddress, jobCostPredi
140
144
  arg_type: input.dynamicArgumentsScriptUrl ? 2 : 1,
141
145
  arguments: input.arguments,
142
146
  dynamic_arguments_script_url: input.dynamicArgumentsScriptUrl,
143
- is_imua: input.isImua ?? false,
147
+ is_imua: input.isImua ?? true,
144
148
  is_safe: input.walletMode === 'safe',
145
149
  safe_name: input.safeName || '',
146
150
  safe_address: input.safeAddress || '',
147
- language: input.language || 'go',
151
+ language: input.language || '',
152
+ };
153
+ }
154
+ function toCreateJobDataFromCustomScript(input, balances, userAddress, jobCostPrediction) {
155
+ return {
156
+ job_id: JOB_ID,
157
+ user_address: userAddress,
158
+ ether_balance: balances.etherBalance,
159
+ token_balance: balances.tokenBalanceWei,
160
+ job_title: input.jobTitle,
161
+ task_definition_id: 7,
162
+ custom: true,
163
+ time_frame: input.timeFrame,
164
+ recurring: input.recurring ?? false,
165
+ job_cost_prediction: jobCostPrediction,
166
+ timezone: input.timezone,
167
+ created_chain_id: input.chainId,
168
+ target_chain_id: input.chainId,
169
+ target_contract_address: input.targetContractAddress || '',
170
+ target_function: input.targetFunction || '',
171
+ abi: input.abi || '',
172
+ arg_type: 2,
173
+ arguments: input.arguments,
174
+ dynamic_arguments_script_url: input.dynamicArgumentsScriptUrl,
175
+ is_imua: input.isImua ?? true,
176
+ is_safe: input.walletMode === 'safe',
177
+ safe_name: input.safeName || '',
178
+ safe_address: input.safeAddress || '',
179
+ language: input.language,
180
+ time_interval: input.timeInterval,
148
181
  };
149
182
  }
150
183
  // --- Encoding helpers for different job types ---
@@ -160,6 +193,9 @@ function encodeJobType3or5Data(recurringJob) {
160
193
  function encodeJobType4or6Data(recurringJob, ipfsHash) {
161
194
  return ethers_1.ethers.AbiCoder.defaultAbiCoder().encode(['bool', 'bytes32'], [recurringJob, ipfsHash]);
162
195
  }
196
+ function encodeJobType7Data(timeInterval, ipfsHash, language) {
197
+ return ethers_1.ethers.AbiCoder.defaultAbiCoder().encode(['uint256', 'bytes32', 'string'], [timeInterval, ipfsHash, language]);
198
+ }
163
199
  /**
164
200
  * Create a job on the blockchain.
165
201
  * @param client TriggerXClient instance
@@ -215,24 +251,16 @@ async function createJob(client, params) {
215
251
  }
216
252
  // Auto-set module target; user does not need to pass targetContractAddress in safe mode
217
253
  jobInput.targetContractAddress = safeModule;
218
- // Function signature must match exactly as in ABI
219
- jobInput.targetFunction = 'execJobFromHub';
220
- // ABI verified per provided interface and matches execJobFromHub
254
+ jobInput.targetFunction = 'execJobFromHub(address,address,uint256,bytes,uint8)';
221
255
  jobInput.abi = JSON.stringify([
222
256
  {
223
- "inputs": [
224
- { "internalType": "address", "name": "safeAddress", "type": "address" },
225
- { "internalType": "address", "name": "actionTarget", "type": "address" },
226
- { "internalType": "uint256", "name": "actionValue", "type": "uint256" },
227
- { "internalType": "bytes", "name": "actionData", "type": "bytes" },
228
- { "internalType": "uint8", "name": "operation", "type": "uint8" }
229
- ],
230
- "name": "execJobFromHub",
231
- "outputs": [
232
- { "internalType": "bool", "name": "success", "type": "bool" }
233
- ],
234
- "stateMutability": "nonpayable",
235
- "type": "function"
257
+ "type": "function", "name": "execJobFromHub", "stateMutability": "nonpayable", "inputs": [
258
+ { "name": "safeAddress", "type": "address" },
259
+ { "name": "actionTarget", "type": "address" },
260
+ { "name": "actionValue", "type": "uint256" },
261
+ { "name": "actionData", "type": "bytes" },
262
+ { "name": "operation", "type": "uint8" }
263
+ ], "outputs": [{ "type": "bool", "name": "success" }]
236
264
  }
237
265
  ]);
238
266
  // Handle static vs dynamic safe wallet jobs
@@ -254,7 +282,7 @@ async function createJob(client, params) {
254
282
  tx.to,
255
283
  tx.value,
256
284
  tx.data,
257
- '0' // CALL
285
+ 0 // CALL
258
286
  ];
259
287
  }
260
288
  else {
@@ -268,7 +296,7 @@ async function createJob(client, params) {
268
296
  multisendCallOnly,
269
297
  '0',
270
298
  encodedMultisendData,
271
- '1' // DELEGATECALL
299
+ 1 // DELEGATECALL
272
300
  ];
273
301
  }
274
302
  }
@@ -321,6 +349,13 @@ async function createJob(client, params) {
321
349
  else {
322
350
  jobType = jobInput.dynamicArgumentsScriptUrl ? 6 : 5; // Condition-based job
323
351
  }
352
+ const jobTypeField = jobInput.jobType;
353
+ if (jobTypeField === types_1.JobType.CustomScript ||
354
+ jobTypeField === 'custom_script' ||
355
+ jobTypeField === 7 ||
356
+ jobTypeField === '7') {
357
+ jobType = 7;
358
+ }
324
359
  // --- Generate encodedData if not provided ---
325
360
  if (!encodedData) {
326
361
  // Time-based jobs
@@ -343,6 +378,12 @@ async function createJob(client, params) {
343
378
  encodedData = encodeJobType4or6Data(jobInput.recurring ?? false, ipfsBytes32);
344
379
  }
345
380
  }
381
+ // Custom script jobs
382
+ else if (jobType === 7) {
383
+ const customInput = jobInput;
384
+ const ipfsBytes32 = ethers_1.ethers.id(customInput.dynamicArgumentsScriptUrl);
385
+ encodedData = encodeJobType7Data(customInput.timeInterval ?? 0, ipfsBytes32, customInput.language);
386
+ }
346
387
  // Condition-based jobs
347
388
  else {
348
389
  if (jobType === 3 || jobType === 5) {
@@ -354,111 +395,134 @@ async function createJob(client, params) {
354
395
  }
355
396
  }
356
397
  }
357
- // Handle job_cost_prediction logic based on argType (static/dynamic)
358
- // If static, set to 0.1. If dynamic, call backend API to get fee and ask user to proceed.
398
+ // Handle job_cost_prediction by always calling backend /api/fees
399
+ // The backend returns the total fee in wei; we convert it to token units (formatted ether)
400
+ // so it can be compared with ETH balance (which is also formatted via ethers.formatEther).
359
401
  // Determine argType directly from user input
360
402
  let argType = 1; // default to static
361
403
  if ('argType' in jobInput) {
362
- if (jobInput.argType === 'dynamic' || jobInput.argType === 2) {
404
+ if (jobInput.argType === 'dynamic' || jobInput.argType === 2 || jobInput.argType === types_1.ArgType.Dynamic) {
363
405
  argType = 2;
364
406
  }
365
407
  else {
366
408
  argType = 1;
367
409
  }
368
410
  }
411
+ if (jobType === 7) {
412
+ argType = 2;
413
+ jobInput.argType = types_1.ArgType.Dynamic;
414
+ }
369
415
  //if jobis time based then check the no of executions of the job from time frame and time interval by deviding time frame by time interval
370
416
  let noOfExecutions = 1;
371
417
  if ('scheduleType' in jobInput) {
372
- noOfExecutions = jobInput.timeFrame / (jobInput.timeInterval ?? 0);
373
- }
374
- // Set job_cost_prediction
375
- // ethers.parseEther expects a string, so we construct the ether amount string safely
376
- let job_cost_prediction = Number(ethers_1.ethers.parseEther((0.1 * noOfExecutions).toString()).toString()); // default for static
377
- // console.log('job_cost_prediction', job_cost_prediction);
378
- if (argType === 2) {
379
- // Dynamic: call backend API to get fee
380
- const ipfs_url = jobInput.dynamicArgumentsScriptUrl;
381
- if (!ipfs_url) {
382
- return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('dynamicArgumentsScriptUrl', 'dynamicArgumentsScriptUrl is required for dynamic argType'), 'Validation error');
418
+ noOfExecutions = Math.ceil(jobInput.timeFrame / (jobInput.timeInterval ?? 1));
419
+ }
420
+ else if (jobType === 7) {
421
+ const customInterval = jobInput.timeInterval ?? 0;
422
+ if (customInterval > 0) {
423
+ noOfExecutions = Math.max(1, Math.floor(jobInput.timeFrame / customInterval));
383
424
  }
384
- // Call backend API to get fee
385
- let fee = 0;
386
- try {
387
- const feeRes = await client.get('/api/fees', { params: { ipfs_url } });
388
- // console.log('feeRes', feeRes);
389
- // console.log('feeRes.total_fee', feeRes.total_fee);
390
- // console.log('typeof feeRes', typeof feeRes);
391
- // console.log('typeof feeRes.total_fee', typeof feeRes.total_fee);
392
- // The API now returns { total_fee: number }
393
- if (feeRes && typeof feeRes.total_fee === 'number') {
394
- fee = feeRes.total_fee;
395
- }
396
- else if (feeRes && feeRes.data && typeof feeRes.data.total_fee === 'number') {
397
- fee = feeRes.data.total_fee;
398
- }
399
- else {
400
- return (0, errors_1.createErrorResponse)(new errors_1.ApiError('Invalid response from /api/fees: missing total_fee', { response: feeRes }), 'API error');
425
+ }
426
+ // Prepare parameters for /api/fees
427
+ const ipfs_url = jobInput.dynamicArgumentsScriptUrl || '';
428
+ const task_definition_id = jobType; // use inferred jobType as task definition id
429
+ const target_chain_id = chainIdStr || '';
430
+ const target_contract_address = jobInput.targetContractAddress || '';
431
+ const target_function = jobInput.targetFunction || '';
432
+ const abi = jobInput.abi || '';
433
+ const args = jobInput.arguments ? JSON.stringify(jobInput.arguments) : '';
434
+ let job_cost_prediction = 0n;
435
+ try {
436
+ const feeRes = await client.get('/api/fees', {
437
+ params: {
438
+ ipfs_url,
439
+ task_definition_id,
440
+ target_chain_id,
441
+ target_contract_address,
442
+ target_function,
443
+ abi,
444
+ args,
401
445
  }
446
+ });
447
+ // The API returns total fee in wei: { total_fee: "<wei>" } or nested under data
448
+ let totalFeeRaw;
449
+ if (feeRes && feeRes.total_fee !== undefined) {
450
+ totalFeeRaw = feeRes.total_fee;
402
451
  }
403
- catch (err) {
404
- const httpStatusCode = (0, errors_1.extractHttpStatusCode)(err);
405
- const errorCode = (0, errors_1.determineErrorCode)(err, httpStatusCode);
406
- return (0, errors_1.createErrorResponse)(new errors_1.ApiError('Failed to fetch job cost prediction', { originalError: err, ipfs_url }, httpStatusCode), 'API error');
452
+ else if (feeRes && feeRes.data && feeRes.data.total_fee !== undefined) {
453
+ totalFeeRaw = feeRes.data.total_fee;
454
+ }
455
+ if (totalFeeRaw === undefined) {
456
+ return (0, errors_1.createErrorResponse)(new errors_1.ApiError('Invalid response from /api/fees: missing total_fee', { response: feeRes }), 'API error');
457
+ }
458
+ // Support both number and string representations of wei
459
+ let totalFeeWei;
460
+ if (typeof totalFeeRaw === 'string') {
461
+ totalFeeWei = BigInt(totalFeeRaw);
462
+ }
463
+ else if (typeof totalFeeRaw === 'number') {
464
+ totalFeeWei = BigInt(Math.floor(totalFeeRaw));
465
+ }
466
+ else {
467
+ return (0, errors_1.createErrorResponse)(new errors_1.ApiError('Invalid total_fee type from /api/fees', { totalFeeRaw }), 'API error');
407
468
  }
408
- // console.log('fee', fee);
409
- // console.log('noOfExecutions', noOfExecutions);
410
- job_cost_prediction = fee * noOfExecutions;
469
+ // Convert wei to token units (formatted ether) so it matches tgBalance units
470
+ // job_cost_prediction = Number(ethers.formatEther(totalFeeWei));
471
+ job_cost_prediction = totalFeeWei;
411
472
  }
412
- // console.log('job_cost_prediction', job_cost_prediction);
473
+ catch (err) {
474
+ const httpStatusCode = (0, errors_1.extractHttpStatusCode)(err);
475
+ const errorCode = (0, errors_1.determineErrorCode)(err, httpStatusCode);
476
+ return (0, errors_1.createErrorResponse)(new errors_1.ApiError('Failed to fetch job cost prediction', { originalError: err, jobType }, httpStatusCode), 'API error');
477
+ }
478
+ job_cost_prediction = job_cost_prediction * BigInt(noOfExecutions);
479
+ let requiredETHwei = job_cost_prediction; // this is in wei
413
480
  // Ask user if they want to proceed
414
481
  // Since this is a library, we can't prompt in Node.js directly.
415
482
  // We'll throw an error with the fee and let the caller handle the prompt/confirmation.
416
483
  // If you want to automate, you can add a `proceed` flag to params in the future.
417
- // Check if the user has enough TG to cover the job cost prediction
418
- // Use chainId if available, so we can use SDK RPC provider even if user's RPC fails
419
- let tgBalanceWei, tgBalance;
484
+ // Check if the user has enough ETH to cover the job cost prediction
485
+ let ethBalanceWei, ethBalance;
420
486
  try {
421
- const balanceResult = await (0, checkTgBalance_1.checkTgBalance)(signer, chainIdStr);
487
+ const balanceResult = await (0, checkBalance_1.checkEthBalance)(userAddress, chainIdStr);
422
488
  if (!balanceResult.success || !balanceResult.data) {
423
- return (0, errors_1.createErrorResponse)(new errors_1.BalanceError('Failed to check TG balance', balanceResult.details), 'Balance check error');
489
+ return (0, errors_1.createErrorResponse)(new errors_1.BalanceError('Failed to check ETH balance', balanceResult.details), 'Balance check error');
424
490
  }
425
- tgBalanceWei = balanceResult.data.tgBalanceWei;
426
- tgBalance = balanceResult.data.tgBalance;
491
+ ethBalanceWei = balanceResult.data.ethBalanceWei;
492
+ ethBalance = balanceResult.data.ethBalance;
427
493
  }
428
494
  catch (err) {
429
- return (0, errors_1.createErrorResponse)(new errors_1.BalanceError('Failed to check TG balance', { originalError: err }), 'Balance check error');
430
- }
431
- if (Number(tgBalanceWei) < job_cost_prediction) {
432
- // Check if user has enabled auto topup
433
- // For each job type, autotopupTG should be present in jobInput
434
- const autoTopupTG = jobInput.autotopupTG === true;
435
- if (!autoTopupTG) {
436
- return (0, errors_1.createErrorResponse)(new errors_1.BalanceError(`Insufficient TG balance. Job cost prediction is ${job_cost_prediction}. Current TG balance: ${tgBalance}. Please set autotopupTG: true in jobInput.`, {
437
- required: job_cost_prediction,
438
- current: tgBalance,
439
- autoTopupEnabled: false
440
- }), 'Insufficient balance');
441
- }
442
- else {
443
- // autotopupTG is true, automatically top up
444
- const requiredTG = Math.ceil(job_cost_prediction); // 1 TG = 0.001 ETH
445
- try {
446
- console.log('topping up TG balance', requiredTG);
447
- const topupResult = await (0, topupTg_1.topupTg)(requiredTG, signer);
448
- if (!topupResult.success) {
449
- return (0, errors_1.createErrorResponse)(new errors_1.BalanceError('Failed to top up TG balance', topupResult.details), 'Top-up error');
450
- }
451
- }
452
- catch (err) {
453
- return (0, errors_1.createErrorResponse)(new errors_1.BalanceError('Failed to top up TG balance', { originalError: err, requiredTG }), 'Top-up error');
495
+ return (0, errors_1.createErrorResponse)(new errors_1.BalanceError('Failed to check ETH balance', { originalError: err }), 'Balance check error');
496
+ }
497
+ // Check if user has enabled auto topup
498
+ // For each job type, autotopupETH should be present in jobInput
499
+ // Support autotopupTG for backward compatibility
500
+ const autoTopupETH = jobInput.autotopupETH === true || jobInput.autotopupTG === true;
501
+ if (!autoTopupETH) {
502
+ return (0, errors_1.createErrorResponse)(new errors_1.BalanceError(`Insufficient ETH balance. Job cost prediction is ${requiredETHwei}. Current ETH balance: ${ethBalanceWei}. Please set autotopupETH: true in jobInput.`, {
503
+ required: requiredETHwei,
504
+ current: ethBalanceWei,
505
+ autoTopupEnabled: false
506
+ }), 'Insufficient balance');
507
+ }
508
+ else {
509
+ // autotopupETH is true, automatically top up
510
+ const requiredETH = requiredETHwei;
511
+ try {
512
+ const topupResult = await (0, topup_1.depositEth)(requiredETHwei, signer);
513
+ if (!topupResult.success) {
514
+ return (0, errors_1.createErrorResponse)(new errors_1.BalanceError('Failed to deposit ETH balance', topupResult.details), 'Top-up error');
454
515
  }
455
516
  }
517
+ catch (err) {
518
+ return (0, errors_1.createErrorResponse)(new errors_1.BalanceError('Failed to deposit ETH balance', { originalError: err, requiredETH }), 'Top-up error');
519
+ }
456
520
  }
457
521
  // Compute balances to store with the job
458
- const tokenBalanceWei = tgBalanceWei;
459
- const etherBalance = tokenBalanceWei / 1000n;
522
+ const tokenBalanceWei = ethBalanceWei;
523
+ const etherBalance = tokenBalanceWei;
460
524
  // Patch jobInput with job_cost_prediction for downstream usage
461
- jobInput.jobCostPrediction = job_cost_prediction;
525
+ jobInput.jobCostPrediction = Number(ethers_1.ethers.formatEther(tokenBalanceWei)); // this is in ether
462
526
  let jobId;
463
527
  try {
464
528
  jobId = await (0, JobRegistry_1.createJobOnChain)({
@@ -468,7 +532,7 @@ async function createJob(client, params) {
468
532
  targetContractAddress: targetContractAddress,
469
533
  encodedData: encodedData || '',
470
534
  contractAddress: JOB_REGISTRY_ADDRESS,
471
- abi: JobRegistry_json_1.default.abi,
535
+ abi: JobRegistry_json_1.default,
472
536
  signer,
473
537
  });
474
538
  }
@@ -479,13 +543,16 @@ async function createJob(client, params) {
479
543
  let jobData;
480
544
  const balances = { etherBalance, tokenBalanceWei };
481
545
  if ('scheduleType' in jobInput) {
482
- jobData = toCreateJobDataFromTime(jobInput, balances, userAddress, job_cost_prediction);
546
+ jobData = toCreateJobDataFromTime(jobInput, balances, userAddress, Number(ethers_1.ethers.formatEther(job_cost_prediction)));
483
547
  }
484
548
  else if ('triggerChainId' in jobInput) {
485
- jobData = toCreateJobDataFromEvent(jobInput, balances, userAddress, job_cost_prediction);
549
+ jobData = toCreateJobDataFromEvent(jobInput, balances, userAddress, Number(ethers_1.ethers.formatEther(job_cost_prediction)));
550
+ }
551
+ else if (jobType === 7) {
552
+ jobData = toCreateJobDataFromCustomScript(jobInput, balances, userAddress, Number(ethers_1.ethers.formatEther(job_cost_prediction)));
486
553
  }
487
554
  else {
488
- jobData = toCreateJobDataFromCondition(jobInput, balances, userAddress, job_cost_prediction);
555
+ jobData = toCreateJobDataFromCondition(jobInput, balances, userAddress, Number(ethers_1.ethers.formatEther(job_cost_prediction)));
489
556
  }
490
557
  // 3. Set the job_id from contract
491
558
  jobData.job_id = jobId;
@@ -497,7 +564,6 @@ async function createJob(client, params) {
497
564
  ether_balance: typeof jobData.ether_balance === 'bigint' ? Number(jobData.ether_balance) : Number(jobData.ether_balance),
498
565
  token_balance: typeof jobData.token_balance === 'bigint' ? Number(jobData.token_balance) : Number(jobData.token_balance),
499
566
  };
500
- // console.log('jobDataForApi', jobDataForApi);
501
567
  const res = await client.post('/api/jobs', [jobDataForApi], {
502
568
  headers: { 'Content-Type': 'application/json', 'X-API-KEY': apiKey },
503
569
  });