sdk-triggerx 0.1.15 → 0.1.17
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/deleteJob.d.ts +2 -1
- package/dist/api/deleteJob.js +23 -1
- package/dist/api/jobs.js +57 -1
- package/dist/client.js +1 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.js +26 -3
- package/dist/contracts/JobRegistry.d.ts +7 -0
- package/dist/contracts/JobRegistry.js +6 -0
- package/dist/contracts/index.d.ts +3 -0
- package/dist/contracts/index.js +4 -0
- package/dist/contracts/safe/SafeFactory.d.ts +3 -0
- package/dist/contracts/safe/SafeFactory.js +33 -0
- package/dist/contracts/safe/SafeWallet.d.ts +6 -0
- package/dist/contracts/safe/SafeWallet.js +89 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +7 -3
- package/dist/types.d.ts +7 -0
- package/dist/utils/errors.d.ts +4 -0
- package/dist/utils/errors.js +9 -1
- package/dist/utils/validation.d.ts +5 -0
- package/dist/utils/validation.js +200 -0
- package/package.json +1 -1
package/dist/api/deleteJob.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import { TriggerXClient } from '../client';
|
|
2
|
-
|
|
2
|
+
import { Signer } from 'ethers';
|
|
3
|
+
export declare const deleteJob: (client: TriggerXClient, jobId: string, signer: Signer, chainId: string) => Promise<void>;
|
package/dist/api/deleteJob.js
CHANGED
|
@@ -1,16 +1,38 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// sdk-triggerx/src/api/deleteJob.ts
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
3
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
7
|
exports.deleteJob = void 0;
|
|
5
|
-
const
|
|
8
|
+
const JobRegistry_json_1 = __importDefault(require("../contracts/abi/JobRegistry.json"));
|
|
9
|
+
const JobRegistry_1 = require("../contracts/JobRegistry");
|
|
10
|
+
const config_1 = require("../config");
|
|
11
|
+
const deleteJob = async (client, jobId, signer, chainId) => {
|
|
6
12
|
const apiKey = client.getApiKey(); // Assuming you have a method to get the API key
|
|
13
|
+
const { jobRegistry: jobRegistryAddress } = config_1.CONTRACT_ADDRESSES_BY_CHAIN[chainId];
|
|
14
|
+
if (!jobRegistryAddress) {
|
|
15
|
+
throw new Error(`No contract address found for chain ID: ${chainId}`);
|
|
16
|
+
}
|
|
7
17
|
try {
|
|
18
|
+
console.log(`Deleting job ${jobId} on chain ${chainId}...`);
|
|
19
|
+
// First delete on-chain
|
|
20
|
+
await (0, JobRegistry_1.deleteJobOnChain)({
|
|
21
|
+
jobId,
|
|
22
|
+
contractAddress: jobRegistryAddress,
|
|
23
|
+
abi: JobRegistry_json_1.default,
|
|
24
|
+
signer,
|
|
25
|
+
});
|
|
26
|
+
console.log('On-chain deletion successful, updating API...');
|
|
27
|
+
// Then update the API
|
|
8
28
|
await client.put(`api/jobs/delete/${jobId}`, {}, {
|
|
9
29
|
headers: {
|
|
10
30
|
'Content-Type': 'application/json',
|
|
11
31
|
'X-API-KEY': apiKey,
|
|
12
32
|
},
|
|
33
|
+
timeout: 30000, // 30 second timeout
|
|
13
34
|
});
|
|
35
|
+
console.log('API update successful');
|
|
14
36
|
}
|
|
15
37
|
catch (error) {
|
|
16
38
|
console.error('Error deleting job:', error);
|
package/dist/api/jobs.js
CHANGED
|
@@ -13,6 +13,10 @@ 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");
|
|
19
|
+
const SafeFactory_1 = require("../contracts/safe/SafeFactory");
|
|
16
20
|
const JOB_ID = '300949528249665590178224313442040528409305273634097553067152835846309150732';
|
|
17
21
|
const DYNAMIC_ARGS_URL = 'https://teal-random-koala-993.mypinata.cloud/ipfs/bafkreif426p7t7takzhw3g6we2h6wsvf27p5jxj3gaiynqf22p3jvhx4la';
|
|
18
22
|
function toCreateJobDataFromTime(input, balances, userAddress, jobCostPrediction) {
|
|
@@ -120,13 +124,25 @@ function encodeJobType4or6Data(recurringJob, ipfsHash) {
|
|
|
120
124
|
*/
|
|
121
125
|
async function createJob(client, params) {
|
|
122
126
|
let { jobInput, signer, encodedData } = params;
|
|
127
|
+
// 0. Validate user input thoroughly before proceeding
|
|
128
|
+
try {
|
|
129
|
+
const argValue = jobInput.argType;
|
|
130
|
+
(0, validation_1.validateJobInput)(jobInput, argValue);
|
|
131
|
+
console.log('Job input validated successfully');
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
if (err instanceof errors_1.ValidationError) {
|
|
135
|
+
return { success: false, error: `${err.field}: ${err.message}` };
|
|
136
|
+
}
|
|
137
|
+
return { success: false, error: err.message };
|
|
138
|
+
}
|
|
123
139
|
// Use the API key from the client instance
|
|
124
140
|
const apiKey = client.getApiKey();
|
|
125
141
|
const userAddress = await signer.getAddress();
|
|
126
142
|
// Resolve chain-specific addresses
|
|
127
143
|
const network = await signer.provider?.getNetwork();
|
|
128
144
|
const chainIdStr = network?.chainId ? network.chainId.toString() : undefined;
|
|
129
|
-
const { jobRegistry } = (0, config_1.getChainAddresses)(chainIdStr);
|
|
145
|
+
const { jobRegistry, safeModule, safeFactory } = (0, config_1.getChainAddresses)(chainIdStr);
|
|
130
146
|
const JOB_REGISTRY_ADDRESS = jobRegistry;
|
|
131
147
|
if (!JOB_REGISTRY_ADDRESS) {
|
|
132
148
|
return { success: false, error: 'JobRegistry address not configured for this chain. Update config mapping.' };
|
|
@@ -262,6 +278,46 @@ async function createJob(client, params) {
|
|
|
262
278
|
const etherBalance = tokenBalanceWei / 1000n;
|
|
263
279
|
// Patch jobInput with job_cost_prediction for downstream usage
|
|
264
280
|
jobInput.jobCostPrediction = job_cost_prediction;
|
|
281
|
+
// --- Safe wallet integration (override target to module when selected) ---
|
|
282
|
+
const walletMode = jobInput.walletMode;
|
|
283
|
+
if (walletMode === 'safe') {
|
|
284
|
+
if (!safeModule) {
|
|
285
|
+
return { success: false, error: 'Safe Module address not configured for this chain.' };
|
|
286
|
+
}
|
|
287
|
+
// Enforce dynamic parameters coming from IPFS only
|
|
288
|
+
const dynUrl = jobInput.dynamicArgumentsScriptUrl;
|
|
289
|
+
if (!dynUrl) {
|
|
290
|
+
return { success: false, error: 'Safe jobs require dynamicArgumentsScriptUrl (IPFS) for parameters.' };
|
|
291
|
+
}
|
|
292
|
+
// Resolve or create Safe wallet
|
|
293
|
+
const providedSafeAddress = jobInput.safeAddress;
|
|
294
|
+
let safeAddressToUse = providedSafeAddress;
|
|
295
|
+
if (!safeAddressToUse) {
|
|
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;
|
|
320
|
+
}
|
|
265
321
|
const jobId = await (0, JobRegistry_1.createJobOnChain)({
|
|
266
322
|
jobTitle: jobTitle,
|
|
267
323
|
jobType,
|
package/dist/client.js
CHANGED
|
@@ -12,6 +12,7 @@ class TriggerXClient {
|
|
|
12
12
|
this.client = axios_1.default.create({
|
|
13
13
|
baseURL: 'https://data.triggerx.network', //'http://localhost:9002', //'https://data.triggerx.network',
|
|
14
14
|
headers: { 'Authorization': `Bearer ${this.apiKey}` }, // Set the API key here
|
|
15
|
+
timeout: 30000, // 30 second timeout
|
|
15
16
|
...config,
|
|
16
17
|
});
|
|
17
18
|
}
|
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
|
@@ -8,12 +8,32 @@ exports.getChainAddresses = getChainAddresses;
|
|
|
8
8
|
// Contract addresses per chain
|
|
9
9
|
// Keyed by chainId as string to avoid bigint conversions throughout the SDK
|
|
10
10
|
exports.CONTRACT_ADDRESSES_BY_CHAIN = {
|
|
11
|
-
//
|
|
11
|
+
// TESTNET CONFIGURATIONS
|
|
12
|
+
// OP Sepolia (11155420) - Optimism Sepolia Testnet
|
|
12
13
|
'11155420': {
|
|
13
|
-
gasRegistry: '
|
|
14
|
+
gasRegistry: '0x664CB20BCEEc9416D290AC820e5446e61a5c75e4',
|
|
14
15
|
jobRegistry: '0x476ACc7949a95e31144cC84b8F6BC7abF0967E4b',
|
|
15
16
|
},
|
|
16
|
-
//
|
|
17
|
+
// Ethereum Sepolia (11155111) - Ethereum Sepolia Testnet
|
|
18
|
+
'11155111': {
|
|
19
|
+
gasRegistry: '0x664CB20BCEEc9416D290AC820e5446e61a5c75e4',
|
|
20
|
+
jobRegistry: '0x476ACc7949a95e31144cC84b8F6BC7abF0967E4b',
|
|
21
|
+
},
|
|
22
|
+
// Arbitrum Sepolia (421614) - Arbitrum Sepolia Testnet
|
|
23
|
+
'421614': {
|
|
24
|
+
gasRegistry: '0x664CB20BCEEc9416D290AC820e5446e61a5c75e4',
|
|
25
|
+
jobRegistry: '0x476ACc7949a95e31144cC84b8F6BC7abF0967E4b',
|
|
26
|
+
safeFactory: '0x383D4a61D0B069D02cA2Db5A82003b9561d56e19',
|
|
27
|
+
safeModule: '0xca3a0c43Ac9E4FcB76C774F330fD31D4098EEacD',
|
|
28
|
+
// safeSingleton can be provided per deployment (Safe or SafeL2)
|
|
29
|
+
},
|
|
30
|
+
// Base Sepolia (84532) - Base Sepolia Testnet
|
|
31
|
+
'84532': {
|
|
32
|
+
gasRegistry: '0x664CB20BCEEc9416D290AC820e5446e61a5c75e4',
|
|
33
|
+
jobRegistry: '0x476ACc7949a95e31144cC84b8F6BC7abF0967E4b',
|
|
34
|
+
},
|
|
35
|
+
// MAINNET CONFIGURATIONS
|
|
36
|
+
// Arbitrum One (42161) - Mainnet
|
|
17
37
|
'42161': {
|
|
18
38
|
gasRegistry: '0x93dDB2307F3Af5df85F361E5Cddd898Acd3d132d',
|
|
19
39
|
jobRegistry: '0xAf1189aFd1F1880F09AeC3Cbc32cf415c735C710',
|
|
@@ -32,5 +52,8 @@ function getChainAddresses(chainId) {
|
|
|
32
52
|
return {
|
|
33
53
|
gasRegistry: mapped ? mapped.gasRegistry : '',
|
|
34
54
|
jobRegistry: mapped ? mapped.jobRegistry : '',
|
|
55
|
+
safeFactory: mapped ? mapped.safeFactory : '',
|
|
56
|
+
safeModule: mapped ? mapped.safeModule : '',
|
|
57
|
+
safeSingleton: mapped ? mapped.safeSingleton : '',
|
|
35
58
|
};
|
|
36
59
|
}
|
|
@@ -9,4 +9,11 @@ export interface CreateJobOnChainParams {
|
|
|
9
9
|
abi: any;
|
|
10
10
|
signer: Signer;
|
|
11
11
|
}
|
|
12
|
+
export interface DeleteJobOnChainParams {
|
|
13
|
+
jobId: string;
|
|
14
|
+
contractAddress: string;
|
|
15
|
+
abi: any;
|
|
16
|
+
signer: Signer;
|
|
17
|
+
}
|
|
12
18
|
export declare function createJobOnChain({ jobTitle, jobType, timeFrame, targetContractAddress, encodedData, contractAddress, abi, signer, }: CreateJobOnChainParams): Promise<string>;
|
|
19
|
+
export declare function deleteJobOnChain({ jobId, contractAddress, abi, signer, }: DeleteJobOnChainParams): Promise<void>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createJobOnChain = createJobOnChain;
|
|
4
|
+
exports.deleteJobOnChain = deleteJobOnChain;
|
|
4
5
|
const ethers_1 = require("ethers");
|
|
5
6
|
async function createJobOnChain({ jobTitle, jobType, timeFrame, targetContractAddress, encodedData, contractAddress, abi, signer, }) {
|
|
6
7
|
const contract = new ethers_1.ethers.Contract(contractAddress, abi, signer);
|
|
@@ -22,3 +23,8 @@ async function createJobOnChain({ jobTitle, jobType, timeFrame, targetContractAd
|
|
|
22
23
|
}
|
|
23
24
|
throw new Error('Job ID not found in contract events');
|
|
24
25
|
}
|
|
26
|
+
async function deleteJobOnChain({ jobId, contractAddress, abi, signer, }) {
|
|
27
|
+
const contract = new ethers_1.ethers.Contract(contractAddress, abi.abi, signer);
|
|
28
|
+
const tx = await contract.deleteJob(jobId);
|
|
29
|
+
await tx.wait();
|
|
30
|
+
}
|
package/dist/contracts/index.js
CHANGED
|
@@ -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,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>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SAFE_TX_TYPEHASH = exports.SAFE_ABI = void 0;
|
|
4
|
+
exports.isSafeModuleEnabled = isSafeModuleEnabled;
|
|
5
|
+
exports.ensureSingleOwnerAndMatchSigner = ensureSingleOwnerAndMatchSigner;
|
|
6
|
+
exports.enableSafeModule = enableSafeModule;
|
|
7
|
+
const ethers_1 = require("ethers");
|
|
8
|
+
// Minimal Safe ABI fragments used in this SDK
|
|
9
|
+
exports.SAFE_ABI = [
|
|
10
|
+
// module checks
|
|
11
|
+
"function isModuleEnabled(address module) view returns (bool)",
|
|
12
|
+
// EIP-712 domain separator for Safe
|
|
13
|
+
"function domainSeparator() view returns (bytes32)",
|
|
14
|
+
// Safe nonce
|
|
15
|
+
"function nonce() view returns (uint256)",
|
|
16
|
+
// Exec transaction (self-call to enable module)
|
|
17
|
+
"function execTransaction(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,bytes signatures) returns (bool)",
|
|
18
|
+
// Owners and threshold, to validate single signer safes
|
|
19
|
+
"function getOwners() view returns (address[])",
|
|
20
|
+
"function getThreshold() view returns (uint256)",
|
|
21
|
+
];
|
|
22
|
+
// Safe EIP-712 typehash for transactions
|
|
23
|
+
exports.SAFE_TX_TYPEHASH = ethers_1.ethers.id("SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 _nonce)");
|
|
24
|
+
async function isSafeModuleEnabled(safeAddress, provider, moduleAddress) {
|
|
25
|
+
const safe = new ethers_1.ethers.Contract(safeAddress, exports.SAFE_ABI, provider);
|
|
26
|
+
return await safe.isModuleEnabled(moduleAddress);
|
|
27
|
+
}
|
|
28
|
+
async function ensureSingleOwnerAndMatchSigner(safeAddress, provider, signerAddress) {
|
|
29
|
+
const safe = new ethers_1.ethers.Contract(safeAddress, exports.SAFE_ABI, provider);
|
|
30
|
+
const [owners, threshold] = await Promise.all([
|
|
31
|
+
safe.getOwners(),
|
|
32
|
+
safe.getThreshold(),
|
|
33
|
+
]);
|
|
34
|
+
if (Number(threshold) !== 1) {
|
|
35
|
+
throw new Error('Safe wallet must have threshold 1');
|
|
36
|
+
}
|
|
37
|
+
// Check if signerAddress matches any of the Safe owners
|
|
38
|
+
const normalizedSigner = signerAddress.toLowerCase();
|
|
39
|
+
const normalizedOwners = owners.map((owner) => owner.toLowerCase());
|
|
40
|
+
if (!normalizedOwners.includes(normalizedSigner)) {
|
|
41
|
+
throw new Error('Signer is not an owner of the Safe wallet');
|
|
42
|
+
}
|
|
43
|
+
if (owners[0].toLowerCase() !== signerAddress.toLowerCase()) {
|
|
44
|
+
throw new Error('Signer must be the sole owner of the Safe wallet');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Enable provided module on the Safe by crafting and signing a Safe execTransaction
|
|
48
|
+
async function enableSafeModule(safeAddress, signer, moduleAddress) {
|
|
49
|
+
const provider = signer.provider;
|
|
50
|
+
if (!provider)
|
|
51
|
+
throw new Error('Signer provider is required');
|
|
52
|
+
const safeProxy = new ethers_1.ethers.Contract(safeAddress, exports.SAFE_ABI, provider);
|
|
53
|
+
// If already enabled, exit early
|
|
54
|
+
const already = await safeProxy.isModuleEnabled(moduleAddress);
|
|
55
|
+
if (already)
|
|
56
|
+
return;
|
|
57
|
+
const safeNonce = await safeProxy.nonce();
|
|
58
|
+
const iface = new ethers_1.ethers.Interface(exports.SAFE_ABI);
|
|
59
|
+
const data = iface.encodeFunctionData('enableModule', [moduleAddress]);
|
|
60
|
+
const to = safeAddress;
|
|
61
|
+
const value = 0n;
|
|
62
|
+
const operation = 0; // CALL
|
|
63
|
+
const safeTxGas = 0n;
|
|
64
|
+
const baseGas = 0n;
|
|
65
|
+
const gasPrice = 0n;
|
|
66
|
+
const gasToken = ethers_1.ethers.ZeroAddress;
|
|
67
|
+
const refundReceiver = ethers_1.ethers.ZeroAddress;
|
|
68
|
+
// Calculate Safe transaction hash per EIP-712
|
|
69
|
+
const safeTxHash = ethers_1.ethers.keccak256(ethers_1.ethers.AbiCoder.defaultAbiCoder().encode([
|
|
70
|
+
'bytes32', 'address', 'uint256', 'bytes32', 'uint8',
|
|
71
|
+
'uint256', 'uint256', 'uint256', 'address', 'address', 'uint256'
|
|
72
|
+
], [
|
|
73
|
+
exports.SAFE_TX_TYPEHASH, to, value, ethers_1.ethers.keccak256(data), operation,
|
|
74
|
+
safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, safeNonce
|
|
75
|
+
]));
|
|
76
|
+
const domainSeparator = await safeProxy.domainSeparator();
|
|
77
|
+
const txHash = ethers_1.ethers.keccak256(ethers_1.ethers.solidityPacked(['bytes1', 'bytes1', 'bytes32', 'bytes32'], ['0x19', '0x01', domainSeparator, safeTxHash]));
|
|
78
|
+
const rawSignature = await signer.signMessage(ethers_1.ethers.getBytes(txHash));
|
|
79
|
+
const sig = ethers_1.ethers.Signature.from(rawSignature);
|
|
80
|
+
const adjustedV = sig.v + 4; // EthSign type
|
|
81
|
+
const signature = ethers_1.ethers.concat([sig.r, sig.s, ethers_1.ethers.toBeHex(adjustedV, 1)]);
|
|
82
|
+
const safeWithSigner = new ethers_1.ethers.Contract(safeAddress, exports.SAFE_ABI, signer);
|
|
83
|
+
const tx = await safeWithSigner.execTransaction(to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, signature);
|
|
84
|
+
await tx.wait();
|
|
85
|
+
const check = await safeProxy.isModuleEnabled(moduleAddress);
|
|
86
|
+
if (!check) {
|
|
87
|
+
throw new Error('Module verification failed');
|
|
88
|
+
}
|
|
89
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,11 @@ export * from './client';
|
|
|
2
2
|
export * from './config';
|
|
3
3
|
export * from './types';
|
|
4
4
|
export * from './api/jobs';
|
|
5
|
-
export
|
|
5
|
+
export * from './api/getjob';
|
|
6
|
+
export * from './api/getUserData';
|
|
7
|
+
export * from './api/checkTgBalance';
|
|
8
|
+
export * from './api/topupTg';
|
|
9
|
+
export * from './api/deleteJob';
|
|
10
|
+
export * from './api/getJobDataById';
|
|
6
11
|
export * from './contracts';
|
|
7
12
|
export * from './utils/errors';
|
package/dist/index.js
CHANGED
|
@@ -14,12 +14,16 @@ 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
|
-
exports.createJobOnChain = void 0;
|
|
18
17
|
__exportStar(require("./client"), exports);
|
|
19
18
|
__exportStar(require("./config"), exports);
|
|
20
19
|
__exportStar(require("./types"), exports);
|
|
21
20
|
__exportStar(require("./api/jobs"), exports);
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
__exportStar(require("./api/getjob"), exports);
|
|
22
|
+
__exportStar(require("./api/getUserData"), exports);
|
|
23
|
+
__exportStar(require("./api/checkTgBalance"), exports);
|
|
24
|
+
__exportStar(require("./api/topupTg"), exports);
|
|
25
|
+
__exportStar(require("./api/deleteJob"), exports);
|
|
26
|
+
__exportStar(require("./api/getJobDataById"), exports);
|
|
27
|
+
// export { createJobOnChain } from './contracts/JobRegistry';
|
|
24
28
|
__exportStar(require("./contracts"), exports);
|
|
25
29
|
__exportStar(require("./utils/errors"), exports);
|
package/dist/types.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export declare enum ArgType {
|
|
|
17
17
|
Static = "static",
|
|
18
18
|
Dynamic = "dynamic"
|
|
19
19
|
}
|
|
20
|
+
export type WalletMode = 'regular' | 'safe';
|
|
20
21
|
export type CreateJobInput = (TimeBasedJobInput & {
|
|
21
22
|
jobType: JobType.Time;
|
|
22
23
|
argType: ArgType.Static | ArgType.Dynamic;
|
|
@@ -43,6 +44,8 @@ export interface TimeBasedJobInput {
|
|
|
43
44
|
arguments?: string[];
|
|
44
45
|
dynamicArgumentsScriptUrl?: string;
|
|
45
46
|
autotopupTG?: boolean;
|
|
47
|
+
walletMode?: WalletMode;
|
|
48
|
+
safeAddress?: string;
|
|
46
49
|
}
|
|
47
50
|
export interface EventBasedJobInput {
|
|
48
51
|
jobTitle: string;
|
|
@@ -60,6 +63,8 @@ export interface EventBasedJobInput {
|
|
|
60
63
|
arguments?: string[];
|
|
61
64
|
dynamicArgumentsScriptUrl?: string;
|
|
62
65
|
autotopupTG?: boolean;
|
|
66
|
+
walletMode?: WalletMode;
|
|
67
|
+
safeAddress?: string;
|
|
63
68
|
}
|
|
64
69
|
export interface ConditionBasedJobInput {
|
|
65
70
|
jobTitle: string;
|
|
@@ -79,6 +84,8 @@ export interface ConditionBasedJobInput {
|
|
|
79
84
|
arguments?: string[];
|
|
80
85
|
dynamicArgumentsScriptUrl?: string;
|
|
81
86
|
autotopupTG?: boolean;
|
|
87
|
+
walletMode?: WalletMode;
|
|
88
|
+
safeAddress?: string;
|
|
82
89
|
}
|
|
83
90
|
export interface CreateJobData {
|
|
84
91
|
job_id: string;
|
package/dist/utils/errors.d.ts
CHANGED
|
@@ -2,3 +2,7 @@ export declare class TriggerXError extends Error {
|
|
|
2
2
|
constructor(message: string);
|
|
3
3
|
}
|
|
4
4
|
export declare function wrapError(error: unknown): TriggerXError;
|
|
5
|
+
export declare class ValidationError extends Error {
|
|
6
|
+
field: string;
|
|
7
|
+
constructor(field: string, message: string);
|
|
8
|
+
}
|
package/dist/utils/errors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TriggerXError = void 0;
|
|
3
|
+
exports.ValidationError = exports.TriggerXError = void 0;
|
|
4
4
|
exports.wrapError = wrapError;
|
|
5
5
|
class TriggerXError extends Error {
|
|
6
6
|
constructor(message) {
|
|
@@ -15,3 +15,11 @@ function wrapError(error) {
|
|
|
15
15
|
}
|
|
16
16
|
return new TriggerXError('Unknown error');
|
|
17
17
|
}
|
|
18
|
+
class ValidationError extends Error {
|
|
19
|
+
constructor(field, message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = 'ValidationError';
|
|
22
|
+
this.field = field;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.ValidationError = ValidationError;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { TimeBasedJobInput, EventBasedJobInput, ConditionBasedJobInput } from '../types';
|
|
2
|
+
export declare function validateTimeBasedJobInput(input: TimeBasedJobInput, argType: 'static' | 'dynamic' | 1 | 2 | undefined): void;
|
|
3
|
+
export declare function validateEventBasedJobInput(input: EventBasedJobInput, argType: 'static' | 'dynamic' | 1 | 2 | undefined): void;
|
|
4
|
+
export declare function validateConditionBasedJobInput(input: ConditionBasedJobInput, argType: 'static' | 'dynamic' | 1 | 2 | undefined): void;
|
|
5
|
+
export declare function validateJobInput(jobInput: TimeBasedJobInput | EventBasedJobInput | ConditionBasedJobInput, argType?: 'static' | 'dynamic' | 1 | 2): void;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateTimeBasedJobInput = validateTimeBasedJobInput;
|
|
4
|
+
exports.validateEventBasedJobInput = validateEventBasedJobInput;
|
|
5
|
+
exports.validateConditionBasedJobInput = validateConditionBasedJobInput;
|
|
6
|
+
exports.validateJobInput = validateJobInput;
|
|
7
|
+
const ethers_1 = require("ethers");
|
|
8
|
+
const errors_1 = require("./errors");
|
|
9
|
+
function isNonEmptyString(value) {
|
|
10
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
11
|
+
}
|
|
12
|
+
function isValidUrl(value) {
|
|
13
|
+
try {
|
|
14
|
+
// eslint-disable-next-line no-new
|
|
15
|
+
new URL(value);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function buildFunctionSignature(name, inputs) {
|
|
23
|
+
const types = (inputs || []).map((i) => i.type).join(',');
|
|
24
|
+
return `${name}(${types})`;
|
|
25
|
+
}
|
|
26
|
+
function parseAbi(abiString) {
|
|
27
|
+
if (!isNonEmptyString(abiString))
|
|
28
|
+
return null;
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(abiString);
|
|
31
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function validateContractBasics(address, abi, targetFunction, fieldPrefix = 'contract') {
|
|
38
|
+
if (!isNonEmptyString(address)) {
|
|
39
|
+
throw new errors_1.ValidationError(`${fieldPrefix}Address`, 'Contract address is required.');
|
|
40
|
+
}
|
|
41
|
+
if (!ethers_1.ethers.isAddress(address)) {
|
|
42
|
+
throw new errors_1.ValidationError(`${fieldPrefix}Address`, 'Invalid contract address.');
|
|
43
|
+
}
|
|
44
|
+
if (!isNonEmptyString(abi)) {
|
|
45
|
+
throw new errors_1.ValidationError(`${fieldPrefix}ABI`, 'Contract ABI must be provided.');
|
|
46
|
+
}
|
|
47
|
+
if (!isNonEmptyString(targetFunction)) {
|
|
48
|
+
throw new errors_1.ValidationError(`${fieldPrefix}Target`, 'Target function must be selected.');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function validateStaticArguments(abiString, targetFunction, args, fieldPrefix = 'contract') {
|
|
52
|
+
const abi = parseAbi(abiString);
|
|
53
|
+
if (!abi) {
|
|
54
|
+
// If ABI is invalid JSON, surface a clear error
|
|
55
|
+
throw new errors_1.ValidationError(`${fieldPrefix}ABI`, 'Contract ABI must be valid JSON array.');
|
|
56
|
+
}
|
|
57
|
+
const fnItem = abi.find((item) => item && item.type === 'function' && buildFunctionSignature(item.name, item.inputs) === targetFunction);
|
|
58
|
+
if (!fnItem)
|
|
59
|
+
return; // If we cannot find it, skip strict arg validation
|
|
60
|
+
const inputs = Array.isArray(fnItem.inputs) ? fnItem.inputs : [];
|
|
61
|
+
if (inputs.length === 0)
|
|
62
|
+
return;
|
|
63
|
+
if (!args || args.length < inputs.length) {
|
|
64
|
+
throw new errors_1.ValidationError(`${fieldPrefix}Args`, 'All function arguments are required for static argument type.');
|
|
65
|
+
}
|
|
66
|
+
const missing = inputs.findIndex((_, idx) => !isNonEmptyString(args[idx]));
|
|
67
|
+
if (missing !== -1) {
|
|
68
|
+
throw new errors_1.ValidationError(`${fieldPrefix}Args`, 'All function arguments are required for static argument type.');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function validateTimeBasedJobInput(input, argType) {
|
|
72
|
+
if (!isNonEmptyString(input.jobTitle)) {
|
|
73
|
+
throw new errors_1.ValidationError('jobTitle', 'Job title is required.');
|
|
74
|
+
}
|
|
75
|
+
if (!input.timeFrame || input.timeFrame <= 0) {
|
|
76
|
+
throw new errors_1.ValidationError('timeframe', 'Timeframe must be a positive number of seconds.');
|
|
77
|
+
}
|
|
78
|
+
if (!isNonEmptyString(input.timezone)) {
|
|
79
|
+
throw new errors_1.ValidationError('timezone', 'Timezone is required.');
|
|
80
|
+
}
|
|
81
|
+
if (!isNonEmptyString(input.chainId)) {
|
|
82
|
+
throw new errors_1.ValidationError('chainId', 'Chain ID is required.');
|
|
83
|
+
}
|
|
84
|
+
// Schedule-specific required fields
|
|
85
|
+
if (input.scheduleType === 'interval') {
|
|
86
|
+
if (input.timeInterval === undefined || input.timeInterval === null || input.timeInterval <= 0) {
|
|
87
|
+
throw new errors_1.ValidationError('timeInterval', 'timeInterval is required and must be > 0 when scheduleType is interval.');
|
|
88
|
+
}
|
|
89
|
+
if (input.timeInterval > input.timeFrame) {
|
|
90
|
+
throw new errors_1.ValidationError('timeInterval', 'Time interval cannot exceed the timeframe.');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else if (input.scheduleType === 'cron') {
|
|
94
|
+
if (!isNonEmptyString(input.cronExpression)) {
|
|
95
|
+
throw new errors_1.ValidationError('cronExpression', 'cronExpression is required when scheduleType is cron.');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else if (input.scheduleType === 'specific') {
|
|
99
|
+
if (!isNonEmptyString(input.specificSchedule)) {
|
|
100
|
+
throw new errors_1.ValidationError('specificSchedule', 'specificSchedule is required when scheduleType is specific.');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
throw new errors_1.ValidationError('scheduleType', 'scheduleType must be one of interval | cron | specific.');
|
|
105
|
+
}
|
|
106
|
+
validateContractBasics(input.targetContractAddress, input.abi, input.targetFunction, 'contract');
|
|
107
|
+
// Arg type checks
|
|
108
|
+
const isDynamic = argType === 'dynamic' || argType === 2;
|
|
109
|
+
if (isDynamic) {
|
|
110
|
+
if (!isNonEmptyString(input.dynamicArgumentsScriptUrl) || !isValidUrl(input.dynamicArgumentsScriptUrl)) {
|
|
111
|
+
throw new errors_1.ValidationError('contractIpfs', 'IPFS Code URL is required and must be valid for dynamic argument type.');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
validateStaticArguments(input.abi, input.targetFunction, input.arguments, 'contract');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function validateEventBasedJobInput(input, argType) {
|
|
119
|
+
if (!isNonEmptyString(input.jobTitle)) {
|
|
120
|
+
throw new errors_1.ValidationError('jobTitle', 'Job title is required.');
|
|
121
|
+
}
|
|
122
|
+
if (!input.timeFrame || input.timeFrame <= 0) {
|
|
123
|
+
throw new errors_1.ValidationError('timeframe', 'Timeframe must be a positive number of seconds.');
|
|
124
|
+
}
|
|
125
|
+
if (!isNonEmptyString(input.timezone)) {
|
|
126
|
+
throw new errors_1.ValidationError('timezone', 'Timezone is required.');
|
|
127
|
+
}
|
|
128
|
+
if (!isNonEmptyString(input.chainId)) {
|
|
129
|
+
throw new errors_1.ValidationError('chainId', 'Chain ID is required.');
|
|
130
|
+
}
|
|
131
|
+
if (!isNonEmptyString(input.triggerChainId)) {
|
|
132
|
+
throw new errors_1.ValidationError('triggerChainId', 'Trigger chain ID is required.');
|
|
133
|
+
}
|
|
134
|
+
validateContractBasics(input.triggerContractAddress, input.abi, input.triggerEvent, 'eventContract');
|
|
135
|
+
validateContractBasics(input.targetContractAddress, input.abi, input.targetFunction, 'contract');
|
|
136
|
+
const isDynamic = argType === 'dynamic' || argType === 2;
|
|
137
|
+
if (isDynamic) {
|
|
138
|
+
if (!isNonEmptyString(input.dynamicArgumentsScriptUrl) || !isValidUrl(input.dynamicArgumentsScriptUrl)) {
|
|
139
|
+
throw new errors_1.ValidationError('contractIpfs', 'IPFS Code URL is required and must be valid for dynamic argument type.');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
validateStaticArguments(input.abi, input.targetFunction, input.arguments, 'contract');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function validateConditionBasedJobInput(input, argType) {
|
|
147
|
+
if (!isNonEmptyString(input.jobTitle)) {
|
|
148
|
+
throw new errors_1.ValidationError('jobTitle', 'Job title is required.');
|
|
149
|
+
}
|
|
150
|
+
if (!input.timeFrame || input.timeFrame <= 0) {
|
|
151
|
+
throw new errors_1.ValidationError('timeframe', 'Timeframe must be a positive number of seconds.');
|
|
152
|
+
}
|
|
153
|
+
if (!isNonEmptyString(input.timezone)) {
|
|
154
|
+
throw new errors_1.ValidationError('timezone', 'Timezone is required.');
|
|
155
|
+
}
|
|
156
|
+
if (!isNonEmptyString(input.chainId)) {
|
|
157
|
+
throw new errors_1.ValidationError('chainId', 'Chain ID is required.');
|
|
158
|
+
}
|
|
159
|
+
// Condition fields
|
|
160
|
+
if (!isNonEmptyString(input.conditionType)) {
|
|
161
|
+
throw new errors_1.ValidationError('contractConditionType', 'Condition type is required.');
|
|
162
|
+
}
|
|
163
|
+
if (!isNonEmptyString(input.valueSourceType)) {
|
|
164
|
+
throw new errors_1.ValidationError('contractSourceType', 'Value source type is required.');
|
|
165
|
+
}
|
|
166
|
+
if (!isNonEmptyString(input.valueSourceUrl) || !isValidUrl(input.valueSourceUrl)) {
|
|
167
|
+
throw new errors_1.ValidationError('contractSourceUrl', 'Source URL is required and must be valid.');
|
|
168
|
+
}
|
|
169
|
+
if (input.conditionType === 'between') {
|
|
170
|
+
if (input.upperLimit === undefined || input.lowerLimit === undefined || input.upperLimit === null || input.lowerLimit === null) {
|
|
171
|
+
throw new errors_1.ValidationError('contractLimits', 'Both upper and lower limits are required.');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
if (input.upperLimit === undefined || input.upperLimit === null) {
|
|
176
|
+
throw new errors_1.ValidationError('contractLimits', 'Value is required.');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
validateContractBasics(input.targetContractAddress, input.abi, input.targetFunction, 'contract');
|
|
180
|
+
const isDynamic = argType === 'dynamic' || argType === 2;
|
|
181
|
+
if (isDynamic) {
|
|
182
|
+
if (!isNonEmptyString(input.dynamicArgumentsScriptUrl) || !isValidUrl(input.dynamicArgumentsScriptUrl)) {
|
|
183
|
+
throw new errors_1.ValidationError('contractIpfs', 'IPFS Code URL is required and must be valid for dynamic argument type.');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
validateStaticArguments(input.abi, input.targetFunction, input.arguments, 'contract');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function validateJobInput(jobInput, argType) {
|
|
191
|
+
if ('scheduleType' in jobInput) {
|
|
192
|
+
validateTimeBasedJobInput(jobInput, argType);
|
|
193
|
+
}
|
|
194
|
+
else if ('triggerChainId' in jobInput) {
|
|
195
|
+
validateEventBasedJobInput(jobInput, argType);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
validateConditionBasedJobInput(jobInput, argType);
|
|
199
|
+
}
|
|
200
|
+
}
|