sdk-triggerx 0.1.26 → 0.1.28
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 +135 -16
- package/dist/api/checkTgBalance.d.ts +9 -1
- package/dist/api/checkTgBalance.js +38 -6
- package/dist/api/jobs.js +105 -27
- package/dist/config.d.ts +12 -0
- package/dist/config.js +27 -0
- package/dist/types.d.ts +26 -0
- package/dist/utils/validation.js +100 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,10 @@ Supports job automation on EVM-compatible chains using time, event, or condition
|
|
|
12
12
|
- 📅 Supports time-based, event-based, and condition-based jobs
|
|
13
13
|
- 🔐 TypeScript support with clean types
|
|
14
14
|
- 🧠 Dynamic argument fetching via external scripts (e.g., IPFS)
|
|
15
|
+
- 🛡️ Safe (Gnosis Safe) wallet integration with support for:
|
|
16
|
+
- Static single transactions (e.g., ETH transfers)
|
|
17
|
+
- Static batch transactions (e.g., DeFi operations like token approvals + swaps)
|
|
18
|
+
- Dynamic runtime arguments via IPFS scripts
|
|
15
19
|
|
|
16
20
|
---
|
|
17
21
|
|
|
@@ -57,9 +61,11 @@ const client = new TriggerXClient('YOUR_API_KEY');
|
|
|
57
61
|
- Requirements:
|
|
58
62
|
- Safe threshold must be 1 (single-signer) in this implementation.
|
|
59
63
|
- The signer must be an owner of the Safe.
|
|
60
|
-
-
|
|
61
|
-
- The SDK will auto-create a Safe if `safeAddress` is not provided and the chain is configured with a Safe Factory.
|
|
64
|
+
- Supports both static transactions (`safeTransactions` array) and dynamic arguments (`dynamicArgumentsScriptUrl`).
|
|
62
65
|
- The SDK will auto-enable the TriggerX Safe Module on the Safe if not already enabled.
|
|
66
|
+
- Static transactions:
|
|
67
|
+
- Single transaction: Provide one transaction in `safeTransactions` array (operation: CALL).
|
|
68
|
+
- Batch transactions: Provide multiple transactions in `safeTransactions` array (uses Safe MultiSend with DELEGATECALL).
|
|
63
69
|
|
|
64
70
|
#### Supported Condition Types (for `conditionType`)
|
|
65
71
|
|
|
@@ -128,11 +134,11 @@ const jobInput = {
|
|
|
128
134
|
|
|
129
135
|
// Safe mode (no target required — SDK auto-sets module target/function/ABI)
|
|
130
136
|
walletMode: 'safe',
|
|
131
|
-
//
|
|
132
|
-
// safeAddress: '0xYourSafeAddress',
|
|
137
|
+
safeAddress: '0xYourSafeAddress', // Required: provide your Safe address
|
|
133
138
|
|
|
134
|
-
// Dynamic params
|
|
139
|
+
// Dynamic params fetched from IPFS/URL script at execution time
|
|
135
140
|
dynamicArgumentsScriptUrl: 'https://your-ipfs-gateway/ipfs/your-hash',
|
|
141
|
+
language:'', //Your code language exmampel-> language:'go',
|
|
136
142
|
|
|
137
143
|
// Optional helper to auto-top up TG if low
|
|
138
144
|
autotopupTG: true,
|
|
@@ -144,8 +150,8 @@ console.log(result);
|
|
|
144
150
|
|
|
145
151
|
Notes for Safe wallet mode:
|
|
146
152
|
- In Safe mode, you do NOT need to set `targetContractAddress`/`targetFunction`/`abi` — the SDK sets these for the Safe Module and uses `execJobFromHub(address,address,uint256,bytes,uint8)` under the hood.
|
|
147
|
-
-
|
|
148
|
-
-
|
|
153
|
+
- For dynamic jobs: your action details (action target/value/data/op) must be produced by your IPFS script at execution time.
|
|
154
|
+
- For static jobs: use the `safeTransactions` array to provide hardcoded transaction details (see Safe Wallet Flow section below).
|
|
149
155
|
|
|
150
156
|
---
|
|
151
157
|
|
|
@@ -172,6 +178,7 @@ const jobInput = {
|
|
|
172
178
|
|
|
173
179
|
arguments: [], // Target function args as strings
|
|
174
180
|
dynamicArgumentsScriptUrl: 'https://your-ipfs-url', // Script URL for dynamic args
|
|
181
|
+
language:'', //Your code language exmampel-> language:'go',
|
|
175
182
|
isImua: false, // Optional feature flag
|
|
176
183
|
autotopupTG: true, // Auto top-up TG if balance is low
|
|
177
184
|
};
|
|
@@ -224,7 +231,7 @@ To create jobs that use Safe wallets (`walletMode: 'safe'`), you must first crea
|
|
|
224
231
|
#### 1️⃣ Create a Safe wallet for your user
|
|
225
232
|
|
|
226
233
|
```ts
|
|
227
|
-
import { createSafeWallet } from 'sdk-triggerx/api/safeWallet';
|
|
234
|
+
import { createSafeWallet } from 'sdk-triggerx/api/safeWallet';
|
|
228
235
|
const safeAddress = await createSafeWallet(signer /* ethers.Signer */);
|
|
229
236
|
console.log('Your Safe address:', safeAddress);
|
|
230
237
|
```
|
|
@@ -234,29 +241,133 @@ console.log('Your Safe address:', safeAddress);
|
|
|
234
241
|
- This MUST be done before you attempt to create any job with `walletMode: 'safe'`.
|
|
235
242
|
- All safe wallet creation helpers are now in the dedicated `api/safeWallet` module so your file structure stays clean.
|
|
236
243
|
|
|
237
|
-
#### 2️⃣ Create a job using the Safe
|
|
244
|
+
#### 2️⃣ Create a job using the Safe
|
|
245
|
+
|
|
246
|
+
Safe wallet jobs support two modes: **static transactions** and **dynamic arguments**.
|
|
247
|
+
|
|
248
|
+
##### Option A: Static Single Transaction (e.g., ETH transfer)
|
|
238
249
|
|
|
239
250
|
```ts
|
|
240
251
|
const jobInput = {
|
|
241
252
|
jobType: JobType.Time,
|
|
242
|
-
argType: ArgType.
|
|
243
|
-
jobTitle: 'Safe
|
|
253
|
+
argType: ArgType.Static, // Static for hardcoded transactions
|
|
254
|
+
jobTitle: 'Safe ETH Transfer',
|
|
244
255
|
timeFrame: 3600,
|
|
245
256
|
scheduleType: 'interval',
|
|
246
257
|
timeInterval: 300,
|
|
247
258
|
timezone: 'UTC',
|
|
248
259
|
chainId: '421614',
|
|
249
260
|
walletMode: 'safe',
|
|
250
|
-
safeAddress, //
|
|
261
|
+
safeAddress: '0xYourSafeAddress', // from step 1
|
|
262
|
+
safeTransactions: [
|
|
263
|
+
{
|
|
264
|
+
to: '0xRecipientAddress',
|
|
265
|
+
value: '10000000000000', // 0.00001 ETH in wei
|
|
266
|
+
data: '0x' // empty for simple ETH transfer
|
|
267
|
+
}
|
|
268
|
+
],
|
|
269
|
+
autotopupTG: true,
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
await createJob(client, { jobInput, signer });
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
##### Option B: Static Batch Transactions (e.g., Uniswap Swap)
|
|
276
|
+
|
|
277
|
+
For complex multi-step operations like token approvals + swaps:
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
import { ethers } from 'ethers';
|
|
281
|
+
|
|
282
|
+
// Token addresses (Arbitrum Sepolia)
|
|
283
|
+
const USDC = '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d';
|
|
284
|
+
const WETH = '0x980B62Da83eFf3D4576C647993b0c1D7faf17c73';
|
|
285
|
+
const UNISWAP_ROUTER = '0x101F443B4d1b059569D643917553c771E1b9663E';
|
|
286
|
+
|
|
287
|
+
// Encode approve transaction
|
|
288
|
+
const erc20Interface = new ethers.Interface([
|
|
289
|
+
'function approve(address spender, uint256 amount) returns (bool)'
|
|
290
|
+
]);
|
|
291
|
+
const approveData = erc20Interface.encodeFunctionData('approve', [
|
|
292
|
+
UNISWAP_ROUTER,
|
|
293
|
+
'10000' // 0.01 USDC (6 decimals)
|
|
294
|
+
]);
|
|
295
|
+
|
|
296
|
+
// Encode Uniswap V3 swap transaction
|
|
297
|
+
const swapInterface = new ethers.Interface([
|
|
298
|
+
'function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96)) payable returns (uint256 amountOut)'
|
|
299
|
+
]);
|
|
300
|
+
const swapData = swapInterface.encodeFunctionData('exactInputSingle', [{
|
|
301
|
+
tokenIn: USDC,
|
|
302
|
+
tokenOut: WETH,
|
|
303
|
+
fee: 3000, // 0.3%
|
|
304
|
+
recipient: safeAddress, // Safe receives the output
|
|
305
|
+
amountIn: '10000',
|
|
306
|
+
amountOutMinimum: '0',
|
|
307
|
+
sqrtPriceLimitX96: 0
|
|
308
|
+
}]);
|
|
309
|
+
|
|
310
|
+
const jobInput = {
|
|
311
|
+
jobType: JobType.Time,
|
|
312
|
+
argType: ArgType.Static,
|
|
313
|
+
jobTitle: 'Safe Uniswap Swap',
|
|
314
|
+
timeFrame: 3600,
|
|
315
|
+
scheduleType: 'interval',
|
|
316
|
+
timeInterval: 600,
|
|
317
|
+
timezone: 'UTC',
|
|
318
|
+
chainId: '421614',
|
|
319
|
+
walletMode: 'safe',
|
|
320
|
+
safeAddress: '0xYourSafeAddress',
|
|
321
|
+
safeTransactions: [
|
|
322
|
+
{
|
|
323
|
+
to: USDC, // Approve USDC to router
|
|
324
|
+
value: '0',
|
|
325
|
+
data: approveData
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
to: UNISWAP_ROUTER, // Execute swap
|
|
329
|
+
value: '0',
|
|
330
|
+
data: swapData
|
|
331
|
+
}
|
|
332
|
+
],
|
|
333
|
+
autotopupTG: true,
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
await createJob(client, { jobInput, signer });
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**Note:** When multiple transactions are provided in `safeTransactions`, the SDK automatically:
|
|
340
|
+
- Encodes them using Safe's MultiSend format
|
|
341
|
+
- Wraps them with the `multiSend(bytes)` function call
|
|
342
|
+
- Sets the operation to DELEGATECALL for batch execution
|
|
343
|
+
|
|
344
|
+
##### Option C: Dynamic Arguments
|
|
345
|
+
|
|
346
|
+
For runtime-determined parameters:
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
const jobInput = {
|
|
350
|
+
jobType: JobType.Time,
|
|
351
|
+
argType: ArgType.Dynamic,
|
|
352
|
+
jobTitle: 'Dynamic Safe Job',
|
|
353
|
+
timeFrame: 3600,
|
|
354
|
+
scheduleType: 'interval',
|
|
355
|
+
timeInterval: 300,
|
|
356
|
+
timezone: 'UTC',
|
|
357
|
+
chainId: '421614',
|
|
358
|
+
walletMode: 'safe',
|
|
359
|
+
safeAddress: '0xYourSafeAddress',
|
|
251
360
|
dynamicArgumentsScriptUrl: 'https://your-ipfs-gateway/ipfs/your-hash',
|
|
361
|
+
language:'go', //Your code language exmampel-> language:'go',
|
|
252
362
|
autotopupTG: true,
|
|
253
363
|
};
|
|
254
|
-
|
|
255
|
-
await createJob(client, { jobInput, signer });
|
|
364
|
+
|
|
365
|
+
await createJob(client, { jobInput, signer });
|
|
256
366
|
```
|
|
257
367
|
|
|
258
|
-
- The `safeAddress` property is
|
|
259
|
-
-
|
|
368
|
+
- The `safeAddress` property is required for all Safe wallet jobs.
|
|
369
|
+
- For static jobs, provide `safeTransactions` array with transaction details.
|
|
370
|
+
- For dynamic jobs, provide `dynamicArgumentsScriptUrl` that returns the action parameters at execution time.
|
|
260
371
|
|
|
261
372
|
---
|
|
262
373
|
|
|
@@ -337,6 +448,14 @@ Includes:
|
|
|
337
448
|
- `JobInput`, `JobType`, `ArgType`
|
|
338
449
|
- `ConditionType`, `ScheduleType`
|
|
339
450
|
- `UserData`, `JobData`, etc.
|
|
451
|
+
- `SafeTransaction` - Interface for Safe wallet transaction objects:
|
|
452
|
+
```ts
|
|
453
|
+
interface SafeTransaction {
|
|
454
|
+
to: string; // Target contract address
|
|
455
|
+
value: string; // Value in wei (as string)
|
|
456
|
+
data: string; // Encoded function call data (hex with 0x prefix)
|
|
457
|
+
}
|
|
458
|
+
```
|
|
340
459
|
|
|
341
460
|
---
|
|
342
461
|
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { ethers } from 'ethers';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Check TG balance for a given signer using SDK-provided RPC
|
|
4
|
+
* This function uses our own RPC provider to ensure reliable connection
|
|
5
|
+
* even if the user's RPC fails
|
|
6
|
+
* @param signer - ethers.Signer instance (used to get wallet address)
|
|
7
|
+
* @param chainId - Optional chain ID. If not provided, will try to get from signer's provider
|
|
8
|
+
* @returns Balance information or error response
|
|
9
|
+
*/
|
|
10
|
+
export declare const checkTgBalance: (signer: ethers.Signer, chainId?: string | number) => Promise<{
|
|
3
11
|
success: boolean;
|
|
4
12
|
data?: {
|
|
5
13
|
tgBalanceWei: bigint;
|
|
@@ -8,21 +8,53 @@ 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
10
|
const errors_1 = require("../utils/errors");
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Check TG balance for a given signer using SDK-provided RPC
|
|
13
|
+
* This function uses our own RPC provider to ensure reliable connection
|
|
14
|
+
* even if the user's RPC fails
|
|
15
|
+
* @param signer - ethers.Signer instance (used to get wallet address)
|
|
16
|
+
* @param chainId - Optional chain ID. If not provided, will try to get from signer's provider
|
|
17
|
+
* @returns Balance information or error response
|
|
18
|
+
*/
|
|
19
|
+
const checkTgBalance = async (signer, chainId) => {
|
|
12
20
|
// Validate inputs
|
|
13
21
|
if (!signer) {
|
|
14
22
|
return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('signer', 'Signer is required'), 'Validation error');
|
|
15
23
|
}
|
|
16
24
|
try {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
// Try to get chainId from signer's provider if not provided
|
|
26
|
+
// If signer's provider fails, we'll use the provided chainId or return error
|
|
27
|
+
let resolvedChainId = chainId?.toString();
|
|
28
|
+
if (!resolvedChainId) {
|
|
29
|
+
try {
|
|
30
|
+
const network = await signer.provider?.getNetwork();
|
|
31
|
+
resolvedChainId = network?.chainId ? network.chainId.toString() : undefined;
|
|
32
|
+
}
|
|
33
|
+
catch (providerError) {
|
|
34
|
+
// If user's RPC fails, we can't get chainId from it
|
|
35
|
+
// This is expected in cases where user's RPC is down
|
|
36
|
+
console.warn('Could not get network from signer provider, using provided chainId or will fail:', providerError);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (!resolvedChainId) {
|
|
40
|
+
return (0, errors_1.createErrorResponse)(new errors_1.ConfigurationError('Chain ID is required. Please provide chainId parameter or ensure signer has a working provider.'), 'Configuration error');
|
|
41
|
+
}
|
|
42
|
+
const { gasRegistry } = (0, config_1.getChainAddresses)(resolvedChainId);
|
|
20
43
|
const gasRegistryContractAddress = gasRegistry;
|
|
21
44
|
if (!gasRegistryContractAddress) {
|
|
22
|
-
return (0, errors_1.createErrorResponse)(new errors_1.ConfigurationError(`GasRegistry address not configured for chain ID: ${
|
|
45
|
+
return (0, errors_1.createErrorResponse)(new errors_1.ConfigurationError(`GasRegistry address not configured for chain ID: ${resolvedChainId}`), 'Configuration error');
|
|
46
|
+
}
|
|
47
|
+
// Use SDK-provided RPC provider instead of user's provider
|
|
48
|
+
// This ensures we can read balance even if user's RPC fails
|
|
49
|
+
const rpcProvider = (0, config_1.getRpcProvider)(resolvedChainId);
|
|
50
|
+
if (!rpcProvider) {
|
|
51
|
+
return (0, errors_1.createErrorResponse)(new errors_1.ConfigurationError(`RPC URL not configured for chain ID: ${resolvedChainId}. Cannot check balance without RPC provider.`), 'Configuration error');
|
|
23
52
|
}
|
|
24
|
-
|
|
53
|
+
// Create contract instance with our RPC provider (read-only)
|
|
54
|
+
const contract = new ethers_1.ethers.Contract(gasRegistryContractAddress, GasRegistry_json_1.default, rpcProvider);
|
|
55
|
+
// Get address from signer (this doesn't require provider)
|
|
25
56
|
const address = await signer.getAddress();
|
|
57
|
+
// Read balance using our RPC provider
|
|
26
58
|
const balance = await contract.balances(address);
|
|
27
59
|
// balance is likely an array or object with ethSpent and TGbalance, both in wei
|
|
28
60
|
// We'll convert TGbalance from wei to ETH
|
package/dist/api/jobs.js
CHANGED
|
@@ -16,11 +16,44 @@ 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
|
-
|
|
20
|
-
|
|
19
|
+
// Helper function to encode multisend batch transactions
|
|
20
|
+
function encodeMultisendData(transactions) {
|
|
21
|
+
// Multisend format: for each transaction, encode as:
|
|
22
|
+
// operation (1 byte) + to (20 bytes) + value (32 bytes) + dataLength (32 bytes) + data (variable)
|
|
23
|
+
let encodedTransactions = '';
|
|
24
|
+
for (const tx of transactions) {
|
|
25
|
+
const txWithOperation = tx;
|
|
26
|
+
const to = txWithOperation.to;
|
|
27
|
+
const value = ethers_1.ethers.toBigInt(txWithOperation.value);
|
|
28
|
+
const data = txWithOperation.data;
|
|
29
|
+
// Remove 0x prefix from data if present
|
|
30
|
+
const dataWithoutPrefix = data.startsWith('0x') ? data.slice(2) : data;
|
|
31
|
+
const dataLength = ethers_1.ethers.toBigInt(dataWithoutPrefix.length / 2);
|
|
32
|
+
// Encode each field and concatenate
|
|
33
|
+
// operation: uint8 (1 byte)
|
|
34
|
+
const operation = typeof txWithOperation.operation === 'number' ? txWithOperation.operation : 0;
|
|
35
|
+
if (operation < 0 || operation > 1) {
|
|
36
|
+
throw new Error(`Invalid Safe transaction operation: ${operation}. Expected 0 (CALL) or 1 (DELEGATECALL).`);
|
|
37
|
+
}
|
|
38
|
+
const operationHex = operation.toString(16).padStart(2, '0');
|
|
39
|
+
// to: address (20 bytes)
|
|
40
|
+
const toHex = to.toLowerCase().replace(/^0x/, '').padStart(40, '0');
|
|
41
|
+
// value: uint256 (32 bytes)
|
|
42
|
+
const valueHex = value.toString(16).padStart(64, '0');
|
|
43
|
+
// dataLength: uint256 (32 bytes)
|
|
44
|
+
const dataLengthHex = dataLength.toString(16).padStart(64, '0');
|
|
45
|
+
// data: bytes (variable length)
|
|
46
|
+
encodedTransactions += operationHex + toHex + valueHex + dataLengthHex + dataWithoutPrefix;
|
|
47
|
+
}
|
|
48
|
+
const packedTransactions = `0x${encodedTransactions}`;
|
|
49
|
+
const multiSendInterface = new ethers_1.ethers.Interface([
|
|
50
|
+
'function multiSend(bytes transactions)'
|
|
51
|
+
]);
|
|
52
|
+
return multiSendInterface.encodeFunctionData('multiSend', [packedTransactions]);
|
|
53
|
+
}
|
|
21
54
|
function toCreateJobDataFromTime(input, balances, userAddress, jobCostPrediction) {
|
|
22
55
|
return {
|
|
23
|
-
job_id:
|
|
56
|
+
job_id: "",
|
|
24
57
|
user_address: userAddress,
|
|
25
58
|
ether_balance: balances.etherBalance,
|
|
26
59
|
token_balance: balances.tokenBalanceWei,
|
|
@@ -43,16 +76,16 @@ function toCreateJobDataFromTime(input, balances, userAddress, jobCostPrediction
|
|
|
43
76
|
arg_type: input.dynamicArgumentsScriptUrl ? 2 : 1,
|
|
44
77
|
arguments: input.arguments,
|
|
45
78
|
dynamic_arguments_script_url: input.dynamicArgumentsScriptUrl,
|
|
46
|
-
is_imua: input.isImua ??
|
|
79
|
+
is_imua: input.isImua ?? false,
|
|
47
80
|
is_safe: input.walletMode === 'safe',
|
|
48
81
|
safe_name: input.safeName || '',
|
|
49
82
|
safe_address: input.safeAddress || '',
|
|
50
|
-
language: input.language || '',
|
|
83
|
+
language: input.language || 'go',
|
|
51
84
|
};
|
|
52
85
|
}
|
|
53
86
|
function toCreateJobDataFromEvent(input, balances, userAddress, jobCostPrediction) {
|
|
54
87
|
return {
|
|
55
|
-
job_id:
|
|
88
|
+
job_id: "",
|
|
56
89
|
user_address: userAddress,
|
|
57
90
|
ether_balance: balances.etherBalance,
|
|
58
91
|
token_balance: balances.tokenBalanceWei,
|
|
@@ -74,16 +107,16 @@ function toCreateJobDataFromEvent(input, balances, userAddress, jobCostPredictio
|
|
|
74
107
|
arg_type: input.dynamicArgumentsScriptUrl ? 2 : 1,
|
|
75
108
|
arguments: input.arguments,
|
|
76
109
|
dynamic_arguments_script_url: input.dynamicArgumentsScriptUrl,
|
|
77
|
-
is_imua: input.isImua ??
|
|
110
|
+
is_imua: input.isImua ?? false,
|
|
78
111
|
is_safe: input.walletMode === 'safe',
|
|
79
112
|
safe_name: input.safeName || '',
|
|
80
113
|
safe_address: input.safeAddress || '',
|
|
81
|
-
language: input.language || '',
|
|
114
|
+
language: input.language || 'go',
|
|
82
115
|
};
|
|
83
116
|
}
|
|
84
117
|
function toCreateJobDataFromCondition(input, balances, userAddress, jobCostPrediction) {
|
|
85
118
|
return {
|
|
86
|
-
job_id:
|
|
119
|
+
job_id: "",
|
|
87
120
|
user_address: userAddress,
|
|
88
121
|
ether_balance: balances.etherBalance,
|
|
89
122
|
token_balance: balances.tokenBalanceWei,
|
|
@@ -107,11 +140,11 @@ function toCreateJobDataFromCondition(input, balances, userAddress, jobCostPredi
|
|
|
107
140
|
arg_type: input.dynamicArgumentsScriptUrl ? 2 : 1,
|
|
108
141
|
arguments: input.arguments,
|
|
109
142
|
dynamic_arguments_script_url: input.dynamicArgumentsScriptUrl,
|
|
110
|
-
is_imua: input.isImua ??
|
|
143
|
+
is_imua: input.isImua ?? false,
|
|
111
144
|
is_safe: input.walletMode === 'safe',
|
|
112
145
|
safe_name: input.safeName || '',
|
|
113
146
|
safe_address: input.safeAddress || '',
|
|
114
|
-
language: input.language || '',
|
|
147
|
+
language: input.language || 'go',
|
|
115
148
|
};
|
|
116
149
|
}
|
|
117
150
|
// --- Encoding helpers for different job types ---
|
|
@@ -156,7 +189,7 @@ async function createJob(client, params) {
|
|
|
156
189
|
return (0, errors_1.createErrorResponse)(new errors_1.NetworkError('Failed to get network information', { originalError: err }), 'Network error');
|
|
157
190
|
}
|
|
158
191
|
const chainIdStr = network?.chainId ? network.chainId.toString() : undefined;
|
|
159
|
-
const { jobRegistry, safeModule, safeFactory } = (0, config_1.getChainAddresses)(chainIdStr);
|
|
192
|
+
const { jobRegistry, safeModule, safeFactory, multisendCallOnly } = (0, config_1.getChainAddresses)(chainIdStr);
|
|
160
193
|
const JOB_REGISTRY_ADDRESS = jobRegistry;
|
|
161
194
|
if (!JOB_REGISTRY_ADDRESS) {
|
|
162
195
|
return (0, errors_1.createErrorResponse)(new errors_1.ConfigurationError(`JobRegistry address not configured for chain ID: ${chainIdStr}`), 'Configuration error');
|
|
@@ -171,10 +204,6 @@ async function createJob(client, params) {
|
|
|
171
204
|
if (!jobInput.safeAddress || typeof jobInput.safeAddress !== 'string' || !jobInput.safeAddress.trim()) {
|
|
172
205
|
return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('safeAddress', 'safeAddress is required when walletMode is "safe". Call createSafeWallet first.'), 'Validation error');
|
|
173
206
|
}
|
|
174
|
-
const dynUrl = jobInput.dynamicArgumentsScriptUrl;
|
|
175
|
-
if (!dynUrl) {
|
|
176
|
-
return (0, errors_1.createErrorResponse)(new errors_1.ValidationError('dynamicArgumentsScriptUrl', 'Safe jobs require dynamicArgumentsScriptUrl (IPFS) for parameters.'), 'Validation error');
|
|
177
|
-
}
|
|
178
207
|
// Validate Safe has single owner and owner matches signer
|
|
179
208
|
try {
|
|
180
209
|
await (0, SafeWallet_1.ensureSingleOwnerAndMatchSigner)(jobInput.safeAddress, signer.provider, await signer.getAddress());
|
|
@@ -186,20 +215,67 @@ async function createJob(client, params) {
|
|
|
186
215
|
}
|
|
187
216
|
// Auto-set module target; user does not need to pass targetContractAddress in safe mode
|
|
188
217
|
jobInput.targetContractAddress = safeModule;
|
|
189
|
-
|
|
218
|
+
// Function signature must match exactly as in ABI
|
|
219
|
+
jobInput.targetFunction = 'execJobFromHub';
|
|
220
|
+
// ABI verified per provided interface and matches execJobFromHub
|
|
190
221
|
jobInput.abi = JSON.stringify([
|
|
191
222
|
{
|
|
192
|
-
"
|
|
193
|
-
{ "name": "safeAddress", "type": "address" },
|
|
194
|
-
{ "name": "actionTarget", "type": "address" },
|
|
195
|
-
{ "name": "actionValue", "type": "uint256" },
|
|
196
|
-
{ "name": "actionData", "type": "bytes" },
|
|
197
|
-
{ "name": "operation", "type": "uint8" }
|
|
198
|
-
],
|
|
223
|
+
"inputs": [
|
|
224
|
+
{ "internalType": "address", "name": "safeAddress", "type": "address" },
|
|
225
|
+
{ "internalType": "address", "name": "actionTarget", "type": "address" },
|
|
226
|
+
{ "internalType": "uint256", "name": "actionValue", "type": "uint256" },
|
|
227
|
+
{ "internalType": "bytes", "name": "actionData", "type": "bytes" },
|
|
228
|
+
{ "internalType": "uint8", "name": "operation", "type": "uint8" }
|
|
229
|
+
],
|
|
230
|
+
"name": "execJobFromHub",
|
|
231
|
+
"outputs": [
|
|
232
|
+
{ "internalType": "bool", "name": "success", "type": "bool" }
|
|
233
|
+
],
|
|
234
|
+
"stateMutability": "nonpayable",
|
|
235
|
+
"type": "function"
|
|
199
236
|
}
|
|
200
237
|
]);
|
|
201
|
-
//
|
|
202
|
-
|
|
238
|
+
// Handle static vs dynamic safe wallet jobs
|
|
239
|
+
const hasDynamicUrl = !!jobInput.dynamicArgumentsScriptUrl;
|
|
240
|
+
const hasSafeTransactions = !!(jobInput.safeTransactions && jobInput.safeTransactions.length > 0);
|
|
241
|
+
if (hasDynamicUrl) {
|
|
242
|
+
// Dynamic safe wallet job - keep existing behavior
|
|
243
|
+
jobInput.arguments = undefined;
|
|
244
|
+
}
|
|
245
|
+
else if (hasSafeTransactions) {
|
|
246
|
+
// Static safe wallet job - encode transactions into arguments
|
|
247
|
+
const safeTransactions = jobInput.safeTransactions;
|
|
248
|
+
const safeAddress = jobInput.safeAddress;
|
|
249
|
+
if (safeTransactions.length === 1) {
|
|
250
|
+
// Single transaction: use transaction directly
|
|
251
|
+
const tx = safeTransactions[0];
|
|
252
|
+
jobInput.arguments = [
|
|
253
|
+
safeAddress,
|
|
254
|
+
tx.to,
|
|
255
|
+
tx.value,
|
|
256
|
+
tx.data,
|
|
257
|
+
'0' // CALL
|
|
258
|
+
];
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
// Multiple transactions: use multisend
|
|
262
|
+
if (!multisendCallOnly) {
|
|
263
|
+
return (0, errors_1.createErrorResponse)(new errors_1.ConfigurationError('MultisendCallOnly address not configured for this chain.'), 'Configuration error');
|
|
264
|
+
}
|
|
265
|
+
const encodedMultisendData = encodeMultisendData(safeTransactions);
|
|
266
|
+
jobInput.arguments = [
|
|
267
|
+
safeAddress,
|
|
268
|
+
multisendCallOnly,
|
|
269
|
+
'0',
|
|
270
|
+
encodedMultisendData,
|
|
271
|
+
'1' // DELEGATECALL
|
|
272
|
+
];
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
// Will be caught by validation
|
|
277
|
+
jobInput.arguments = undefined;
|
|
278
|
+
}
|
|
203
279
|
}
|
|
204
280
|
// 0. Validate user input thoroughly before proceeding (after safe overrides)
|
|
205
281
|
try {
|
|
@@ -330,9 +406,10 @@ async function createJob(client, params) {
|
|
|
330
406
|
// We'll throw an error with the fee and let the caller handle the prompt/confirmation.
|
|
331
407
|
// If you want to automate, you can add a `proceed` flag to params in the future.
|
|
332
408
|
// Check if the user has enough TG to cover the job cost prediction
|
|
409
|
+
// Use chainId if available, so we can use SDK RPC provider even if user's RPC fails
|
|
333
410
|
let tgBalanceWei, tgBalance;
|
|
334
411
|
try {
|
|
335
|
-
const balanceResult = await (0, checkTgBalance_1.checkTgBalance)(signer);
|
|
412
|
+
const balanceResult = await (0, checkTgBalance_1.checkTgBalance)(signer, chainIdStr);
|
|
336
413
|
if (!balanceResult.success || !balanceResult.data) {
|
|
337
414
|
return (0, errors_1.createErrorResponse)(new errors_1.BalanceError('Failed to check TG balance', balanceResult.details), 'Balance check error');
|
|
338
415
|
}
|
|
@@ -410,6 +487,7 @@ async function createJob(client, params) {
|
|
|
410
487
|
ether_balance: typeof jobData.ether_balance === 'bigint' ? Number(jobData.ether_balance) : Number(jobData.ether_balance),
|
|
411
488
|
token_balance: typeof jobData.token_balance === 'bigint' ? Number(jobData.token_balance) : Number(jobData.token_balance),
|
|
412
489
|
};
|
|
490
|
+
console.log('jobDataForApi', jobDataForApi);
|
|
413
491
|
const res = await client.post('/api/jobs', [jobDataForApi], {
|
|
414
492
|
headers: { 'Content-Type': 'application/json', 'X-API-KEY': apiKey },
|
|
415
493
|
});
|
package/dist/config.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ethers } from 'ethers';
|
|
1
2
|
export interface SDKConfig {
|
|
2
3
|
apiKey: string;
|
|
3
4
|
apiUrl: string;
|
|
@@ -8,12 +9,23 @@ export declare const CONTRACT_ADDRESSES_BY_CHAIN: Record<string, {
|
|
|
8
9
|
safeFactory?: string;
|
|
9
10
|
safeModule?: string;
|
|
10
11
|
safeSingleton?: string;
|
|
12
|
+
multisendCallOnly?: string;
|
|
13
|
+
rpcUrl?: string;
|
|
11
14
|
}>;
|
|
12
15
|
export declare function getConfig(): SDKConfig;
|
|
16
|
+
/**
|
|
17
|
+
* Get RPC provider for a given chain ID using SDK-configured RPC URLs
|
|
18
|
+
* This ensures reliable connection even if user's RPC fails
|
|
19
|
+
* @param chainId - Chain ID as string or number
|
|
20
|
+
* @returns ethers.JsonRpcProvider instance or null if chain not supported
|
|
21
|
+
*/
|
|
22
|
+
export declare function getRpcProvider(chainId: string | number | undefined): ethers.JsonRpcProvider | null;
|
|
13
23
|
export declare function getChainAddresses(chainId: string | number | undefined): {
|
|
14
24
|
gasRegistry: string;
|
|
15
25
|
jobRegistry: string;
|
|
16
26
|
safeFactory: string | undefined;
|
|
17
27
|
safeModule: string | undefined;
|
|
18
28
|
safeSingleton: string | undefined;
|
|
29
|
+
multisendCallOnly: string | undefined;
|
|
30
|
+
rpcUrl: string | undefined;
|
|
19
31
|
};
|
package/dist/config.js
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.CONTRACT_ADDRESSES_BY_CHAIN = void 0;
|
|
6
6
|
exports.getConfig = getConfig;
|
|
7
|
+
exports.getRpcProvider = getRpcProvider;
|
|
7
8
|
exports.getChainAddresses = getChainAddresses;
|
|
9
|
+
const ethers_1 = require("ethers");
|
|
8
10
|
// Contract addresses per chain
|
|
9
11
|
// Keyed by chainId as string to avoid bigint conversions throughout the SDK
|
|
10
12
|
exports.CONTRACT_ADDRESSES_BY_CHAIN = {
|
|
@@ -15,6 +17,8 @@ exports.CONTRACT_ADDRESSES_BY_CHAIN = {
|
|
|
15
17
|
jobRegistry: '0x476ACc7949a95e31144cC84b8F6BC7abF0967E4b',
|
|
16
18
|
safeFactory: '0x04359eDC46Cd6C6BD7F6359512984222BE10F8Be',
|
|
17
19
|
safeModule: '0xa0bC1477cfc452C05786262c377DE51FB8bc4669',
|
|
20
|
+
multisendCallOnly: '0x9641d764fc13c8B624c04430C7356C1C7C8102e2',
|
|
21
|
+
rpcUrl: 'https://sepolia.optimism.io',
|
|
18
22
|
},
|
|
19
23
|
// Ethereum Sepolia (11155111) - Ethereum Sepolia Testnet
|
|
20
24
|
'11155111': {
|
|
@@ -22,6 +26,8 @@ exports.CONTRACT_ADDRESSES_BY_CHAIN = {
|
|
|
22
26
|
jobRegistry: '0x476ACc7949a95e31144cC84b8F6BC7abF0967E4b',
|
|
23
27
|
safeFactory: '0xdf76E2A796a206D877086c717979054544B1D9Bc',
|
|
24
28
|
safeModule: '0xa0bC1477cfc452C05786262c377DE51FB8bc4669',
|
|
29
|
+
multisendCallOnly: '0x9641d764fc13c8B624c04430C7356C1C7C8102e2',
|
|
30
|
+
rpcUrl: 'https://rpc.sepolia.org',
|
|
25
31
|
},
|
|
26
32
|
// Arbitrum Sepolia (421614) - Arbitrum Sepolia Testnet
|
|
27
33
|
'421614': {
|
|
@@ -29,6 +35,8 @@ exports.CONTRACT_ADDRESSES_BY_CHAIN = {
|
|
|
29
35
|
jobRegistry: '0x476ACc7949a95e31144cC84b8F6BC7abF0967E4b',
|
|
30
36
|
safeFactory: '0x04359eDC46Cd6C6BD7F6359512984222BE10F8Be',
|
|
31
37
|
safeModule: '0xa0bC1477cfc452C05786262c377DE51FB8bc4669',
|
|
38
|
+
multisendCallOnly: '0x9641d764fc13c8B624c04430C7356C1C7C8102e2',
|
|
39
|
+
rpcUrl: 'https://sepolia-rollup.arbitrum.io/rpc',
|
|
32
40
|
// safeSingleton can be provided per deployment (Safe or SafeL2)
|
|
33
41
|
},
|
|
34
42
|
// Base Sepolia (84532) - Base Sepolia Testnet
|
|
@@ -37,12 +45,16 @@ exports.CONTRACT_ADDRESSES_BY_CHAIN = {
|
|
|
37
45
|
jobRegistry: '0x476ACc7949a95e31144cC84b8F6BC7abF0967E4b',
|
|
38
46
|
safeFactory: '0x04359eDC46Cd6C6BD7F6359512984222BE10F8Be',
|
|
39
47
|
safeModule: '0xa0bC1477cfc452C05786262c377DE51FB8bc4669',
|
|
48
|
+
multisendCallOnly: '0x9641d764fc13c8B624c04430C7356C1C7C8102e2',
|
|
49
|
+
rpcUrl: 'https://sepolia.base.org',
|
|
40
50
|
},
|
|
41
51
|
// MAINNET CONFIGURATIONS
|
|
42
52
|
// Arbitrum One (42161) - Mainnet
|
|
43
53
|
'42161': {
|
|
44
54
|
gasRegistry: '0x93dDB2307F3Af5df85F361E5Cddd898Acd3d132d',
|
|
45
55
|
jobRegistry: '0xAf1189aFd1F1880F09AeC3Cbc32cf415c735C710',
|
|
56
|
+
multisendCallOnly: '0x9641d764fc13c8B624c04430C7356C1C7C8102e2',
|
|
57
|
+
rpcUrl: 'https://arb1.arbitrum.io/rpc',
|
|
46
58
|
},
|
|
47
59
|
// Default/fallbacks can be extended as needed for other networks
|
|
48
60
|
};
|
|
@@ -52,6 +64,19 @@ function getConfig() {
|
|
|
52
64
|
apiUrl: '',
|
|
53
65
|
};
|
|
54
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Get RPC provider for a given chain ID using SDK-configured RPC URLs
|
|
69
|
+
* This ensures reliable connection even if user's RPC fails
|
|
70
|
+
* @param chainId - Chain ID as string or number
|
|
71
|
+
* @returns ethers.JsonRpcProvider instance or null if chain not supported
|
|
72
|
+
*/
|
|
73
|
+
function getRpcProvider(chainId) {
|
|
74
|
+
const { rpcUrl } = getChainAddresses(chainId);
|
|
75
|
+
if (!rpcUrl) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return new ethers_1.ethers.JsonRpcProvider(rpcUrl);
|
|
79
|
+
}
|
|
55
80
|
function getChainAddresses(chainId) {
|
|
56
81
|
const chainKey = String(chainId ?? '');
|
|
57
82
|
const mapped = exports.CONTRACT_ADDRESSES_BY_CHAIN[chainKey];
|
|
@@ -61,5 +86,7 @@ function getChainAddresses(chainId) {
|
|
|
61
86
|
safeFactory: mapped ? mapped.safeFactory : '',
|
|
62
87
|
safeModule: mapped ? mapped.safeModule : '',
|
|
63
88
|
safeSingleton: mapped ? mapped.safeSingleton : '',
|
|
89
|
+
multisendCallOnly: mapped ? mapped.multisendCallOnly : '',
|
|
90
|
+
rpcUrl: mapped ? mapped.rpcUrl : '',
|
|
64
91
|
};
|
|
65
92
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -33,6 +33,11 @@ export declare enum ArgType {
|
|
|
33
33
|
Dynamic = "dynamic"
|
|
34
34
|
}
|
|
35
35
|
export type WalletMode = 'regular' | 'safe';
|
|
36
|
+
export interface SafeTransaction {
|
|
37
|
+
to: string;
|
|
38
|
+
value: string;
|
|
39
|
+
data: string;
|
|
40
|
+
}
|
|
36
41
|
export type CreateJobInput = (TimeBasedJobInput & {
|
|
37
42
|
jobType: JobType.Time;
|
|
38
43
|
argType: ArgType.Static | ArgType.Dynamic;
|
|
@@ -67,6 +72,13 @@ export interface TimeBasedJobInput {
|
|
|
67
72
|
* Required if walletMode is 'safe'.
|
|
68
73
|
*/
|
|
69
74
|
safeAddress?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Array of transactions for safe wallet jobs with static parameters.
|
|
77
|
+
* If length === 1: single call (operation 0)
|
|
78
|
+
* If length > 1: batch call via multisend (operation 1)
|
|
79
|
+
* Only used when walletMode is 'safe' and argType is 'static'.
|
|
80
|
+
*/
|
|
81
|
+
safeTransactions?: SafeTransaction[];
|
|
70
82
|
}
|
|
71
83
|
export interface EventBasedJobInput {
|
|
72
84
|
jobTitle: string;
|
|
@@ -92,6 +104,13 @@ export interface EventBasedJobInput {
|
|
|
92
104
|
* Required if walletMode is 'safe'.
|
|
93
105
|
*/
|
|
94
106
|
safeAddress?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Array of transactions for safe wallet jobs with static parameters.
|
|
109
|
+
* If length === 1: single call (operation 0)
|
|
110
|
+
* If length > 1: batch call via multisend (operation 1)
|
|
111
|
+
* Only used when walletMode is 'safe' and argType is 'static'.
|
|
112
|
+
*/
|
|
113
|
+
safeTransactions?: SafeTransaction[];
|
|
95
114
|
}
|
|
96
115
|
export interface ConditionBasedJobInput {
|
|
97
116
|
jobTitle: string;
|
|
@@ -119,6 +138,13 @@ export interface ConditionBasedJobInput {
|
|
|
119
138
|
* Required if walletMode is 'safe'.
|
|
120
139
|
*/
|
|
121
140
|
safeAddress?: string;
|
|
141
|
+
/**
|
|
142
|
+
* Array of transactions for safe wallet jobs with static parameters.
|
|
143
|
+
* If length === 1: single call (operation 0)
|
|
144
|
+
* If length > 1: batch call via multisend (operation 1)
|
|
145
|
+
* Only used when walletMode is 'safe' and argType is 'static'.
|
|
146
|
+
*/
|
|
147
|
+
safeTransactions?: SafeTransaction[];
|
|
122
148
|
}
|
|
123
149
|
export interface CreateJobData {
|
|
124
150
|
job_id: string;
|
package/dist/utils/validation.js
CHANGED
|
@@ -68,6 +68,26 @@ function validateStaticArguments(abiString, targetFunction, args, fieldPrefix =
|
|
|
68
68
|
throw new errors_1.ValidationError(`${fieldPrefix}Args`, 'All function arguments are required for static argument type.');
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
+
function validateSafeTransactions(transactions) {
|
|
72
|
+
if (!transactions || !Array.isArray(transactions) || transactions.length === 0) {
|
|
73
|
+
throw new errors_1.ValidationError('safeTransactions', 'safeTransactions array is required and must contain at least one transaction for static safe wallet jobs.');
|
|
74
|
+
}
|
|
75
|
+
for (let i = 0; i < transactions.length; i++) {
|
|
76
|
+
const tx = transactions[i];
|
|
77
|
+
if (!tx || typeof tx !== 'object') {
|
|
78
|
+
throw new errors_1.ValidationError('safeTransactions', `Transaction at index ${i} is invalid.`);
|
|
79
|
+
}
|
|
80
|
+
if (!isNonEmptyString(tx.to) || !ethers_1.ethers.isAddress(tx.to)) {
|
|
81
|
+
throw new errors_1.ValidationError('safeTransactions', `Transaction at index ${i}: 'to' must be a valid Ethereum address.`);
|
|
82
|
+
}
|
|
83
|
+
if (typeof tx.value !== 'string') {
|
|
84
|
+
throw new errors_1.ValidationError('safeTransactions', `Transaction at index ${i}: 'value' must be a string representing wei amount.`);
|
|
85
|
+
}
|
|
86
|
+
if (!isNonEmptyString(tx.data) || !tx.data.startsWith('0x')) {
|
|
87
|
+
throw new errors_1.ValidationError('safeTransactions', `Transaction at index ${i}: 'data' must be a hex string starting with 0x.`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
71
91
|
function validateTimeBasedJobInput(input, argType) {
|
|
72
92
|
if (!isNonEmptyString(input.jobTitle)) {
|
|
73
93
|
throw new errors_1.ValidationError('jobTitle', 'Job title is required.');
|
|
@@ -103,18 +123,37 @@ function validateTimeBasedJobInput(input, argType) {
|
|
|
103
123
|
else {
|
|
104
124
|
throw new errors_1.ValidationError('scheduleType', 'scheduleType must be one of interval | cron | specific.');
|
|
105
125
|
}
|
|
106
|
-
if (input.walletMode !== 'safe') {
|
|
107
|
-
validateContractBasics(input.targetContractAddress, input.abi, input.targetFunction, 'contract');
|
|
108
|
-
}
|
|
109
126
|
// Arg type checks
|
|
110
127
|
const isDynamic = argType === 'dynamic' || argType === 2;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
128
|
+
// Safe wallet mode validation
|
|
129
|
+
if (input.walletMode === 'safe') {
|
|
130
|
+
// Ensure static and dynamic are mutually exclusive
|
|
131
|
+
if (isDynamic && input.safeTransactions && input.safeTransactions.length > 0) {
|
|
132
|
+
throw new errors_1.ValidationError('safeTransactions', 'Cannot provide both dynamicArgumentsScriptUrl and safeTransactions. Use one or the other.');
|
|
133
|
+
}
|
|
134
|
+
if (!isDynamic && !input.safeTransactions) {
|
|
135
|
+
throw new errors_1.ValidationError('safeTransactions', 'For static safe wallet jobs, either provide safeTransactions or use dynamicArgumentsScriptUrl for dynamic jobs.');
|
|
136
|
+
}
|
|
137
|
+
if (isDynamic) {
|
|
138
|
+
// Dynamic safe wallet job
|
|
139
|
+
if (!isNonEmptyString(input.dynamicArgumentsScriptUrl) || !isValidUrl(input.dynamicArgumentsScriptUrl)) {
|
|
140
|
+
throw new errors_1.ValidationError('contractIpfs', 'IPFS Code URL is required and must be valid for dynamic argument type.');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
// Static safe wallet job
|
|
145
|
+
validateSafeTransactions(input.safeTransactions);
|
|
114
146
|
}
|
|
115
147
|
}
|
|
116
148
|
else {
|
|
117
|
-
|
|
149
|
+
// Regular wallet mode
|
|
150
|
+
validateContractBasics(input.targetContractAddress, input.abi, input.targetFunction, 'contract');
|
|
151
|
+
if (isDynamic) {
|
|
152
|
+
if (!isNonEmptyString(input.dynamicArgumentsScriptUrl) || !isValidUrl(input.dynamicArgumentsScriptUrl)) {
|
|
153
|
+
throw new errors_1.ValidationError('contractIpfs', 'IPFS Code URL is required and must be valid for dynamic argument type.');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
118
157
|
validateStaticArguments(input.abi, input.targetFunction, input.arguments, 'contract');
|
|
119
158
|
}
|
|
120
159
|
}
|
|
@@ -136,17 +175,37 @@ function validateEventBasedJobInput(input, argType) {
|
|
|
136
175
|
throw new errors_1.ValidationError('triggerChainId', 'Trigger chain ID is required.');
|
|
137
176
|
}
|
|
138
177
|
validateContractBasics(input.triggerContractAddress, input.abi, input.triggerEvent, 'eventContract');
|
|
139
|
-
|
|
140
|
-
validateContractBasics(input.targetContractAddress, input.abi, input.targetFunction, 'contract');
|
|
141
|
-
}
|
|
178
|
+
// Arg type checks
|
|
142
179
|
const isDynamic = argType === 'dynamic' || argType === 2;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
180
|
+
// Safe wallet mode validation
|
|
181
|
+
if (input.walletMode === 'safe') {
|
|
182
|
+
// Ensure static and dynamic are mutually exclusive
|
|
183
|
+
if (isDynamic && input.safeTransactions && input.safeTransactions.length > 0) {
|
|
184
|
+
throw new errors_1.ValidationError('safeTransactions', 'Cannot provide both dynamicArgumentsScriptUrl and safeTransactions. Use one or the other.');
|
|
185
|
+
}
|
|
186
|
+
if (!isDynamic && !input.safeTransactions) {
|
|
187
|
+
throw new errors_1.ValidationError('safeTransactions', 'For static safe wallet jobs, either provide safeTransactions or use dynamicArgumentsScriptUrl for dynamic jobs.');
|
|
188
|
+
}
|
|
189
|
+
if (isDynamic) {
|
|
190
|
+
// Dynamic safe wallet job
|
|
191
|
+
if (!isNonEmptyString(input.dynamicArgumentsScriptUrl) || !isValidUrl(input.dynamicArgumentsScriptUrl)) {
|
|
192
|
+
throw new errors_1.ValidationError('contractIpfs', 'IPFS Code URL is required and must be valid for dynamic argument type.');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
// Static safe wallet job
|
|
197
|
+
validateSafeTransactions(input.safeTransactions);
|
|
146
198
|
}
|
|
147
199
|
}
|
|
148
200
|
else {
|
|
149
|
-
|
|
201
|
+
// Regular wallet mode
|
|
202
|
+
validateContractBasics(input.targetContractAddress, input.abi, input.targetFunction, 'contract');
|
|
203
|
+
if (isDynamic) {
|
|
204
|
+
if (!isNonEmptyString(input.dynamicArgumentsScriptUrl) || !isValidUrl(input.dynamicArgumentsScriptUrl)) {
|
|
205
|
+
throw new errors_1.ValidationError('contractIpfs', 'IPFS Code URL is required and must be valid for dynamic argument type.');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
150
209
|
validateStaticArguments(input.abi, input.targetFunction, input.arguments, 'contract');
|
|
151
210
|
}
|
|
152
211
|
}
|
|
@@ -184,17 +243,37 @@ function validateConditionBasedJobInput(input, argType) {
|
|
|
184
243
|
throw new errors_1.ValidationError('contractLimits', 'Value is required.');
|
|
185
244
|
}
|
|
186
245
|
}
|
|
187
|
-
|
|
188
|
-
validateContractBasics(input.targetContractAddress, input.abi, input.targetFunction, 'contract');
|
|
189
|
-
}
|
|
246
|
+
// Arg type checks
|
|
190
247
|
const isDynamic = argType === 'dynamic' || argType === 2;
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
248
|
+
// Safe wallet mode validation
|
|
249
|
+
if (input.walletMode === 'safe') {
|
|
250
|
+
// Ensure static and dynamic are mutually exclusive
|
|
251
|
+
if (isDynamic && input.safeTransactions && input.safeTransactions.length > 0) {
|
|
252
|
+
throw new errors_1.ValidationError('safeTransactions', 'Cannot provide both dynamicArgumentsScriptUrl and safeTransactions. Use one or the other.');
|
|
253
|
+
}
|
|
254
|
+
if (!isDynamic && !input.safeTransactions) {
|
|
255
|
+
throw new errors_1.ValidationError('safeTransactions', 'For static safe wallet jobs, either provide safeTransactions or use dynamicArgumentsScriptUrl for dynamic jobs.');
|
|
256
|
+
}
|
|
257
|
+
if (isDynamic) {
|
|
258
|
+
// Dynamic safe wallet job
|
|
259
|
+
if (!isNonEmptyString(input.dynamicArgumentsScriptUrl) || !isValidUrl(input.dynamicArgumentsScriptUrl)) {
|
|
260
|
+
throw new errors_1.ValidationError('contractIpfs', 'IPFS Code URL is required and must be valid for dynamic argument type.');
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
// Static safe wallet job
|
|
265
|
+
validateSafeTransactions(input.safeTransactions);
|
|
194
266
|
}
|
|
195
267
|
}
|
|
196
268
|
else {
|
|
197
|
-
|
|
269
|
+
// Regular wallet mode
|
|
270
|
+
validateContractBasics(input.targetContractAddress, input.abi, input.targetFunction, 'contract');
|
|
271
|
+
if (isDynamic) {
|
|
272
|
+
if (!isNonEmptyString(input.dynamicArgumentsScriptUrl) || !isValidUrl(input.dynamicArgumentsScriptUrl)) {
|
|
273
|
+
throw new errors_1.ValidationError('contractIpfs', 'IPFS Code URL is required and must be valid for dynamic argument type.');
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
198
277
|
validateStaticArguments(input.abi, input.targetFunction, input.arguments, 'contract');
|
|
199
278
|
}
|
|
200
279
|
}
|