sdk-triggerx 0.1.16 → 0.1.18

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/dist/api/jobs.js CHANGED
@@ -13,6 +13,9 @@ const JobRegistry_json_1 = __importDefault(require("../contracts/abi/JobRegistry
13
13
  const topupTg_1 = require("./topupTg");
14
14
  const checkTgBalance_1 = require("./checkTgBalance");
15
15
  const config_1 = require("../config");
16
+ const validation_1 = require("../utils/validation");
17
+ const errors_1 = require("../utils/errors");
18
+ const SafeWallet_1 = require("../contracts/safe/SafeWallet");
16
19
  const JOB_ID = '300949528249665590178224313442040528409305273634097553067152835846309150732';
17
20
  const DYNAMIC_ARGS_URL = 'https://teal-random-koala-993.mypinata.cloud/ipfs/bafkreif426p7t7takzhw3g6we2h6wsvf27p5jxj3gaiynqf22p3jvhx4la';
18
21
  function toCreateJobDataFromTime(input, balances, userAddress, jobCostPrediction) {
@@ -34,9 +37,9 @@ function toCreateJobDataFromTime(input, balances, userAddress, jobCostPrediction
34
37
  cron_expression: input.scheduleType === 'cron' ? input.cronExpression : undefined,
35
38
  specific_schedule: input.scheduleType === 'specific' ? input.specificSchedule : undefined,
36
39
  target_chain_id: input.chainId,
37
- target_contract_address: input.targetContractAddress,
38
- target_function: input.targetFunction,
39
- abi: input.abi,
40
+ target_contract_address: input.targetContractAddress || '',
41
+ target_function: input.targetFunction || '',
42
+ abi: input.abi || '',
40
43
  arg_type: input.dynamicArgumentsScriptUrl ? 2 : 1,
41
44
  arguments: input.arguments,
42
45
  dynamic_arguments_script_url: input.dynamicArgumentsScriptUrl,
@@ -61,9 +64,9 @@ function toCreateJobDataFromEvent(input, balances, userAddress, jobCostPredictio
61
64
  trigger_contract_address: input.triggerContractAddress,
62
65
  trigger_event: input.triggerEvent,
63
66
  target_chain_id: input.chainId,
64
- target_contract_address: input.targetContractAddress,
65
- target_function: input.targetFunction,
66
- abi: input.abi,
67
+ target_contract_address: input.targetContractAddress || '',
68
+ target_function: input.targetFunction || '',
69
+ abi: input.abi || '',
67
70
  arg_type: input.dynamicArgumentsScriptUrl ? 2 : 1,
68
71
  arguments: input.arguments,
69
72
  dynamic_arguments_script_url: input.dynamicArgumentsScriptUrl,
@@ -90,9 +93,9 @@ function toCreateJobDataFromCondition(input, balances, userAddress, jobCostPredi
90
93
  value_source_type: input.valueSourceType,
91
94
  value_source_url: input.valueSourceUrl,
92
95
  target_chain_id: input.chainId,
93
- target_contract_address: input.targetContractAddress,
94
- target_function: input.targetFunction,
95
- abi: input.abi,
96
+ target_contract_address: input.targetContractAddress || '',
97
+ target_function: input.targetFunction || '',
98
+ abi: input.abi || '',
96
99
  arg_type: input.dynamicArgumentsScriptUrl ? 2 : 1,
97
100
  arguments: input.arguments,
98
101
  dynamic_arguments_script_url: input.dynamicArgumentsScriptUrl,
@@ -122,32 +125,102 @@ async function createJob(client, params) {
122
125
  let { jobInput, signer, encodedData } = params;
123
126
  // Use the API key from the client instance
124
127
  const apiKey = client.getApiKey();
125
- const userAddress = await signer.getAddress();
128
+ if (!apiKey) {
129
+ return (0, errors_1.createErrorResponse)(new errors_1.AuthenticationError('API key is required but not provided'), 'Authentication failed');
130
+ }
131
+ let userAddress;
132
+ try {
133
+ userAddress = await signer.getAddress();
134
+ }
135
+ catch (err) {
136
+ return (0, errors_1.createErrorResponse)(new errors_1.AuthenticationError('Failed to get signer address', { originalError: err }), 'Authentication failed');
137
+ }
126
138
  // Resolve chain-specific addresses
127
- const network = await signer.provider?.getNetwork();
139
+ let network;
140
+ try {
141
+ network = await signer.provider?.getNetwork();
142
+ }
143
+ catch (err) {
144
+ return (0, errors_1.createErrorResponse)(new errors_1.NetworkError('Failed to get network information', { originalError: err }), 'Network error');
145
+ }
128
146
  const chainIdStr = network?.chainId ? network.chainId.toString() : undefined;
129
- const { jobRegistry } = (0, config_1.getChainAddresses)(chainIdStr);
147
+ const { jobRegistry, safeModule, safeFactory } = (0, config_1.getChainAddresses)(chainIdStr);
130
148
  const JOB_REGISTRY_ADDRESS = jobRegistry;
131
149
  if (!JOB_REGISTRY_ADDRESS) {
132
- return { success: false, error: 'JobRegistry address not configured for this chain. Update config mapping.' };
150
+ return (0, errors_1.createErrorResponse)(new errors_1.ConfigurationError(`JobRegistry address not configured for chain ID: ${chainIdStr}`), 'Configuration error');
151
+ }
152
+ // If Safe mode, override target fields BEFORE validation so user doesn't need to provide them
153
+ const walletModePre = jobInput.walletMode;
154
+ if (walletModePre === 'safe') {
155
+ if (!safeModule) {
156
+ return (0, errors_1.createErrorResponse)(new errors_1.ConfigurationError('Safe Module address not configured for this chain.'), 'Configuration error');
157
+ }
158
+ // If safeAddress is missing, require it (must be created by the user before this call)
159
+ if (!jobInput.safeAddress || typeof jobInput.safeAddress !== 'string' || !jobInput.safeAddress.trim()) {
160
+ return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('safeAddress', 'safeAddress is required when walletMode is "safe". Call createSafeWallet first.'), 'Validation error');
161
+ }
162
+ const dynUrl = jobInput.dynamicArgumentsScriptUrl;
163
+ if (!dynUrl) {
164
+ return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('dynamicArgumentsScriptUrl', 'Safe jobs require dynamicArgumentsScriptUrl (IPFS) for parameters.'), 'Validation error');
165
+ }
166
+ // Validate Safe has single owner and owner matches signer
167
+ try {
168
+ await (0, SafeWallet_1.ensureSingleOwnerAndMatchSigner)(jobInput.safeAddress, signer.provider, await signer.getAddress());
169
+ // Ensure module is enabled on Safe
170
+ await (0, SafeWallet_1.enableSafeModule)(jobInput.safeAddress, signer, safeModule);
171
+ }
172
+ catch (err) {
173
+ return (0, errors_1.createErrorResponse)(new errors_1.ContractError('Failed to configure Safe wallet', { originalError: err, safeAddress: jobInput.safeAddress }), 'Contract error');
174
+ }
175
+ // Auto-set module target; user does not need to pass targetContractAddress in safe mode
176
+ jobInput.targetContractAddress = safeModule;
177
+ jobInput.targetFunction = 'execJobFromHub(address,address,uint256,bytes,uint8)';
178
+ jobInput.abi = JSON.stringify([
179
+ {
180
+ "type": "function", "name": "execJobFromHub", "stateMutability": "nonpayable", "inputs": [
181
+ { "name": "safeAddress", "type": "address" },
182
+ { "name": "actionTarget", "type": "address" },
183
+ { "name": "actionValue", "type": "uint256" },
184
+ { "name": "actionData", "type": "bytes" },
185
+ { "name": "operation", "type": "uint8" }
186
+ ], "outputs": [{ "type": "bool", "name": "success" }]
187
+ }
188
+ ]);
189
+ // Ensure we don't carry static args in safe mode
190
+ jobInput.arguments = undefined;
191
+ }
192
+ // 0. Validate user input thoroughly before proceeding (after safe overrides)
193
+ try {
194
+ const argValue = jobInput.argType;
195
+ (0, validation_1.validateJobInput)(jobInput, argValue);
196
+ console.log('Job input validated successfully');
197
+ }
198
+ catch (err) {
199
+ if (err instanceof errors_1.ValidationError) {
200
+ return (0, errors_1.createErrorResponse)(err);
201
+ }
202
+ return (0, errors_1.createErrorResponse)(err, 'Job input validation failed');
133
203
  }
134
- let jobTitle, timeFrame, targetContractAddress, jobType;
204
+ let jobTitle = '';
205
+ let timeFrame = 0;
206
+ let targetContractAddress = '';
207
+ let jobType = 0;
135
208
  if ('jobTitle' in jobInput)
136
209
  jobTitle = jobInput.jobTitle;
137
210
  if ('timeFrame' in jobInput)
138
211
  timeFrame = jobInput.timeFrame;
139
212
  if ('targetContractAddress' in jobInput)
140
- targetContractAddress = jobInput.targetContractAddress;
213
+ targetContractAddress = jobInput.targetContractAddress || '';
141
214
  // Validate schedule-specific fields for time-based jobs
142
215
  if ('scheduleType' in jobInput) {
143
216
  if (jobInput.scheduleType === 'interval' && (jobInput.timeInterval === undefined || jobInput.timeInterval === null)) {
144
- throw new Error('timeInterval is required when scheduleType is interval');
217
+ return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('timeInterval', 'timeInterval is required when scheduleType is interval'), 'Validation error');
145
218
  }
146
219
  if (jobInput.scheduleType === 'cron' && !jobInput.cronExpression) {
147
- throw new Error('cronExpression is required when scheduleType is cron');
220
+ return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('cronExpression', 'cronExpression is required when scheduleType is cron'), 'Validation error');
148
221
  }
149
222
  if (jobInput.scheduleType === 'specific' && !jobInput.specificSchedule) {
150
- throw new Error('specificSchedule is required when scheduleType is specific');
223
+ return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('specificSchedule', 'specificSchedule is required when scheduleType is specific'), 'Validation error');
151
224
  }
152
225
  }
153
226
  // Infer jobType from jobInput
@@ -216,7 +289,7 @@ async function createJob(client, params) {
216
289
  // Dynamic: call backend API to get fee
217
290
  const ipfs_url = jobInput.dynamicArgumentsScriptUrl;
218
291
  if (!ipfs_url) {
219
- throw new Error('dynamicArgumentsScriptUrl is required for dynamic argType');
292
+ return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('dynamicArgumentsScriptUrl', 'dynamicArgumentsScriptUrl is required for dynamic argType'), 'Validation error');
220
293
  }
221
294
  // Call backend API to get fee
222
295
  let fee = 0;
@@ -230,11 +303,13 @@ async function createJob(client, params) {
230
303
  fee = feeRes.data.total_fee;
231
304
  }
232
305
  else {
233
- throw new Error('Invalid response from /api/fees: missing total_fee');
306
+ return (0, errors_1.createErrorResponse)(new errors_1.ApiError('Invalid response from /api/fees: missing total_fee', { response: feeRes }), 'API error');
234
307
  }
235
308
  }
236
309
  catch (err) {
237
- throw new Error('Failed to fetch job cost prediction: ' + err.message);
310
+ const httpStatusCode = (0, errors_1.extractHttpStatusCode)(err);
311
+ const errorCode = (0, errors_1.determineErrorCode)(err, httpStatusCode);
312
+ return (0, errors_1.createErrorResponse)(new errors_1.ApiError('Failed to fetch job cost prediction', { originalError: err, ipfs_url }, httpStatusCode), 'API error');
238
313
  }
239
314
  job_cost_prediction = fee * noOfExecutions;
240
315
  }
@@ -243,18 +318,41 @@ async function createJob(client, params) {
243
318
  // We'll throw an error with the fee and let the caller handle the prompt/confirmation.
244
319
  // If you want to automate, you can add a `proceed` flag to params in the future.
245
320
  // Check if the user has enough TG to cover the job cost prediction
246
- const { tgBalanceWei, tgBalance } = await (0, checkTgBalance_1.checkTgBalance)(signer);
321
+ let tgBalanceWei, tgBalance;
322
+ try {
323
+ const balanceResult = await (0, checkTgBalance_1.checkTgBalance)(signer);
324
+ if (!balanceResult.success || !balanceResult.data) {
325
+ return (0, errors_1.createErrorResponse)(new errors_1.BalanceError('Failed to check TG balance', balanceResult.details), 'Balance check error');
326
+ }
327
+ tgBalanceWei = balanceResult.data.tgBalanceWei;
328
+ tgBalance = balanceResult.data.tgBalance;
329
+ }
330
+ catch (err) {
331
+ return (0, errors_1.createErrorResponse)(new errors_1.BalanceError('Failed to check TG balance', { originalError: err }), 'Balance check error');
332
+ }
247
333
  if (Number(tgBalance) < job_cost_prediction) {
248
334
  // Check if user has enabled auto topup
249
335
  // For each job type, autotopupTG should be present in jobInput
250
336
  const autoTopupTG = jobInput.autotopupTG === true;
251
337
  if (!autoTopupTG) {
252
- throw new Error(`Insufficient TG balance. Job cost prediction is ${job_cost_prediction}. Current TG balance: ${tgBalance}. Please set autotopupTG: true in jobInput.`);
338
+ 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.`, {
339
+ required: job_cost_prediction,
340
+ current: tgBalance,
341
+ autoTopupEnabled: false
342
+ }), 'Insufficient balance');
253
343
  }
254
344
  else {
255
345
  // autotopupTG is true, automatically top up
256
346
  const requiredTG = Math.ceil(job_cost_prediction); // 1 TG = 0.001 ETH
257
- await (0, topupTg_1.topupTg)(requiredTG, signer);
347
+ try {
348
+ const topupResult = await (0, topupTg_1.topupTg)(requiredTG, signer);
349
+ if (!topupResult.success) {
350
+ return (0, errors_1.createErrorResponse)(new errors_1.BalanceError('Failed to top up TG balance', topupResult.details), 'Top-up error');
351
+ }
352
+ }
353
+ catch (err) {
354
+ return (0, errors_1.createErrorResponse)(new errors_1.BalanceError('Failed to top up TG balance', { originalError: err, requiredTG }), 'Top-up error');
355
+ }
258
356
  }
259
357
  }
260
358
  // Compute balances to store with the job
@@ -262,16 +360,22 @@ async function createJob(client, params) {
262
360
  const etherBalance = tokenBalanceWei / 1000n;
263
361
  // Patch jobInput with job_cost_prediction for downstream usage
264
362
  jobInput.jobCostPrediction = job_cost_prediction;
265
- const jobId = await (0, JobRegistry_1.createJobOnChain)({
266
- jobTitle: jobTitle,
267
- jobType,
268
- timeFrame: timeFrame,
269
- targetContractAddress: targetContractAddress,
270
- encodedData: encodedData || '',
271
- contractAddress: JOB_REGISTRY_ADDRESS,
272
- abi: JobRegistry_json_1.default.abi,
273
- signer,
274
- });
363
+ let jobId;
364
+ try {
365
+ jobId = await (0, JobRegistry_1.createJobOnChain)({
366
+ jobTitle: jobTitle,
367
+ jobType,
368
+ timeFrame: timeFrame,
369
+ targetContractAddress: targetContractAddress,
370
+ encodedData: encodedData || '',
371
+ contractAddress: JOB_REGISTRY_ADDRESS,
372
+ abi: JobRegistry_json_1.default.abi,
373
+ signer,
374
+ });
375
+ }
376
+ catch (err) {
377
+ return (0, errors_1.createErrorResponse)(new errors_1.ContractError('Failed to create job on chain', { originalError: err, jobTitle, jobType, timeFrame }), 'Contract error');
378
+ }
275
379
  // 2. Convert input to CreateJobData
276
380
  let jobData;
277
381
  const balances = { etherBalance, tokenBalanceWei };
@@ -300,6 +404,8 @@ async function createJob(client, params) {
300
404
  return { success: true, data: res };
301
405
  }
302
406
  catch (error) {
303
- return { success: false, error: error.message };
407
+ const httpStatusCode = (0, errors_1.extractHttpStatusCode)(error);
408
+ const errorCode = (0, errors_1.determineErrorCode)(error, httpStatusCode);
409
+ return (0, errors_1.createErrorResponse)(new errors_1.ApiError('Failed to create job via API', { originalError: error, jobId }, httpStatusCode), 'API error');
304
410
  }
305
411
  }
@@ -0,0 +1,8 @@
1
+ import { Signer } from 'ethers';
2
+ /**
3
+ * Creates a new Safe wallet for the user on the signer's network.
4
+ * @param signer ethers.Signer (must be connected to the correct network)
5
+ * @returns Promise<string> - the new Safe wallet address
6
+ * @throws If cannot resolve SafeFactory address from config, or provider/chain/network errors.
7
+ */
8
+ export declare function createSafeWallet(signer: Signer): Promise<string>;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSafeWallet = createSafeWallet;
4
+ const config_1 = require("../config");
5
+ const SafeFactory_1 = require("../contracts/safe/SafeFactory");
6
+ const SafeWallet_1 = require("../contracts/safe/SafeWallet");
7
+ /**
8
+ * Creates a new Safe wallet for the user on the signer's network.
9
+ * @param signer ethers.Signer (must be connected to the correct network)
10
+ * @returns Promise<string> - the new Safe wallet address
11
+ * @throws If cannot resolve SafeFactory address from config, or provider/chain/network errors.
12
+ */
13
+ async function createSafeWallet(signer) {
14
+ if (!signer.provider)
15
+ throw new Error('Signer must have a provider');
16
+ const network = await signer.provider.getNetwork();
17
+ if (!network?.chainId)
18
+ throw new Error('Could not get chainId from signer provider');
19
+ const chainId = network.chainId.toString();
20
+ const { safeFactory } = (0, config_1.getChainAddresses)(chainId);
21
+ if (!safeFactory)
22
+ throw new Error(`SafeFactory not configured for chain ${chainId}`);
23
+ const userAddr = await signer.getAddress();
24
+ const safeAddress = await (0, SafeFactory_1.createSafeWalletForUser)(safeFactory, signer, userAddr);
25
+ const { safeModule } = (0, config_1.getChainAddresses)(chainId);
26
+ if (!safeModule) {
27
+ throw new Error(`SafeModule not configured for chain ${chainId}`);
28
+ }
29
+ await (0, SafeWallet_1.enableSafeModule)(safeAddress, signer, safeModule);
30
+ return safeAddress;
31
+ }
@@ -1,2 +1,9 @@
1
1
  import { ethers } from 'ethers';
2
- export declare const topupTg: (tgAmount: number, signer: ethers.Signer) => Promise<any>;
2
+ export declare const topupTg: (tgAmount: number, signer: ethers.Signer) => Promise<{
3
+ success: boolean;
4
+ data?: any;
5
+ error?: string;
6
+ errorCode?: string;
7
+ errorType?: string;
8
+ details?: any;
9
+ }>;
@@ -7,17 +7,45 @@ exports.topupTg = void 0;
7
7
  const ethers_1 = require("ethers");
8
8
  const GasRegistry_json_1 = __importDefault(require("../contracts/abi/GasRegistry.json"));
9
9
  const config_1 = require("../config");
10
+ const errors_1 = require("../utils/errors");
10
11
  const topupTg = async (tgAmount, signer) => {
11
- const network = await signer.provider?.getNetwork();
12
- const chainId = network?.chainId ? network.chainId.toString() : undefined;
13
- const { gasRegistry } = (0, config_1.getChainAddresses)(chainId);
14
- const gasRegistryContractAddress = gasRegistry;
15
- const contract = new ethers_1.ethers.Contract(gasRegistryContractAddress, GasRegistry_json_1.default, signer);
16
- // Each TG costs 0.001 ETH, so calculate the ETH required for the given TG amount
17
- const amountInEthWei = tgAmount;
18
- // const amountInEthWei = ethers.parseEther(amountInEth.toString());
19
- const tx = await contract.purchaseTG(amountInEthWei, { value: amountInEthWei });
20
- await tx.wait();
21
- return tx;
12
+ // Validate inputs
13
+ if (!tgAmount || tgAmount <= 0) {
14
+ return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('tgAmount', 'TG amount must be a positive number'), 'Validation error');
15
+ }
16
+ if (!signer) {
17
+ return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('signer', 'Signer is required'), 'Validation error');
18
+ }
19
+ try {
20
+ const network = await signer.provider?.getNetwork();
21
+ const chainId = network?.chainId ? network.chainId.toString() : undefined;
22
+ const { gasRegistry } = (0, config_1.getChainAddresses)(chainId);
23
+ const gasRegistryContractAddress = gasRegistry;
24
+ if (!gasRegistryContractAddress) {
25
+ return (0, errors_1.createErrorResponse)(new errors_1.ConfigurationError(`GasRegistry address not configured for chain ID: ${chainId}`), 'Configuration error');
26
+ }
27
+ const contract = new ethers_1.ethers.Contract(gasRegistryContractAddress, GasRegistry_json_1.default, signer);
28
+ // Each TG costs 0.001 ETH, so calculate the ETH required for the given TG amount
29
+ const amountInEthWei = tgAmount;
30
+ // const amountInEthWei = ethers.parseEther(amountInEth.toString());
31
+ const tx = await contract.purchaseTG(amountInEthWei, { value: amountInEthWei });
32
+ await tx.wait();
33
+ return { success: true, data: tx };
34
+ }
35
+ catch (error) {
36
+ console.error('Error topping up TG:', error);
37
+ if (error instanceof Error) {
38
+ if (error.message.includes('network') || error.message.includes('timeout')) {
39
+ return (0, errors_1.createErrorResponse)(new errors_1.NetworkError('Network error during TG top-up', { originalError: error, tgAmount }), 'Network error');
40
+ }
41
+ else if (error.message.includes('contract') || error.message.includes('transaction')) {
42
+ return (0, errors_1.createErrorResponse)(new errors_1.ContractError('Contract error during TG top-up', { originalError: error, tgAmount }), 'Contract error');
43
+ }
44
+ else if (error.message.includes('insufficient funds') || error.message.includes('balance')) {
45
+ return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('balance', 'Insufficient funds for TG top-up', { originalError: error, tgAmount }), 'Validation error');
46
+ }
47
+ }
48
+ return (0, errors_1.createErrorResponse)(error, 'Failed to top up TG');
49
+ }
22
50
  };
23
51
  exports.topupTg = topupTg;
@@ -3,6 +3,13 @@ import { ethers } from 'ethers';
3
3
  * Withdraw ETH in exchange for TG tokens.
4
4
  * @param signer ethers.Signer instance
5
5
  * @param amountTG The amount of TG tokens to withdraw (as a string or BigNumberish)
6
- * @returns The transaction object
6
+ * @returns The transaction object or error response
7
7
  */
8
- export declare const withdrawTg: (signer: ethers.Signer, amountTG: string | ethers.BigNumberish) => Promise<any>;
8
+ export declare const withdrawTg: (signer: ethers.Signer, amountTG: string | ethers.BigNumberish) => Promise<{
9
+ success: boolean;
10
+ data?: any;
11
+ error?: string;
12
+ errorCode?: string;
13
+ errorType?: string;
14
+ details?: any;
15
+ }>;
@@ -7,25 +7,50 @@ exports.withdrawTg = void 0;
7
7
  const ethers_1 = require("ethers");
8
8
  const GasRegistry_json_1 = __importDefault(require("../contracts/abi/GasRegistry.json"));
9
9
  const config_1 = require("../config");
10
+ const errors_1 = require("../utils/errors");
10
11
  /**
11
12
  * Withdraw ETH in exchange for TG tokens.
12
13
  * @param signer ethers.Signer instance
13
14
  * @param amountTG The amount of TG tokens to withdraw (as a string or BigNumberish)
14
- * @returns The transaction object
15
+ * @returns The transaction object or error response
15
16
  */
16
17
  const withdrawTg = async (signer, amountTG) => {
17
- const network = await signer.provider?.getNetwork();
18
- const chainId = network?.chainId ? network.chainId.toString() : undefined;
19
- const { gasRegistry } = (0, config_1.getChainAddresses)(chainId);
20
- const gasRegistryContractAddress = gasRegistry;
21
- if (!gasRegistryContractAddress) {
22
- throw new Error('GasRegistry address not configured for this chain. Update config mapping.');
18
+ // Validate inputs
19
+ if (!signer) {
20
+ return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('signer', 'Signer is required'), 'Validation error');
21
+ }
22
+ if (!amountTG || (typeof amountTG === 'string' && amountTG.trim() === '') || Number(amountTG) <= 0) {
23
+ return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('amountTG', 'Amount must be a positive number'), 'Validation error');
24
+ }
25
+ try {
26
+ const network = await signer.provider?.getNetwork();
27
+ const chainId = network?.chainId ? network.chainId.toString() : undefined;
28
+ const { gasRegistry } = (0, config_1.getChainAddresses)(chainId);
29
+ const gasRegistryContractAddress = gasRegistry;
30
+ if (!gasRegistryContractAddress) {
31
+ return (0, errors_1.createErrorResponse)(new errors_1.ConfigurationError(`GasRegistry address not configured for chain ID: ${chainId}`), 'Configuration error');
32
+ }
33
+ const contract = new ethers_1.ethers.Contract(gasRegistryContractAddress, GasRegistry_json_1.default, signer);
34
+ // Assumes the contract has a function: claimEthForTg(uint256 amount)
35
+ const amountTGWei = ethers_1.ethers.parseEther(amountTG.toString());
36
+ const tx = await contract.claimETHForTG(amountTGWei);
37
+ await tx.wait();
38
+ return { success: true, data: tx };
39
+ }
40
+ catch (error) {
41
+ console.error('Error withdrawing TG:', error);
42
+ if (error instanceof Error) {
43
+ if (error.message.includes('network') || error.message.includes('timeout')) {
44
+ return (0, errors_1.createErrorResponse)(new errors_1.NetworkError('Network error during TG withdrawal', { originalError: error, amountTG }), 'Network error');
45
+ }
46
+ else if (error.message.includes('contract') || error.message.includes('transaction')) {
47
+ return (0, errors_1.createErrorResponse)(new errors_1.ContractError('Contract error during TG withdrawal', { originalError: error, amountTG }), 'Contract error');
48
+ }
49
+ else if (error.message.includes('insufficient') || error.message.includes('balance')) {
50
+ return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('balance', 'Insufficient TG balance for withdrawal', { originalError: error, amountTG }), 'Validation error');
51
+ }
52
+ }
53
+ return (0, errors_1.createErrorResponse)(error, 'Failed to withdraw TG');
23
54
  }
24
- const contract = new ethers_1.ethers.Contract(gasRegistryContractAddress, GasRegistry_json_1.default, signer);
25
- // Assumes the contract has a function: claimEthForTg(uint256 amount)
26
- const amountTGWei = ethers_1.ethers.parseEther(amountTG.toString());
27
- const tx = await contract.claimETHForTG(amountTGWei);
28
- await tx.wait();
29
- return tx;
30
55
  };
31
56
  exports.withdrawTg = withdrawTg;
package/dist/config.d.ts CHANGED
@@ -5,9 +5,15 @@ export interface SDKConfig {
5
5
  export declare const CONTRACT_ADDRESSES_BY_CHAIN: Record<string, {
6
6
  gasRegistry: string;
7
7
  jobRegistry: string;
8
+ safeFactory?: string;
9
+ safeModule?: string;
10
+ safeSingleton?: string;
8
11
  }>;
9
12
  export declare function getConfig(): SDKConfig;
10
13
  export declare function getChainAddresses(chainId: string | number | undefined): {
11
14
  gasRegistry: string;
12
15
  jobRegistry: string;
16
+ safeFactory: string | undefined;
17
+ safeModule: string | undefined;
18
+ safeSingleton: string | undefined;
13
19
  };
package/dist/config.js CHANGED
@@ -23,6 +23,9 @@ exports.CONTRACT_ADDRESSES_BY_CHAIN = {
23
23
  '421614': {
24
24
  gasRegistry: '0x664CB20BCEEc9416D290AC820e5446e61a5c75e4',
25
25
  jobRegistry: '0x476ACc7949a95e31144cC84b8F6BC7abF0967E4b',
26
+ safeFactory: '0x383D4a61D0B069D02cA2Db5A82003b9561d56e19',
27
+ safeModule: '0xca3a0c43Ac9E4FcB76C774F330fD31D4098EEacD',
28
+ // safeSingleton can be provided per deployment (Safe or SafeL2)
26
29
  },
27
30
  // Base Sepolia (84532) - Base Sepolia Testnet
28
31
  '84532': {
@@ -49,5 +52,8 @@ function getChainAddresses(chainId) {
49
52
  return {
50
53
  gasRegistry: mapped ? mapped.gasRegistry : '',
51
54
  jobRegistry: mapped ? mapped.jobRegistry : '',
55
+ safeFactory: mapped ? mapped.safeFactory : '',
56
+ safeModule: mapped ? mapped.safeModule : '',
57
+ safeSingleton: mapped ? mapped.safeSingleton : '',
52
58
  };
53
59
  }
@@ -1 +1,4 @@
1
+ export * from './JobRegistry';
2
+ export * from './safe/SafeFactory';
3
+ export * from './safe/SafeWallet';
1
4
  export * from './TriggerXContract';
@@ -14,4 +14,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./JobRegistry"), exports);
18
+ // Safe-related helpers
19
+ __exportStar(require("./safe/SafeFactory"), exports);
20
+ __exportStar(require("./safe/SafeWallet"), exports);
17
21
  __exportStar(require("./TriggerXContract"), exports);
@@ -0,0 +1,3 @@
1
+ import { Signer } from 'ethers';
2
+ export declare const TRIGGERX_SAFE_FACTORY_ABI: string[];
3
+ export declare function createSafeWalletForUser(factoryAddress: string, signer: Signer, user: string): Promise<string>;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TRIGGERX_SAFE_FACTORY_ABI = void 0;
4
+ exports.createSafeWalletForUser = createSafeWalletForUser;
5
+ const ethers_1 = require("ethers");
6
+ // Minimal ABI for TriggerXSafeFactory
7
+ exports.TRIGGERX_SAFE_FACTORY_ABI = [
8
+ "function createSafeWallet(address user) returns (address)",
9
+ "function latestSafeWallet(address user) view returns (address)",
10
+ "function getSafeWallets(address user) view returns (address[])",
11
+ "function predictSafeAddress(address user) view returns (address)",
12
+ "event SafeWalletCreated(address indexed user, address indexed safeWallet, uint256 saltNonce)"
13
+ ];
14
+ async function createSafeWalletForUser(factoryAddress, signer, user) {
15
+ const factory = new ethers_1.ethers.Contract(factoryAddress, exports.TRIGGERX_SAFE_FACTORY_ABI, signer);
16
+ const tx = await factory.createSafeWallet(user);
17
+ const receipt = await tx.wait();
18
+ // Try to fetch from event; fallback to latestSafeWallet
19
+ const evt = receipt.logs
20
+ .map((l) => {
21
+ try {
22
+ return factory.interface.parseLog(l);
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ })
28
+ .find((e) => e && e.name === 'SafeWalletCreated');
29
+ if (evt && evt.args && evt.args.safeWallet) {
30
+ return evt.args.safeWallet;
31
+ }
32
+ return await factory.latestSafeWallet(user);
33
+ }
@@ -0,0 +1,6 @@
1
+ import { ethers, Signer } from 'ethers';
2
+ export declare const SAFE_ABI: string[];
3
+ export declare const SAFE_TX_TYPEHASH: string;
4
+ export declare function isSafeModuleEnabled(safeAddress: string, provider: ethers.Provider, moduleAddress: string): Promise<boolean>;
5
+ export declare function ensureSingleOwnerAndMatchSigner(safeAddress: string, provider: ethers.Provider, signerAddress: string): Promise<void>;
6
+ export declare function enableSafeModule(safeAddress: string, signer: Signer, moduleAddress: string): Promise<void>;