sdk-triggerx 0.1.17 → 0.1.19
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 +95 -0
- package/dist/api/checkTgBalance.d.ts +9 -2
- package/dist/api/checkTgBalance.js +43 -24
- package/dist/api/deleteJob.d.ts +7 -1
- package/dist/api/deleteJob.js +29 -3
- package/dist/api/getJobDataById.d.ts +8 -1
- package/dist/api/getJobDataById.js +27 -3
- package/dist/api/getUserData.d.ts +8 -1
- package/dist/api/getUserData.js +27 -3
- package/dist/api/getjob.js +20 -2
- package/dist/api/jobs.js +134 -84
- package/dist/api/safeWallet.d.ts +8 -0
- package/dist/api/safeWallet.js +31 -0
- package/dist/api/topupTg.d.ts +8 -1
- package/dist/api/topupTg.js +39 -11
- package/dist/api/withdrawTg.d.ts +9 -2
- package/dist/api/withdrawTg.js +38 -13
- package/dist/config.js +8 -2
- package/dist/contracts/safe/SafeWallet.js +44 -24
- package/dist/types.d.ts +47 -12
- package/dist/utils/errors.d.ts +39 -4
- package/dist/utils/errors.js +222 -7
- package/dist/utils/validation.js +18 -6
- package/package.json +3 -3
package/dist/api/jobs.js
CHANGED
|
@@ -16,7 +16,6 @@ const config_1 = require("../config");
|
|
|
16
16
|
const validation_1 = require("../utils/validation");
|
|
17
17
|
const errors_1 = require("../utils/errors");
|
|
18
18
|
const SafeWallet_1 = require("../contracts/safe/SafeWallet");
|
|
19
|
-
const SafeFactory_1 = require("../contracts/safe/SafeFactory");
|
|
20
19
|
const JOB_ID = '300949528249665590178224313442040528409305273634097553067152835846309150732';
|
|
21
20
|
const DYNAMIC_ARGS_URL = 'https://teal-random-koala-993.mypinata.cloud/ipfs/bafkreif426p7t7takzhw3g6we2h6wsvf27p5jxj3gaiynqf22p3jvhx4la';
|
|
22
21
|
function toCreateJobDataFromTime(input, balances, userAddress, jobCostPrediction) {
|
|
@@ -38,9 +37,9 @@ function toCreateJobDataFromTime(input, balances, userAddress, jobCostPrediction
|
|
|
38
37
|
cron_expression: input.scheduleType === 'cron' ? input.cronExpression : undefined,
|
|
39
38
|
specific_schedule: input.scheduleType === 'specific' ? input.specificSchedule : undefined,
|
|
40
39
|
target_chain_id: input.chainId,
|
|
41
|
-
target_contract_address: input.targetContractAddress,
|
|
42
|
-
target_function: input.targetFunction,
|
|
43
|
-
abi: input.abi,
|
|
40
|
+
target_contract_address: input.targetContractAddress || '',
|
|
41
|
+
target_function: input.targetFunction || '',
|
|
42
|
+
abi: input.abi || '',
|
|
44
43
|
arg_type: input.dynamicArgumentsScriptUrl ? 2 : 1,
|
|
45
44
|
arguments: input.arguments,
|
|
46
45
|
dynamic_arguments_script_url: input.dynamicArgumentsScriptUrl,
|
|
@@ -65,9 +64,9 @@ function toCreateJobDataFromEvent(input, balances, userAddress, jobCostPredictio
|
|
|
65
64
|
trigger_contract_address: input.triggerContractAddress,
|
|
66
65
|
trigger_event: input.triggerEvent,
|
|
67
66
|
target_chain_id: input.chainId,
|
|
68
|
-
target_contract_address: input.targetContractAddress,
|
|
69
|
-
target_function: input.targetFunction,
|
|
70
|
-
abi: input.abi,
|
|
67
|
+
target_contract_address: input.targetContractAddress || '',
|
|
68
|
+
target_function: input.targetFunction || '',
|
|
69
|
+
abi: input.abi || '',
|
|
71
70
|
arg_type: input.dynamicArgumentsScriptUrl ? 2 : 1,
|
|
72
71
|
arguments: input.arguments,
|
|
73
72
|
dynamic_arguments_script_url: input.dynamicArgumentsScriptUrl,
|
|
@@ -94,9 +93,9 @@ function toCreateJobDataFromCondition(input, balances, userAddress, jobCostPredi
|
|
|
94
93
|
value_source_type: input.valueSourceType,
|
|
95
94
|
value_source_url: input.valueSourceUrl,
|
|
96
95
|
target_chain_id: input.chainId,
|
|
97
|
-
target_contract_address: input.targetContractAddress,
|
|
98
|
-
target_function: input.targetFunction,
|
|
99
|
-
abi: input.abi,
|
|
96
|
+
target_contract_address: input.targetContractAddress || '',
|
|
97
|
+
target_function: input.targetFunction || '',
|
|
98
|
+
abi: input.abi || '',
|
|
100
99
|
arg_type: input.dynamicArgumentsScriptUrl ? 2 : 1,
|
|
101
100
|
arguments: input.arguments,
|
|
102
101
|
dynamic_arguments_script_url: input.dynamicArgumentsScriptUrl,
|
|
@@ -124,46 +123,104 @@ function encodeJobType4or6Data(recurringJob, ipfsHash) {
|
|
|
124
123
|
*/
|
|
125
124
|
async function createJob(client, params) {
|
|
126
125
|
let { jobInput, signer, encodedData } = params;
|
|
127
|
-
//
|
|
126
|
+
// Use the API key from the client instance
|
|
127
|
+
const apiKey = client.getApiKey();
|
|
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;
|
|
128
132
|
try {
|
|
129
|
-
|
|
130
|
-
(0, validation_1.validateJobInput)(jobInput, argValue);
|
|
131
|
-
console.log('Job input validated successfully');
|
|
133
|
+
userAddress = await signer.getAddress();
|
|
132
134
|
}
|
|
133
135
|
catch (err) {
|
|
134
|
-
|
|
135
|
-
return { success: false, error: `${err.field}: ${err.message}` };
|
|
136
|
-
}
|
|
137
|
-
return { success: false, error: err.message };
|
|
136
|
+
return (0, errors_1.createErrorResponse)(new errors_1.AuthenticationError('Failed to get signer address', { originalError: err }), 'Authentication failed');
|
|
138
137
|
}
|
|
139
|
-
// Use the API key from the client instance
|
|
140
|
-
const apiKey = client.getApiKey();
|
|
141
|
-
const userAddress = await signer.getAddress();
|
|
142
138
|
// Resolve chain-specific addresses
|
|
143
|
-
|
|
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
|
+
}
|
|
144
146
|
const chainIdStr = network?.chainId ? network.chainId.toString() : undefined;
|
|
145
147
|
const { jobRegistry, safeModule, safeFactory } = (0, config_1.getChainAddresses)(chainIdStr);
|
|
146
148
|
const JOB_REGISTRY_ADDRESS = jobRegistry;
|
|
147
149
|
if (!JOB_REGISTRY_ADDRESS) {
|
|
148
|
-
return
|
|
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;
|
|
149
191
|
}
|
|
150
|
-
|
|
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');
|
|
203
|
+
}
|
|
204
|
+
let jobTitle = '';
|
|
205
|
+
let timeFrame = 0;
|
|
206
|
+
let targetContractAddress = '';
|
|
207
|
+
let jobType = 0;
|
|
151
208
|
if ('jobTitle' in jobInput)
|
|
152
209
|
jobTitle = jobInput.jobTitle;
|
|
153
210
|
if ('timeFrame' in jobInput)
|
|
154
211
|
timeFrame = jobInput.timeFrame;
|
|
155
212
|
if ('targetContractAddress' in jobInput)
|
|
156
|
-
targetContractAddress = jobInput.targetContractAddress;
|
|
213
|
+
targetContractAddress = jobInput.targetContractAddress || '';
|
|
157
214
|
// Validate schedule-specific fields for time-based jobs
|
|
158
215
|
if ('scheduleType' in jobInput) {
|
|
159
216
|
if (jobInput.scheduleType === 'interval' && (jobInput.timeInterval === undefined || jobInput.timeInterval === null)) {
|
|
160
|
-
|
|
217
|
+
return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('timeInterval', 'timeInterval is required when scheduleType is interval'), 'Validation error');
|
|
161
218
|
}
|
|
162
219
|
if (jobInput.scheduleType === 'cron' && !jobInput.cronExpression) {
|
|
163
|
-
|
|
220
|
+
return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('cronExpression', 'cronExpression is required when scheduleType is cron'), 'Validation error');
|
|
164
221
|
}
|
|
165
222
|
if (jobInput.scheduleType === 'specific' && !jobInput.specificSchedule) {
|
|
166
|
-
|
|
223
|
+
return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('specificSchedule', 'specificSchedule is required when scheduleType is specific'), 'Validation error');
|
|
167
224
|
}
|
|
168
225
|
}
|
|
169
226
|
// Infer jobType from jobInput
|
|
@@ -232,7 +289,7 @@ async function createJob(client, params) {
|
|
|
232
289
|
// Dynamic: call backend API to get fee
|
|
233
290
|
const ipfs_url = jobInput.dynamicArgumentsScriptUrl;
|
|
234
291
|
if (!ipfs_url) {
|
|
235
|
-
|
|
292
|
+
return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('dynamicArgumentsScriptUrl', 'dynamicArgumentsScriptUrl is required for dynamic argType'), 'Validation error');
|
|
236
293
|
}
|
|
237
294
|
// Call backend API to get fee
|
|
238
295
|
let fee = 0;
|
|
@@ -246,11 +303,13 @@ async function createJob(client, params) {
|
|
|
246
303
|
fee = feeRes.data.total_fee;
|
|
247
304
|
}
|
|
248
305
|
else {
|
|
249
|
-
|
|
306
|
+
return (0, errors_1.createErrorResponse)(new errors_1.ApiError('Invalid response from /api/fees: missing total_fee', { response: feeRes }), 'API error');
|
|
250
307
|
}
|
|
251
308
|
}
|
|
252
309
|
catch (err) {
|
|
253
|
-
|
|
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');
|
|
254
313
|
}
|
|
255
314
|
job_cost_prediction = fee * noOfExecutions;
|
|
256
315
|
}
|
|
@@ -259,18 +318,41 @@ async function createJob(client, params) {
|
|
|
259
318
|
// We'll throw an error with the fee and let the caller handle the prompt/confirmation.
|
|
260
319
|
// If you want to automate, you can add a `proceed` flag to params in the future.
|
|
261
320
|
// Check if the user has enough TG to cover the job cost prediction
|
|
262
|
-
|
|
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
|
+
}
|
|
263
333
|
if (Number(tgBalance) < job_cost_prediction) {
|
|
264
334
|
// Check if user has enabled auto topup
|
|
265
335
|
// For each job type, autotopupTG should be present in jobInput
|
|
266
336
|
const autoTopupTG = jobInput.autotopupTG === true;
|
|
267
337
|
if (!autoTopupTG) {
|
|
268
|
-
|
|
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');
|
|
269
343
|
}
|
|
270
344
|
else {
|
|
271
345
|
// autotopupTG is true, automatically top up
|
|
272
346
|
const requiredTG = Math.ceil(job_cost_prediction); // 1 TG = 0.001 ETH
|
|
273
|
-
|
|
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
|
+
}
|
|
274
356
|
}
|
|
275
357
|
}
|
|
276
358
|
// Compute balances to store with the job
|
|
@@ -278,56 +360,22 @@ async function createJob(client, params) {
|
|
|
278
360
|
const etherBalance = tokenBalanceWei / 1000n;
|
|
279
361
|
// Patch jobInput with job_cost_prediction for downstream usage
|
|
280
362
|
jobInput.jobCostPrediction = job_cost_prediction;
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if (!safeFactory) {
|
|
297
|
-
return { success: false, error: 'Safe Factory address not configured for this chain.' };
|
|
298
|
-
}
|
|
299
|
-
safeAddressToUse = await (0, SafeFactory_1.createSafeWalletForUser)(safeFactory, signer, userAddress);
|
|
300
|
-
}
|
|
301
|
-
// Validate Safe has single owner and owner matches signer
|
|
302
|
-
await (0, SafeWallet_1.ensureSingleOwnerAndMatchSigner)(safeAddressToUse, signer.provider, userAddress);
|
|
303
|
-
// Ensure module is enabled on Safe
|
|
304
|
-
await (0, SafeWallet_1.enableSafeModule)(safeAddressToUse, signer, safeModule);
|
|
305
|
-
// Override target for job to Safe Module and function to execJobFromHub
|
|
306
|
-
targetContractAddress = safeModule; // ensure target contract is SAFE_MODULE_ADDRESS only
|
|
307
|
-
jobInput.targetContractAddress = safeModule;
|
|
308
|
-
jobInput.targetFunction = 'execJobFromHub(address,address,uint256,bytes,uint8)';
|
|
309
|
-
jobInput.abi = JSON.stringify([
|
|
310
|
-
{ "type": "function", "name": "execJobFromHub", "stateMutability": "nonpayable", "inputs": [
|
|
311
|
-
{ "name": "safeAddress", "type": "address" },
|
|
312
|
-
{ "name": "actionTarget", "type": "address" },
|
|
313
|
-
{ "name": "actionValue", "type": "uint256" },
|
|
314
|
-
{ "name": "actionData", "type": "bytes" },
|
|
315
|
-
{ "name": "operation", "type": "uint8" }
|
|
316
|
-
], "outputs": [{ "type": "bool", "name": "success" }] }
|
|
317
|
-
]);
|
|
318
|
-
// Note: Parameters will be dynamically provided via IPFS script; do not set static arguments here
|
|
319
|
-
jobInput.arguments = undefined;
|
|
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');
|
|
320
378
|
}
|
|
321
|
-
const jobId = await (0, JobRegistry_1.createJobOnChain)({
|
|
322
|
-
jobTitle: jobTitle,
|
|
323
|
-
jobType,
|
|
324
|
-
timeFrame: timeFrame,
|
|
325
|
-
targetContractAddress: targetContractAddress,
|
|
326
|
-
encodedData: encodedData || '',
|
|
327
|
-
contractAddress: JOB_REGISTRY_ADDRESS,
|
|
328
|
-
abi: JobRegistry_json_1.default.abi,
|
|
329
|
-
signer,
|
|
330
|
-
});
|
|
331
379
|
// 2. Convert input to CreateJobData
|
|
332
380
|
let jobData;
|
|
333
381
|
const balances = { etherBalance, tokenBalanceWei };
|
|
@@ -356,6 +404,8 @@ async function createJob(client, params) {
|
|
|
356
404
|
return { success: true, data: res };
|
|
357
405
|
}
|
|
358
406
|
catch (error) {
|
|
359
|
-
|
|
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');
|
|
360
410
|
}
|
|
361
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
|
+
}
|
package/dist/api/topupTg.d.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
import { ethers } from 'ethers';
|
|
2
|
-
export declare const topupTg: (tgAmount: number, signer: ethers.Signer) => Promise<
|
|
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
|
+
}>;
|
package/dist/api/topupTg.js
CHANGED
|
@@ -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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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;
|
package/dist/api/withdrawTg.d.ts
CHANGED
|
@@ -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<
|
|
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
|
+
}>;
|
package/dist/api/withdrawTg.js
CHANGED
|
@@ -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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (!
|
|
22
|
-
|
|
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.js
CHANGED
|
@@ -13,24 +13,30 @@ exports.CONTRACT_ADDRESSES_BY_CHAIN = {
|
|
|
13
13
|
'11155420': {
|
|
14
14
|
gasRegistry: '0x664CB20BCEEc9416D290AC820e5446e61a5c75e4',
|
|
15
15
|
jobRegistry: '0x476ACc7949a95e31144cC84b8F6BC7abF0967E4b',
|
|
16
|
+
safeFactory: '0x04359eDC46Cd6C6BD7F6359512984222BE10F8Be',
|
|
17
|
+
safeModule: '0xa0bC1477cfc452C05786262c377DE51FB8bc4669',
|
|
16
18
|
},
|
|
17
19
|
// Ethereum Sepolia (11155111) - Ethereum Sepolia Testnet
|
|
18
20
|
'11155111': {
|
|
19
21
|
gasRegistry: '0x664CB20BCEEc9416D290AC820e5446e61a5c75e4',
|
|
20
22
|
jobRegistry: '0x476ACc7949a95e31144cC84b8F6BC7abF0967E4b',
|
|
23
|
+
safeFactory: '0xdf76E2A796a206D877086c717979054544B1D9Bc',
|
|
24
|
+
safeModule: '0xa0bC1477cfc452C05786262c377DE51FB8bc4669',
|
|
21
25
|
},
|
|
22
26
|
// Arbitrum Sepolia (421614) - Arbitrum Sepolia Testnet
|
|
23
27
|
'421614': {
|
|
24
28
|
gasRegistry: '0x664CB20BCEEc9416D290AC820e5446e61a5c75e4',
|
|
25
29
|
jobRegistry: '0x476ACc7949a95e31144cC84b8F6BC7abF0967E4b',
|
|
26
|
-
safeFactory: '
|
|
27
|
-
safeModule: '
|
|
30
|
+
safeFactory: '0x04359eDC46Cd6C6BD7F6359512984222BE10F8Be',
|
|
31
|
+
safeModule: '0xa0bC1477cfc452C05786262c377DE51FB8bc4669',
|
|
28
32
|
// safeSingleton can be provided per deployment (Safe or SafeL2)
|
|
29
33
|
},
|
|
30
34
|
// Base Sepolia (84532) - Base Sepolia Testnet
|
|
31
35
|
'84532': {
|
|
32
36
|
gasRegistry: '0x664CB20BCEEc9416D290AC820e5446e61a5c75e4',
|
|
33
37
|
jobRegistry: '0x476ACc7949a95e31144cC84b8F6BC7abF0967E4b',
|
|
38
|
+
safeFactory: '0x04359eDC46Cd6C6BD7F6359512984222BE10F8Be',
|
|
39
|
+
safeModule: '0xa0bC1477cfc452C05786262c377DE51FB8bc4669',
|
|
34
40
|
},
|
|
35
41
|
// MAINNET CONFIGURATIONS
|
|
36
42
|
// Arbitrum One (42161) - Mainnet
|