@cfxdevkit/executor 1.0.8 → 1.0.10

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/index.cjs CHANGED
@@ -269,8 +269,8 @@ var Executor = class {
269
269
  };
270
270
 
271
271
  // src/keeper-client.ts
272
+ var import_contracts = require("@cfxdevkit/contracts");
272
273
  var import_core2 = require("@cfxdevkit/core");
273
- var import_protocol = require("@cfxdevkit/protocol");
274
274
  var import_viem = require("viem");
275
275
  var import_accounts = require("viem/accounts");
276
276
  function decodeAmountOut(logs, owner) {
@@ -329,7 +329,7 @@ var KeeperClientImpl = class {
329
329
  const job = await this.withTimeout(
330
330
  (_signal) => this.publicClient.readContract({
331
331
  address: this.contractAddress,
332
- abi: import_protocol.AUTOMATION_MANAGER_ABI,
332
+ abi: import_contracts.AUTOMATION_MANAGER_ABI,
333
333
  functionName: "getJob",
334
334
  args: [onChainJobId]
335
335
  })
@@ -352,7 +352,7 @@ var KeeperClientImpl = class {
352
352
  const isPaused = await this.withTimeout(
353
353
  (_signal) => this.publicClient.readContract({
354
354
  address: this.contractAddress,
355
- abi: import_protocol.AUTOMATION_MANAGER_ABI,
355
+ abi: import_contracts.AUTOMATION_MANAGER_ABI,
356
356
  functionName: "paused"
357
357
  })
358
358
  );
@@ -423,7 +423,7 @@ var KeeperClientImpl = class {
423
423
  await this.withTimeout(
424
424
  (_signal) => this.publicClient.simulateContract({
425
425
  address: this.contractAddress,
426
- abi: import_protocol.AUTOMATION_MANAGER_ABI,
426
+ abi: import_contracts.AUTOMATION_MANAGER_ABI,
427
427
  functionName: "executeLimitOrder",
428
428
  args: [jobId, this.swappiRouter, swapCalldata],
429
429
  account: this.account.address
@@ -440,7 +440,7 @@ var KeeperClientImpl = class {
440
440
  const hash = await this.withTimeout(
441
441
  (_signal) => this.walletClient.writeContract({
442
442
  address: this.contractAddress,
443
- abi: import_protocol.AUTOMATION_MANAGER_ABI,
443
+ abi: import_contracts.AUTOMATION_MANAGER_ABI,
444
444
  functionName: "executeLimitOrder",
445
445
  args: [jobId, this.swappiRouter, swapCalldata],
446
446
  chain: void 0,
@@ -463,7 +463,7 @@ var KeeperClientImpl = class {
463
463
  await this.withTimeout(
464
464
  (_signal) => this.publicClient.simulateContract({
465
465
  address: this.contractAddress,
466
- abi: import_protocol.AUTOMATION_MANAGER_ABI,
466
+ abi: import_contracts.AUTOMATION_MANAGER_ABI,
467
467
  functionName: "executeLimitOrder",
468
468
  args: [jobId, this.swappiRouter, swapCalldata],
469
469
  account: this.account.address
@@ -485,7 +485,7 @@ var KeeperClientImpl = class {
485
485
  await this.preflightCheck();
486
486
  const onChainParams = await this.publicClient.readContract({
487
487
  address: this.contractAddress,
488
- abi: import_protocol.AUTOMATION_MANAGER_ABI,
488
+ abi: import_contracts.AUTOMATION_MANAGER_ABI,
489
489
  functionName: "getDCAJob",
490
490
  args: [jobId]
491
491
  });
@@ -517,7 +517,7 @@ var KeeperClientImpl = class {
517
517
  await this.withTimeout(
518
518
  (_signal) => this.publicClient.simulateContract({
519
519
  address: this.contractAddress,
520
- abi: import_protocol.AUTOMATION_MANAGER_ABI,
520
+ abi: import_contracts.AUTOMATION_MANAGER_ABI,
521
521
  functionName: "executeDCATick",
522
522
  args: [jobId, this.swappiRouter, swapCalldata],
523
523
  account: this.account.address
@@ -534,7 +534,7 @@ var KeeperClientImpl = class {
534
534
  const hash = await this.withTimeout(
535
535
  (_signal) => this.walletClient.writeContract({
536
536
  address: this.contractAddress,
537
- abi: import_protocol.AUTOMATION_MANAGER_ABI,
537
+ abi: import_contracts.AUTOMATION_MANAGER_ABI,
538
538
  functionName: "executeDCATick",
539
539
  args: [jobId, this.swappiRouter, swapCalldata],
540
540
  chain: void 0,
@@ -557,7 +557,7 @@ var KeeperClientImpl = class {
557
557
  await this.withTimeout(
558
558
  (_signal) => this.publicClient.simulateContract({
559
559
  address: this.contractAddress,
560
- abi: import_protocol.AUTOMATION_MANAGER_ABI,
560
+ abi: import_contracts.AUTOMATION_MANAGER_ABI,
561
561
  functionName: "executeDCATick",
562
562
  args: [jobId, this.swappiRouter, swapCalldata],
563
563
  account: this.account.address
@@ -571,7 +571,7 @@ var KeeperClientImpl = class {
571
571
  const amountOut = decodeAmountOut(receipt.logs, owner);
572
572
  const postTickParams = await this.publicClient.readContract({
573
573
  address: this.contractAddress,
574
- abi: import_protocol.AUTOMATION_MANAGER_ABI,
574
+ abi: import_contracts.AUTOMATION_MANAGER_ABI,
575
575
  functionName: "getDCAJob",
576
576
  args: [jobId]
577
577
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/executor.ts","../src/keeper-client.ts","../src/logger.ts","../src/price-checker.ts","../src/retry-queue.ts","../src/safety-guard.ts"],"sourcesContent":["/**\n * @cfxdevkit/executor\n *\n * On-chain strategy execution engine for Conflux DevKit.\n *\n * Provides the runtime primitives needed to build keepers, bots, or AI agents\n * that execute on-chain strategies (limit orders, DCA, TWAP, spot swaps).\n *\n * Key exports:\n * - Job types (LimitOrderJob, DCAJob, TWAPJob, SwapJob) + params\n * - Strategy types (LimitOrderStrategy, DCAStrategy, TWAPStrategy, SwapStrategy)\n * - SafetyGuard — circuit-breaker / swap-cap / retry-cap\n * - RetryQueue — exponential backoff with jitter\n * - PriceChecker — pluggable price source + condition evaluation\n * - KeeperClient interface + KeeperClientImpl (viem / AutomationManager)\n * - Executor — orchestrator that ties all of the above together\n * - AutomationLogger — injectable logger interface (no runtime dep)\n */\n\n// ── Executor + interfaces ─────────────────────────────────────────────────────\nexport type { ExecutorOptions, JobStore, KeeperClient } from './executor.js';\nexport { Executor } from './executor.js';\n// ── KeeperClientImpl (viem) ───────────────────────────────────────────────────\nexport type { KeeperClientConfig } from './keeper-client.js';\nexport { KeeperClientImpl } from './keeper-client.js';\n\n// ── KeeperClient interface ────────────────────────────────────────────────────\nexport type { KeeperClient as IKeeperClient } from './keeper-interface.js';\n// ── Logger ────────────────────────────────────────────────────────────────────\nexport type { AutomationLogger } from './logger.js';\nexport { noopLogger } from './logger.js';\n\n// ── PriceChecker ──────────────────────────────────────────────────────────────\nexport type {\n DecimalsResolver,\n PriceCheckResult,\n PriceSource,\n} from './price-checker.js';\nexport { PriceChecker } from './price-checker.js';\n// ── RetryQueue ────────────────────────────────────────────────────────────────\nexport { RetryQueue } from './retry-queue.js';\n// ── SafetyGuard ───────────────────────────────────────────────────────────────\nexport { DEFAULT_SAFETY_CONFIG, SafetyGuard } from './safety-guard.js';\n// ── Strategy types ────────────────────────────────────────────────────────────\nexport type {\n DCAStrategy,\n LimitOrderStrategy,\n Strategy,\n SwapStrategy,\n TWAPStrategy,\n} from './strategies.js';\n// ── Types ─────────────────────────────────────────────────────────────────────\nexport type {\n BaseJob,\n DCAJob,\n DCAParams,\n Job,\n JobStatus,\n JobType,\n LimitOrderJob,\n LimitOrderParams,\n SafetyCheckResult,\n SafetyConfig,\n SafetyViolation,\n SwapJob,\n SwapParams,\n TWAPJob,\n TWAPParams,\n} from './types.js';\n","import { logger } from '@cfxdevkit/core';\nimport type { PriceChecker } from './price-checker.js';\nimport type { RetryQueue } from './retry-queue.js';\nimport type { SafetyGuard } from './safety-guard.js';\nimport type { DCAJob, Job, LimitOrderJob } from './types.js';\n\n// Minimal on-chain execution interface – fulfilled by the viem-based KeeperClientImpl\nexport interface KeeperClient {\n executeLimitOrder(\n jobId: string,\n owner: string,\n params: LimitOrderJob['params']\n ): Promise<{ txHash: string; amountOut?: string | null }>;\n\n executeDCATick(\n jobId: string,\n owner: string,\n params: DCAJob['params']\n ): Promise<{\n txHash: string;\n amountOut?: string | null;\n nextExecutionSec: number;\n }>;\n\n /** Read the terminal status of a job from the contract. */\n getOnChainStatus(\n jobId: `0x${string}`\n ): Promise<'active' | 'executed' | 'cancelled' | 'expired'>;\n}\n\n// Persistent job store – fulfilled by the DB-backed store in the application layer\nexport interface JobStore {\n getActiveJobs(): Promise<Job[]>;\n markActive(jobId: string): Promise<void>;\n markExecuted(\n jobId: string,\n txHash: string,\n amountOut?: string | null\n ): Promise<void>;\n /**\n * Record one completed DCA tick.\n * Updates swapsCompleted + nextExecution in params_json and inserts an\n * execution record. Sets status → 'executed' when all swaps are done,\n * otherwise keeps the job 'active' so subsequent ticks can run.\n */\n markDCATick(\n jobId: string,\n txHash: string,\n newSwapsCompleted: number,\n nextExecution: number,\n amountOut?: string | null\n ): Promise<void>;\n markFailed(jobId: string, error: string): Promise<void>;\n incrementRetry(jobId: string): Promise<void>;\n markExpired(jobId: string): Promise<void>;\n /** Mark a job cancelled — use when on-chain status is CANCELLED. */\n markCancelled(jobId: string): Promise<void>;\n /** Record the latest error message without changing status or incrementing retries. */\n updateLastError(jobId: string, error: string): Promise<void>;\n}\n\nexport interface ExecutorOptions {\n dryRun?: boolean;\n}\n\n/**\n * Executor – evaluates active jobs and submits on-chain transactions when\n * conditions are met and the SafetyGuard approves.\n */\nexport class Executor {\n private priceChecker: PriceChecker;\n private safetyGuard: SafetyGuard;\n private retryQueue: RetryQueue;\n private keeperClient: KeeperClient;\n private jobStore: JobStore;\n private dryRun: boolean;\n\n constructor(\n priceChecker: PriceChecker,\n safetyGuard: SafetyGuard,\n retryQueue: RetryQueue,\n keeperClient: KeeperClient,\n jobStore: JobStore,\n options: ExecutorOptions = {}\n ) {\n this.priceChecker = priceChecker;\n this.safetyGuard = safetyGuard;\n this.retryQueue = retryQueue;\n this.keeperClient = keeperClient;\n this.jobStore = jobStore;\n this.dryRun = options.dryRun ?? false;\n }\n\n /**\n * Process a single job tick.\n */\n async processTick(job: Job): Promise<void> {\n // 1. Quick expiry guard\n if (job.expiresAt !== null && Date.now() >= job.expiresAt) {\n logger.info(`[Executor] job ${job.id} expired`);\n await this.jobStore.markExpired(job.id);\n return;\n }\n\n try {\n // Transition pending → active on first pickup\n if (job.status === 'pending') {\n await this.jobStore.markActive(job.id);\n }\n\n if (job.type === 'limit_order') {\n await this._processLimitOrder(job as LimitOrderJob);\n } else if (job.type === 'dca') {\n await this._processDCA(job as DCAJob);\n }\n // 'twap' and 'swap' are not yet implemented — silently skip\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n\n // ── Transient errors — handle silently without logging as ERROR ──────────\n\n // PriceConditionNotMet is a transient race: the off-chain check passed but\n // the on-chain oracle ticked back before the tx was mined.\n if (message.includes('PriceConditionNotMet')) {\n logger.debug(\n `[Executor] job ${job.id}: price condition no longer met at execution time — will retry next tick`\n );\n return;\n }\n\n // DCAIntervalNotReached means the on-chain interval timer hasn't elapsed yet.\n if (message.includes('DCAIntervalNotReached')) {\n logger.debug(\n `[Executor] job ${job.id}: DCA interval not yet reached at execution time — will retry next tick`\n );\n return;\n }\n\n // Receipt-not-found: the node didn't have the receipt indexed yet.\n if (\n message.includes('could not be found') ||\n message.includes('TransactionReceiptNotFoundError')\n ) {\n logger.debug(\n `[Executor] job ${job.id}: receipt not yet indexed — will retry next tick`\n );\n return;\n }\n\n // Slippage exceeded is transient: pool price moved between simulation and mine.\n if (message.includes('Slippage exceeded')) {\n logger.debug(\n `[Executor] job ${job.id}: slippage exceeded at execution time — will retry next tick`\n );\n await this.jobStore.incrementRetry(job.id);\n await this.jobStore.updateLastError(job.id, message);\n return;\n }\n\n // ── Unexpected errors — log full cause for diagnosis ─────────────────────\n if (err instanceof Error && (err as { cause?: unknown }).cause) {\n logger.error(\n { cause: (err as { cause?: unknown }).cause },\n '[Executor] raw error cause'\n );\n }\n\n // JobNotFound: on-chain job ID doesn't exist (contract redeployed).\n if (message.includes('JobNotFound')) {\n const ocId = (job as { onChainJobId?: string }).onChainJobId;\n logger.warn(\n { jobId: job.id, onChainJobId: ocId },\n '[Executor] JobNotFound — on_chain_job_id not found on current contract ' +\n '(likely left over from an old deployment) — marking cancelled'\n );\n await this.jobStore.markCancelled(job.id);\n return;\n }\n\n // JobNotActive: job is EXECUTED/CANCELLED/EXPIRED on-chain but DB is out of sync.\n if (message.includes('JobNotActive')) {\n const ocId = (job as { onChainJobId?: string }).onChainJobId as\n | `0x${string}`\n | undefined;\n let onChainStatus: 'active' | 'executed' | 'cancelled' | 'expired' =\n 'cancelled';\n if (ocId) {\n try {\n onChainStatus = await this.keeperClient.getOnChainStatus(ocId);\n } catch (statusErr) {\n logger.warn(\n { jobId: job.id, err: statusErr },\n '[Executor] could not read on-chain status — defaulting to cancelled'\n );\n }\n }\n if (onChainStatus === 'executed') {\n logger.warn(\n { jobId: job.id, onChainJobId: ocId },\n '[Executor] job EXECUTED on-chain but DB was out of sync — marking executed'\n );\n await this.jobStore.markExecuted(job.id, 'chain-sync');\n } else {\n logger.warn(\n { jobId: job.id, onChainJobId: ocId, onChainStatus },\n '[Executor] job is CANCELLED/EXPIRED on-chain — marking cancelled in DB'\n );\n await this.jobStore.markCancelled(job.id);\n }\n return;\n }\n\n logger.error(`[Executor] job ${job.id} failed: ${message}`);\n const nextRetries = job.retries + 1;\n if (job.retries < job.maxRetries) {\n await this.jobStore.incrementRetry(job.id);\n }\n if (nextRetries < job.maxRetries) {\n this.retryQueue.enqueue({ ...job, retries: nextRetries });\n }\n await this.jobStore.markFailed(job.id, message);\n }\n }\n\n /**\n * Process all active jobs + due retries in one tick.\n */\n async runAllTicks(): Promise<void> {\n const activeJobs = await this.jobStore.getActiveJobs();\n const retries = this.retryQueue.drainDue();\n\n const all = [...activeJobs, ...retries];\n logger.info(\n `[Executor] tick – ${activeJobs.length} active, ${retries.length} retries`\n );\n\n await Promise.allSettled(all.map((job) => this.processTick(job)));\n }\n\n // ─── Private ────────────────────────────────────────────────────────────────\n\n private async _processLimitOrder(job: LimitOrderJob): Promise<void> {\n const priceResult = await this.priceChecker.checkLimitOrder(job);\n\n if (!priceResult.conditionMet) {\n logger.info(\n {\n jobId: job.id,\n currentPrice: priceResult.currentPrice.toString(),\n targetPrice: priceResult.targetPrice.toString(),\n direction: job.params.direction,\n },\n `[Executor] limit-order ${job.id}: condition not met – waiting`\n );\n return;\n }\n\n const safetyResult = this.safetyGuard.check(job, {\n swapUsd: priceResult.swapUsd,\n });\n if (!safetyResult.ok) {\n logger.warn(\n { violation: safetyResult.violation },\n `[Executor] limit-order ${job.id} blocked by safety guard`\n );\n return;\n }\n\n if (this.dryRun) {\n logger.info(`[Executor][dryRun] would execute limit-order ${job.id}`);\n return;\n }\n\n logger.info(`[Executor] executing limit-order ${job.id}`);\n const onChainJobId = job.onChainJobId;\n if (!onChainJobId) {\n logger.warn(\n `[Executor] limit-order ${job.id} has no onChainJobId – skipping until registered on-chain`\n );\n return;\n }\n const { txHash, amountOut } = await this.keeperClient.executeLimitOrder(\n onChainJobId,\n job.owner,\n job.params\n );\n await this.jobStore.markExecuted(job.id, txHash, amountOut);\n logger.info(`[Executor] limit-order ${job.id} executed – tx ${txHash}`);\n }\n\n private async _processDCA(job: DCAJob): Promise<void> {\n const priceResult = await this.priceChecker.checkDCA(job);\n\n if (!priceResult.conditionMet) {\n logger.info(\n {\n jobId: job.id,\n nextExecution: job.params.nextExecution,\n now: Date.now(),\n secsRemaining: Math.round(\n (job.params.nextExecution - Date.now()) / 1000\n ),\n },\n `[Executor] DCA ${job.id}: interval not reached`\n );\n return;\n }\n\n const safetyResult = this.safetyGuard.check(job, {\n swapUsd: priceResult.swapUsd,\n });\n if (!safetyResult.ok) {\n logger.warn(\n { violation: safetyResult.violation },\n `[Executor] DCA ${job.id} blocked by safety guard`\n );\n return;\n }\n\n if (this.dryRun) {\n logger.info(`[Executor][dryRun] would execute DCA tick for ${job.id}`);\n return;\n }\n\n logger.info(`[Executor] executing DCA tick ${job.id}`);\n const onChainJobId = job.onChainJobId;\n if (!onChainJobId) {\n logger.warn(\n `[Executor] DCA job ${job.id} has no onChainJobId – skipping until registered on-chain`\n );\n return;\n }\n const { txHash, amountOut, nextExecutionSec } =\n await this.keeperClient.executeDCATick(\n onChainJobId,\n job.owner,\n job.params\n );\n\n const newSwapsCompleted = job.params.swapsCompleted + 1;\n // Use the on-chain nextExecution (returned by KeeperClient after reading\n // the contract state post-tick) rather than computing it locally.\n const nextExecutionMs = nextExecutionSec * 1000;\n await this.jobStore.markDCATick(\n job.id,\n txHash,\n newSwapsCompleted,\n nextExecutionMs,\n amountOut\n );\n\n logger.info(\n {\n jobId: job.id,\n txHash,\n swapsCompleted: newSwapsCompleted,\n total: job.params.totalSwaps,\n },\n `[Executor] DCA tick executed – ${newSwapsCompleted}/${job.params.totalSwaps}`\n );\n }\n}\n","/**\n * KeeperClient — viem implementation of the KeeperClient interface.\n *\n * Wraps AutomationManager contract calls for executeLimitOrder and executeDCATick.\n * The executor wallet is a keeper (not a custodian) — it cannot move user funds\n * unless the user has set an on-chain allowance for that specific swap.\n */\n\nimport { logger } from '@cfxdevkit/core';\nimport { AUTOMATION_MANAGER_ABI } from '@cfxdevkit/protocol';\nimport {\n type Address,\n createPublicClient,\n createWalletClient,\n type Hash,\n http,\n} from 'viem';\nimport { privateKeyToAccount } from 'viem/accounts';\nimport type { KeeperClient as IKeeperClient } from './executor.js';\nimport type { DCAJob, LimitOrderJob } from './types.js';\n\n// --------------------------------------------------------------------------\n// Helpers\n// --------------------------------------------------------------------------\n\n/**\n * Decode the amountOut from a swap receipt by finding the last ERC-20\n * Transfer event whose `to` address is the job owner (the swap recipient).\n * Returns the raw amount as a decimal string, or null if not found.\n */\nfunction decodeAmountOut(\n logs: readonly { topics: readonly string[]; data: string; address: string }[],\n owner: Address\n): string | null {\n const TRANSFER_TOPIC =\n '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';\n const ownerLower = owner.toLowerCase();\n // Walk logs in reverse — the last Transfer to owner is tokenOut\n for (let i = logs.length - 1; i >= 0; i--) {\n const log = logs[i];\n if (\n log.topics[0]?.toLowerCase() === TRANSFER_TOPIC &&\n log.topics[2] &&\n `0x${log.topics[2].slice(26)}`.toLowerCase() === ownerLower\n ) {\n try {\n return BigInt(log.data).toString();\n } catch {\n return null;\n }\n }\n }\n return null;\n}\n\n// --------------------------------------------------------------------------\n// Config\n// --------------------------------------------------------------------------\nexport interface KeeperClientConfig {\n /** RPC endpoint for Conflux eSpace */\n rpcUrl: string;\n /** Hex private key (0x-prefixed) of the keeper wallet */\n privateKey: `0x${string}`;\n /** Deployed AutomationManager contract address */\n contractAddress: Address;\n /**\n * Swappi router address.\n * Testnet: 0x873789aaf553fd0b4252d0d2b72c6331c47aff2e\n * Mainnet: 0xE37B52296b0bAA91412cD0Cd97975B0805037B84 (Swappi v2 — only address with deployed code;\n * old 0x62B0873... has no bytecode)\n */\n swappiRouter: Address;\n /**\n * Maximum gas price in gwei before aborting execution (circuit breaker).\n * Defaults to 1000 gwei.\n */\n maxGasPriceGwei?: bigint;\n /** Chain definition — pass the viem chain object (espaceTestnet / espaceMainnet) */\n chain: Parameters<typeof createWalletClient>[0]['chain'];\n /** RPC request timeout in milliseconds (applies to read/simulate/write calls). */\n rpcTimeoutMs?: number;\n}\n\n// --------------------------------------------------------------------------\n// KeeperClientImpl\n// --------------------------------------------------------------------------\nexport class KeeperClientImpl implements IKeeperClient {\n private readonly walletClient;\n private readonly publicClient;\n private readonly contractAddress: Address;\n private readonly swappiRouter: Address;\n private readonly maxGasPriceGwei: bigint;\n private readonly rpcTimeoutMs: number;\n private readonly account;\n\n constructor(config: KeeperClientConfig) {\n this.account = privateKeyToAccount(config.privateKey);\n this.contractAddress = config.contractAddress;\n this.swappiRouter = config.swappiRouter;\n this.maxGasPriceGwei = config.maxGasPriceGwei ?? 1000n;\n this.rpcTimeoutMs = config.rpcTimeoutMs ?? 120_000; // default 2 minutes\n\n this.publicClient = createPublicClient({\n chain: config.chain,\n transport: http(config.rpcUrl),\n });\n\n this.walletClient = createWalletClient({\n chain: config.chain,\n transport: http(config.rpcUrl),\n account: this.account,\n });\n }\n\n private withTimeout<T>(\n fn: (signal: AbortSignal) => Promise<T>,\n timeoutMs = this.rpcTimeoutMs\n ): Promise<T> {\n const controller = new AbortController();\n const id = setTimeout(() => controller.abort(), timeoutMs);\n return fn(controller.signal).finally(() => clearTimeout(id));\n }\n\n // --------------------------------------------------------------------------\n // Helpers\n // --------------------------------------------------------------------------\n\n /**\n * Query the on-chain status of a job.\n * Returns one of: 'active' | 'executed' | 'cancelled' | 'expired'.\n * Throws if the contract call fails (e.g. job not found).\n */\n async getOnChainStatus(\n onChainJobId: `0x${string}`\n ): Promise<'active' | 'executed' | 'cancelled' | 'expired'> {\n const job = (await this.withTimeout((_signal) =>\n this.publicClient.readContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'getJob',\n args: [onChainJobId],\n })\n )) as { status: number };\n // JobStatus enum: 0=ACTIVE, 1=EXECUTED, 2=CANCELLED, 3=EXPIRED\n switch (job.status) {\n case 0:\n return 'active';\n case 1:\n return 'executed';\n case 2:\n return 'cancelled';\n case 3:\n return 'expired';\n default:\n return 'cancelled'; // unknown → treat as cancelled (stop retrying)\n }\n }\n\n /** Guard: abort if chain is paused or gas price is too high */\n private async preflightCheck(): Promise<void> {\n // Check on-chain pause flag\n const isPaused = await this.withTimeout((_signal) =>\n this.publicClient.readContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'paused',\n })\n );\n if (isPaused) {\n throw new Error('AutomationManager is paused on-chain');\n }\n\n // Check gas price circuit breaker\n const gasPrice = await Promise.race([\n this.publicClient.getGasPrice(),\n new Promise<never>((_, rej) =>\n setTimeout(\n () => rej(new Error('getGasPrice timeout')),\n this.rpcTimeoutMs\n )\n ),\n ]);\n const gasPriceGwei = gasPrice / 1_000_000_000n;\n if (gasPriceGwei > this.maxGasPriceGwei) {\n throw new Error(\n `Gas price ${gasPriceGwei} gwei exceeds cap ${this.maxGasPriceGwei} gwei`\n );\n }\n }\n\n /**\n * Build minimal swap calldata for a token-in/token-out pair via Swappi.\n *\n * In a production keeper the calldata would be built via a DEX aggregator\n * (e.g. OKX DEX API) to get optimal routing. For the MVP we encode the\n * simplest Swappi path: `swapExactTokensForTokens(amountIn, minOut, path, to, deadline)`.\n *\n * NOTE: The AutomationManager does NOT use this calldata for custody;\n * it only uses it to call the router on behalf of the user's pre-approved allowance.\n */\n private buildSwapCalldata(\n tokenIn: Address,\n tokenOut: Address,\n amountIn: bigint,\n minAmountOut: bigint,\n recipient: Address\n ): `0x${string}` {\n // swapExactTokensForTokens(uint256,uint256,address[],address,uint256)\n // selector = 0x38ed1739\n const selector = '38ed1739';\n const deadline = BigInt(Math.floor(Date.now() / 1000) + 1800); // 30 min\n const encode256 = (n: bigint) => n.toString(16).padStart(64, '0');\n const encodeAddr = (a: string) =>\n a.slice(2).toLowerCase().padStart(64, '0');\n\n // path is dynamic array: offset + length + [tokenIn, tokenOut]\n const pathOffset = (5 * 32).toString(16).padStart(64, '0'); // 5 static params before dynamic\n const pathLength = encode256(2n);\n const pathData = encodeAddr(tokenIn) + encodeAddr(tokenOut);\n\n const calldata =\n '0x' +\n selector +\n encode256(amountIn) +\n encode256(minAmountOut) +\n pathOffset +\n encodeAddr(recipient) +\n encode256(deadline) +\n pathLength +\n pathData;\n\n return calldata as `0x${string}`;\n }\n\n // --------------------------------------------------------------------------\n // IKeeperClient interface implementation\n // --------------------------------------------------------------------------\n\n async executeLimitOrder(\n jobId: string,\n owner: string,\n params: LimitOrderJob['params']\n ): Promise<{ txHash: string; amountOut: string | null }> {\n await this.preflightCheck();\n\n const amountIn = BigInt(params.amountIn);\n // Pass 0 for the router-level minAmountOut so the router never reverts due\n // to slippage — the AutomationManager contract enforces params.minAmountOut\n // via a post-swap balanceOf check, which is the canonical guard.\n const minAmountOut = 0n;\n\n const swapCalldata = this.buildSwapCalldata(\n params.tokenIn as Address,\n params.tokenOut as Address,\n amountIn,\n minAmountOut,\n owner as Address\n );\n\n logger.info(\n {\n jobId,\n tokenIn: params.tokenIn,\n tokenOut: params.tokenOut,\n amountIn: params.amountIn,\n },\n '[KeeperClient] executeLimitOrder'\n );\n\n // Simulate first — throws with a decoded revert reason if it would fail.\n try {\n await this.withTimeout((_signal) =>\n this.publicClient.simulateContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeLimitOrder',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n account: this.account.address,\n })\n );\n } catch (simErr: unknown) {\n const msg = simErr instanceof Error ? simErr.message : String(simErr);\n logger.error(\n { jobId, error: msg.slice(0, 500) },\n '[KeeperClient] executeLimitOrder simulation reverted'\n );\n throw simErr;\n }\n\n const hash: Hash = await this.withTimeout((_signal) =>\n this.walletClient.writeContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeLimitOrder',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n chain: undefined,\n account: this.account,\n })\n );\n\n logger.info({ jobId, hash }, '[KeeperClient] limitOrder tx submitted');\n\n const receipt = await Promise.race([\n this.publicClient.waitForTransactionReceipt({ hash } as Parameters<\n typeof this.publicClient.waitForTransactionReceipt\n >[0]),\n new Promise<never>((_, rej) =>\n setTimeout(\n () => rej(new Error('waitForTransactionReceipt timeout')),\n this.rpcTimeoutMs\n )\n ),\n ]);\n if ((receipt as { status: string }).status !== 'success') {\n let reason: string = (hash as string) ?? 'unknown';\n try {\n await this.withTimeout((_signal) =>\n this.publicClient.simulateContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeLimitOrder',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n account: this.account.address,\n })\n );\n } catch (simErr: unknown) {\n if (simErr instanceof Error) reason = simErr.message;\n }\n throw new Error(`Limit order tx reverted: ${reason}`);\n }\n\n const amountOut = decodeAmountOut(receipt.logs, owner as Address);\n logger.info(\n { jobId, hash, blockNumber: receipt.blockNumber.toString(), amountOut },\n '[KeeperClient] limitOrder confirmed'\n );\n return { txHash: hash, amountOut };\n }\n\n async executeDCATick(\n jobId: string,\n owner: string,\n params: DCAJob['params']\n ): Promise<{\n txHash: string;\n amountOut: string | null;\n nextExecutionSec: number;\n }> {\n await this.preflightCheck();\n\n // On-chain preflight: verify the DCA interval has actually been reached\n const onChainParams = (await this.publicClient.readContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'getDCAJob',\n args: [jobId as `0x${string}`],\n })) as {\n intervalSeconds: bigint;\n nextExecution: bigint;\n swapsCompleted: bigint;\n totalSwaps: bigint;\n };\n const nowSec = BigInt(Math.floor(Date.now() / 1000));\n if (nowSec < onChainParams.nextExecution) {\n throw new Error(\n `DCAIntervalNotReached(${onChainParams.nextExecution}) — on-chain interval not reached yet ` +\n `(now=${nowSec}, next=${onChainParams.nextExecution}, ` +\n `remaining=${onChainParams.nextExecution - nowSec}s)`\n );\n }\n\n const amountPerTick = BigInt(params.amountPerSwap);\n const minAmountOut = 0n;\n\n const swapCalldata = this.buildSwapCalldata(\n params.tokenIn as Address,\n params.tokenOut as Address,\n amountPerTick,\n minAmountOut,\n owner as Address\n );\n\n logger.info(\n {\n jobId,\n tokenIn: params.tokenIn,\n tokenOut: params.tokenOut,\n amountPerTick: params.amountPerSwap,\n },\n '[KeeperClient] executeDCATick'\n );\n\n try {\n await this.withTimeout((_signal) =>\n this.publicClient.simulateContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeDCATick',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n account: this.account.address,\n })\n );\n } catch (simErr: unknown) {\n const msg = simErr instanceof Error ? simErr.message : String(simErr);\n logger.error(\n { jobId, error: msg.slice(0, 500) },\n '[KeeperClient] executeDCATick simulation reverted'\n );\n throw simErr;\n }\n\n const hash: Hash = await this.withTimeout((_signal) =>\n this.walletClient.writeContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeDCATick',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n chain: undefined,\n account: this.account,\n })\n );\n\n logger.info({ jobId, hash }, '[KeeperClient] dca tx submitted');\n\n const receipt = await Promise.race([\n this.publicClient.waitForTransactionReceipt({ hash } as Parameters<\n typeof this.publicClient.waitForTransactionReceipt\n >[0]),\n new Promise<never>((_, rej) =>\n setTimeout(\n () => rej(new Error('waitForTransactionReceipt timeout')),\n this.rpcTimeoutMs\n )\n ),\n ]);\n if ((receipt as { status: string }).status !== 'success') {\n let reason: string = (hash as string) ?? 'unknown';\n try {\n await this.withTimeout((_signal) =>\n this.publicClient.simulateContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeDCATick',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n account: this.account.address,\n })\n );\n } catch (simErr: unknown) {\n if (simErr instanceof Error) reason = simErr.message;\n }\n throw new Error(`DCA tick tx reverted: ${reason}`);\n }\n\n const amountOut = decodeAmountOut(receipt.logs, owner as Address);\n // Re-read the contract's nextExecution after the tick so the DB is exactly\n // in sync with what the contract stored.\n const postTickParams = (await this.publicClient.readContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'getDCAJob',\n args: [jobId as `0x${string}`],\n })) as {\n intervalSeconds: bigint;\n nextExecution: bigint;\n swapsCompleted: bigint;\n totalSwaps: bigint;\n };\n const nextExecutionSec = Number(postTickParams.nextExecution);\n logger.info(\n {\n jobId,\n hash,\n blockNumber: receipt.blockNumber.toString(),\n amountOut,\n nextExecutionSec,\n },\n '[KeeperClient] dca confirmed'\n );\n return { txHash: hash, amountOut, nextExecutionSec };\n }\n}\n","/**\n * Minimal injectable logger interface for the automation module.\n *\n * The SDK ships with NO logging dependency. Consumers can pass any compatible\n * logger (pino, winston, console wrapper …) or omit it to get silent behaviour.\n */\nexport interface AutomationLogger {\n info(msg: string | object, ...args: unknown[]): void;\n warn(msg: string | object, ...args: unknown[]): void;\n debug(msg: string | object, ...args: unknown[]): void;\n error(msg: string | object, ...args: unknown[]): void;\n}\n\n/** Default no-op logger — used when no logger is supplied. */\nexport const noopLogger: AutomationLogger = {\n info: () => {},\n warn: () => {},\n debug: () => {},\n error: () => {},\n};\n","import type { AutomationLogger } from './logger.js';\nimport { noopLogger } from './logger.js';\nimport type { Job } from './types.js';\n\n/**\n * Price source adapter interface.\n *\n * Returns the spot price of `tokenIn` denominated in `tokenOut`, scaled by\n * 1e18. Implementations may call a DEX (Swappi, etc.), an oracle, or a mock.\n */\nexport interface PriceSource {\n /**\n * Returns 0n if the pair is unknown or price cannot be fetched.\n */\n getPrice(tokenIn: string, tokenOut: string): Promise<bigint>;\n}\n\nexport interface PriceCheckResult {\n conditionMet: boolean;\n currentPrice: bigint;\n targetPrice: bigint;\n swapUsd: number;\n}\n\n/**\n * Callback that resolves a token address to its ERC-20 decimal count.\n * Implementations should cache the result for performance.\n */\nexport type DecimalsResolver = (token: string) => Promise<number>;\n\n/** Default resolver — assumes 18 decimals for every token. */\nconst defaultDecimalsResolver: DecimalsResolver = async () => 18;\n\n/**\n * PriceChecker – queries a price source and evaluates whether a job's\n * trigger condition is currently met.\n */\nexport class PriceChecker {\n private source: PriceSource;\n private tokenPricesUsd: Map<string, number>;\n /**\n * Resolves a token's decimal count. Called lazily when _estimateUsd needs\n * to convert raw wei amounts into human-readable values. The resolver may\n * query the chain, a static map, or simply return 18.\n */\n private getDecimals: DecimalsResolver;\n private readonly log: AutomationLogger;\n\n constructor(\n source: PriceSource,\n tokenPricesUsd: Map<string, number> = new Map(),\n logger: AutomationLogger = noopLogger,\n getDecimals: DecimalsResolver = defaultDecimalsResolver\n ) {\n this.source = source;\n this.tokenPricesUsd = tokenPricesUsd;\n this.getDecimals = getDecimals;\n this.log = logger;\n }\n\n async checkLimitOrder(\n job: Job & { type: 'limit_order' }\n ): Promise<PriceCheckResult> {\n const params = job.params;\n const currentPrice = await this.source.getPrice(\n params.tokenIn,\n params.tokenOut\n );\n const targetPrice = BigInt(params.targetPrice);\n\n let conditionMet: boolean;\n if (params.direction === 'gte') {\n conditionMet = currentPrice >= targetPrice;\n } else {\n conditionMet = currentPrice <= targetPrice;\n }\n\n const swapUsd = await this._estimateUsd(params.tokenIn, params.amountIn);\n\n this.log.debug(\n {\n jobId: job.id,\n currentPrice: currentPrice.toString(),\n targetPrice: targetPrice.toString(),\n conditionMet,\n swapUsd,\n },\n '[PriceChecker] limit-order check'\n );\n\n return { conditionMet, currentPrice, targetPrice, swapUsd };\n }\n\n async checkDCA(job: Job & { type: 'dca' }): Promise<PriceCheckResult> {\n const params = job.params;\n // DCA has no price condition — just verify the interval has been reached.\n // We add a 15-second buffer before declaring conditionMet so that by the\n // time the transaction is mined the on-chain block.timestamp is also\n // reliably past nextExecution, avoiding DCAIntervalNotReached reverts at\n // the execution boundary.\n const DCA_EXECUTION_BUFFER_MS = 15_000;\n const conditionMet =\n Date.now() >= params.nextExecution + DCA_EXECUTION_BUFFER_MS;\n const currentPrice = await this.source.getPrice(\n params.tokenIn,\n params.tokenOut\n );\n const swapUsd = await this._estimateUsd(\n params.tokenIn,\n params.amountPerSwap\n );\n\n this.log.debug(\n {\n jobId: job.id,\n nextExecution: params.nextExecution,\n conditionMet,\n swapUsd,\n },\n '[PriceChecker] DCA check'\n );\n\n return { conditionMet, currentPrice, targetPrice: 0n, swapUsd };\n }\n\n updateTokenPrice(token: string, usdPrice: number): void {\n this.tokenPricesUsd.set(token.toLowerCase(), usdPrice);\n }\n\n private async _estimateUsd(\n token: string,\n amountWei: string\n ): Promise<number> {\n const usdPerToken = this.tokenPricesUsd.get(token.toLowerCase()) ?? 0;\n // Resolve the token's actual decimal count (on-chain or cached).\n const decimals = await this.getDecimals(token);\n const divisor = 10 ** decimals;\n const amount = Number(BigInt(amountWei)) / divisor;\n return amount * usdPerToken;\n }\n}\n","import type { AutomationLogger } from './logger.js';\nimport { noopLogger } from './logger.js';\nimport type { Job } from './types.js';\n\n/**\n * RetryQueue – wraps jobs for retry-with-exponential-backoff scheduling.\n *\n * Backoff formula:\n * delay = min(base × 2^attempt, maxDelay) × (1 + jitter × rand)\n */\nexport class RetryQueue {\n private readonly baseDelayMs: number;\n private readonly maxDelayMs: number;\n private readonly jitter: number;\n private readonly log: AutomationLogger;\n private queue: Map<\n string,\n { job: Job; nextRetryAt: number; attempt: number }\n > = new Map();\n\n constructor(\n options?: {\n baseDelayMs?: number;\n maxDelayMs?: number;\n jitter?: number;\n },\n logger: AutomationLogger = noopLogger\n ) {\n this.baseDelayMs = options?.baseDelayMs ?? 5_000; // 5 s\n this.maxDelayMs = options?.maxDelayMs ?? 300_000; // 5 min cap\n this.jitter = options?.jitter ?? 0.2; // 20 % jitter\n this.log = logger;\n }\n\n /** Enqueue a job for retry after a calculated backoff delay. */\n enqueue(job: Job): void {\n const existing = this.queue.get(job.id);\n const attempt = existing ? existing.attempt + 1 : 0;\n const delay = this._backoffDelay(attempt);\n const nextRetryAt = Date.now() + delay;\n\n this.queue.set(job.id, { job, nextRetryAt, attempt });\n this.log.info(\n `[RetryQueue] job ${job.id} enqueued, attempt=${attempt}, delay=${delay}ms`\n );\n }\n\n /** Remove a job from the queue (e.g. after success or manual cancel). */\n remove(jobId: string): void {\n this.queue.delete(jobId);\n }\n\n /** Return all jobs whose retry time has arrived; removes them from the queue. */\n drainDue(now = Date.now()): Job[] {\n const due: Job[] = [];\n for (const [jobId, entry] of this.queue) {\n if (now >= entry.nextRetryAt) {\n due.push(entry.job);\n this.queue.delete(jobId);\n }\n }\n return due;\n }\n\n size(): number {\n return this.queue.size;\n }\n\n // ─── Private ──────────────────────────────────────────────────────\n\n private _backoffDelay(attempt: number): number {\n const exponential = this.baseDelayMs * 2 ** attempt;\n const capped = Math.min(exponential, this.maxDelayMs);\n const withJitter = capped * (1 + this.jitter * Math.random());\n return Math.floor(withJitter);\n }\n}\n","import type { AutomationLogger } from './logger.js';\nimport { noopLogger } from './logger.js';\nimport type {\n Job,\n SafetyCheckResult,\n SafetyConfig,\n SafetyViolation,\n} from './types.js';\n\n/** Default on-chain safety limits. */\nexport const DEFAULT_SAFETY_CONFIG: SafetyConfig = {\n maxSwapUsd: 10_000,\n maxSlippageBps: 500,\n maxRetries: 5,\n minExecutionIntervalSeconds: 30,\n globalPause: false,\n};\n\n/**\n * SafetyGuard – off-chain complement to AutomationManager's on-chain checks.\n *\n * Responsibilities:\n * 1. Validate a job satisfies configured safety bounds before the keeper\n * submits a transaction.\n * 2. Provide a global pause switch (circuit-breaker).\n * 3. Log every violation for audit purposes.\n *\n * The logger is injected rather than imported — the SDK ships with no logging\n * dependency. Pass your pino/winston/console instance, or omit for silence.\n */\nexport class SafetyGuard {\n private config: SafetyConfig;\n private violations: SafetyViolation[] = [];\n private readonly log: AutomationLogger;\n\n constructor(\n config: Partial<SafetyConfig> = {},\n logger: AutomationLogger = noopLogger\n ) {\n this.config = { ...DEFAULT_SAFETY_CONFIG, ...config };\n this.log = logger;\n this.log.info(\n {\n maxSwapUsd: this.config.maxSwapUsd,\n maxSlippageBps: this.config.maxSlippageBps,\n maxRetries: this.config.maxRetries,\n globalPause: this.config.globalPause,\n },\n '[SafetyGuard] initialized'\n );\n }\n\n // ─── Core check ───────────────────────────────────────────────────\n\n /**\n * Run all configured safety checks against a job.\n * Returns `{ ok: true }` if all pass, or `{ ok: false, violation }` on first failure.\n */\n check(\n job: Job,\n context: { swapUsd: number; currentTime?: number }\n ): SafetyCheckResult {\n const now = context.currentTime ?? Date.now();\n\n // Global pause (circuit-breaker)\n if (this.config.globalPause) {\n return this._fail(\n job.id,\n 'globalPause',\n 'Global pause is active — all execution halted'\n );\n }\n\n // Job status guard — only pending and active are executable\n if (job.status !== 'active' && job.status !== 'pending') {\n return this._fail(\n job.id,\n 'globalPause',\n `Job ${job.id} cannot be executed (status: ${job.status})`\n );\n }\n\n // Retry cap\n if (job.retries >= job.maxRetries) {\n return this._fail(\n job.id,\n 'maxRetries',\n `Job ${job.id} has exhausted retries (${job.retries}/${job.maxRetries})`\n );\n }\n\n // Swap USD cap\n if (context.swapUsd > this.config.maxSwapUsd) {\n return this._fail(\n job.id,\n 'maxSwapUsd',\n `Swap value $${context.swapUsd.toFixed(2)} exceeds limit $${this.config.maxSwapUsd}`\n );\n }\n\n // Job expiry\n if (job.expiresAt !== null && now >= job.expiresAt) {\n return this._fail(job.id, 'globalPause', `Job ${job.id} has expired`);\n }\n\n // DCA interval check\n if (job.type === 'dca') {\n const dcaParams = job.params;\n if (now < dcaParams.nextExecution) {\n const waitSec = Math.ceil((dcaParams.nextExecution - now) / 1000);\n return this._fail(\n job.id,\n 'minExecutionIntervalSeconds',\n `DCA interval not yet reached (${waitSec}s remaining)`\n );\n }\n }\n\n return { ok: true };\n }\n\n // ─── Config management ────────────────────────────────────────────\n\n updateConfig(patch: Partial<SafetyConfig>): void {\n this.config = { ...this.config, ...patch };\n this.log.info(patch, '[SafetyGuard] config updated');\n }\n\n getConfig(): Readonly<SafetyConfig> {\n return { ...this.config };\n }\n\n pauseAll(): void {\n this.config.globalPause = true;\n this.log.warn('[SafetyGuard] GLOBAL PAUSE ACTIVATED');\n }\n\n resumeAll(): void {\n this.config.globalPause = false;\n this.log.info('[SafetyGuard] global pause lifted');\n }\n\n isPaused(): boolean {\n return this.config.globalPause;\n }\n\n // ─── Audit log ────────────────────────────────────────────────────\n\n getViolations(): readonly SafetyViolation[] {\n return this.violations;\n }\n\n clearViolations(): void {\n this.violations = [];\n }\n\n // ─── Private ──────────────────────────────────────────────────────\n\n private _fail(\n jobId: string,\n rule: keyof SafetyConfig,\n detail: string\n ): SafetyCheckResult {\n const violation: SafetyViolation = {\n jobId,\n rule,\n detail,\n timestamp: Date.now(),\n };\n this.violations.push(violation);\n this.log.warn({ jobId, detail }, `[SafetyGuard] violation – ${rule}`);\n return { ok: false, violation };\n }\n}\n"],"mappings":"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","names":["import_core","logger","logger","logger"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/executor.ts","../src/keeper-client.ts","../src/logger.ts","../src/price-checker.ts","../src/retry-queue.ts","../src/safety-guard.ts"],"sourcesContent":["/**\n * @cfxdevkit/executor\n *\n * On-chain strategy execution engine for Conflux DevKit.\n *\n * Provides the runtime primitives needed to build keepers, bots, or AI agents\n * that execute on-chain strategies (limit orders, DCA, TWAP, spot swaps).\n *\n * Key exports:\n * - Job types (LimitOrderJob, DCAJob, TWAPJob, SwapJob) + params\n * - Strategy types (LimitOrderStrategy, DCAStrategy, TWAPStrategy, SwapStrategy)\n * - SafetyGuard — circuit-breaker / swap-cap / retry-cap\n * - RetryQueue — exponential backoff with jitter\n * - PriceChecker — pluggable price source + condition evaluation\n * - KeeperClient interface + KeeperClientImpl (viem / AutomationManager)\n * - Executor — orchestrator that ties all of the above together\n * - AutomationLogger — injectable logger interface (no runtime dep)\n */\n\n// ── Executor + interfaces ─────────────────────────────────────────────────────\nexport type { ExecutorOptions, JobStore, KeeperClient } from './executor.js';\nexport { Executor } from './executor.js';\n// ── KeeperClientImpl (viem) ───────────────────────────────────────────────────\nexport type { KeeperClientConfig } from './keeper-client.js';\nexport { KeeperClientImpl } from './keeper-client.js';\n\n// ── KeeperClient interface ────────────────────────────────────────────────────\nexport type { KeeperClient as IKeeperClient } from './keeper-interface.js';\n// ── Logger ────────────────────────────────────────────────────────────────────\nexport type { AutomationLogger } from './logger.js';\nexport { noopLogger } from './logger.js';\n\n// ── PriceChecker ──────────────────────────────────────────────────────────────\nexport type {\n DecimalsResolver,\n PriceCheckResult,\n PriceSource,\n} from './price-checker.js';\nexport { PriceChecker } from './price-checker.js';\n// ── RetryQueue ────────────────────────────────────────────────────────────────\nexport { RetryQueue } from './retry-queue.js';\n// ── SafetyGuard ───────────────────────────────────────────────────────────────\nexport { DEFAULT_SAFETY_CONFIG, SafetyGuard } from './safety-guard.js';\n// ── Strategy types ────────────────────────────────────────────────────────────\nexport type {\n DCAStrategy,\n LimitOrderStrategy,\n Strategy,\n SwapStrategy,\n TWAPStrategy,\n} from './strategies.js';\n// ── Types ─────────────────────────────────────────────────────────────────────\nexport type {\n BaseJob,\n DCAJob,\n DCAParams,\n Job,\n JobStatus,\n JobType,\n LimitOrderJob,\n LimitOrderParams,\n SafetyCheckResult,\n SafetyConfig,\n SafetyViolation,\n SwapJob,\n SwapParams,\n TWAPJob,\n TWAPParams,\n} from './types.js';\n","import { logger } from '@cfxdevkit/core';\nimport type { PriceChecker } from './price-checker.js';\nimport type { RetryQueue } from './retry-queue.js';\nimport type { SafetyGuard } from './safety-guard.js';\nimport type { DCAJob, Job, LimitOrderJob } from './types.js';\n\n// Minimal on-chain execution interface – fulfilled by the viem-based KeeperClientImpl\nexport interface KeeperClient {\n executeLimitOrder(\n jobId: string,\n owner: string,\n params: LimitOrderJob['params']\n ): Promise<{ txHash: string; amountOut?: string | null }>;\n\n executeDCATick(\n jobId: string,\n owner: string,\n params: DCAJob['params']\n ): Promise<{\n txHash: string;\n amountOut?: string | null;\n nextExecutionSec: number;\n }>;\n\n /** Read the terminal status of a job from the contract. */\n getOnChainStatus(\n jobId: `0x${string}`\n ): Promise<'active' | 'executed' | 'cancelled' | 'expired'>;\n}\n\n// Persistent job store – fulfilled by the DB-backed store in the application layer\nexport interface JobStore {\n getActiveJobs(): Promise<Job[]>;\n markActive(jobId: string): Promise<void>;\n markExecuted(\n jobId: string,\n txHash: string,\n amountOut?: string | null\n ): Promise<void>;\n /**\n * Record one completed DCA tick.\n * Updates swapsCompleted + nextExecution in params_json and inserts an\n * execution record. Sets status → 'executed' when all swaps are done,\n * otherwise keeps the job 'active' so subsequent ticks can run.\n */\n markDCATick(\n jobId: string,\n txHash: string,\n newSwapsCompleted: number,\n nextExecution: number,\n amountOut?: string | null\n ): Promise<void>;\n markFailed(jobId: string, error: string): Promise<void>;\n incrementRetry(jobId: string): Promise<void>;\n markExpired(jobId: string): Promise<void>;\n /** Mark a job cancelled — use when on-chain status is CANCELLED. */\n markCancelled(jobId: string): Promise<void>;\n /** Record the latest error message without changing status or incrementing retries. */\n updateLastError(jobId: string, error: string): Promise<void>;\n}\n\nexport interface ExecutorOptions {\n dryRun?: boolean;\n}\n\n/**\n * Executor – evaluates active jobs and submits on-chain transactions when\n * conditions are met and the SafetyGuard approves.\n */\nexport class Executor {\n private priceChecker: PriceChecker;\n private safetyGuard: SafetyGuard;\n private retryQueue: RetryQueue;\n private keeperClient: KeeperClient;\n private jobStore: JobStore;\n private dryRun: boolean;\n\n constructor(\n priceChecker: PriceChecker,\n safetyGuard: SafetyGuard,\n retryQueue: RetryQueue,\n keeperClient: KeeperClient,\n jobStore: JobStore,\n options: ExecutorOptions = {}\n ) {\n this.priceChecker = priceChecker;\n this.safetyGuard = safetyGuard;\n this.retryQueue = retryQueue;\n this.keeperClient = keeperClient;\n this.jobStore = jobStore;\n this.dryRun = options.dryRun ?? false;\n }\n\n /**\n * Process a single job tick.\n */\n async processTick(job: Job): Promise<void> {\n // 1. Quick expiry guard\n if (job.expiresAt !== null && Date.now() >= job.expiresAt) {\n logger.info(`[Executor] job ${job.id} expired`);\n await this.jobStore.markExpired(job.id);\n return;\n }\n\n try {\n // Transition pending → active on first pickup\n if (job.status === 'pending') {\n await this.jobStore.markActive(job.id);\n }\n\n if (job.type === 'limit_order') {\n await this._processLimitOrder(job as LimitOrderJob);\n } else if (job.type === 'dca') {\n await this._processDCA(job as DCAJob);\n }\n // 'twap' and 'swap' are not yet implemented — silently skip\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n\n // ── Transient errors — handle silently without logging as ERROR ──────────\n\n // PriceConditionNotMet is a transient race: the off-chain check passed but\n // the on-chain oracle ticked back before the tx was mined.\n if (message.includes('PriceConditionNotMet')) {\n logger.debug(\n `[Executor] job ${job.id}: price condition no longer met at execution time — will retry next tick`\n );\n return;\n }\n\n // DCAIntervalNotReached means the on-chain interval timer hasn't elapsed yet.\n if (message.includes('DCAIntervalNotReached')) {\n logger.debug(\n `[Executor] job ${job.id}: DCA interval not yet reached at execution time — will retry next tick`\n );\n return;\n }\n\n // Receipt-not-found: the node didn't have the receipt indexed yet.\n if (\n message.includes('could not be found') ||\n message.includes('TransactionReceiptNotFoundError')\n ) {\n logger.debug(\n `[Executor] job ${job.id}: receipt not yet indexed — will retry next tick`\n );\n return;\n }\n\n // Slippage exceeded is transient: pool price moved between simulation and mine.\n if (message.includes('Slippage exceeded')) {\n logger.debug(\n `[Executor] job ${job.id}: slippage exceeded at execution time — will retry next tick`\n );\n await this.jobStore.incrementRetry(job.id);\n await this.jobStore.updateLastError(job.id, message);\n return;\n }\n\n // ── Unexpected errors — log full cause for diagnosis ─────────────────────\n if (err instanceof Error && (err as { cause?: unknown }).cause) {\n logger.error(\n { cause: (err as { cause?: unknown }).cause },\n '[Executor] raw error cause'\n );\n }\n\n // JobNotFound: on-chain job ID doesn't exist (contract redeployed).\n if (message.includes('JobNotFound')) {\n const ocId = (job as { onChainJobId?: string }).onChainJobId;\n logger.warn(\n { jobId: job.id, onChainJobId: ocId },\n '[Executor] JobNotFound — on_chain_job_id not found on current contract ' +\n '(likely left over from an old deployment) — marking cancelled'\n );\n await this.jobStore.markCancelled(job.id);\n return;\n }\n\n // JobNotActive: job is EXECUTED/CANCELLED/EXPIRED on-chain but DB is out of sync.\n if (message.includes('JobNotActive')) {\n const ocId = (job as { onChainJobId?: string }).onChainJobId as\n | `0x${string}`\n | undefined;\n let onChainStatus: 'active' | 'executed' | 'cancelled' | 'expired' =\n 'cancelled';\n if (ocId) {\n try {\n onChainStatus = await this.keeperClient.getOnChainStatus(ocId);\n } catch (statusErr) {\n logger.warn(\n { jobId: job.id, err: statusErr },\n '[Executor] could not read on-chain status — defaulting to cancelled'\n );\n }\n }\n if (onChainStatus === 'executed') {\n logger.warn(\n { jobId: job.id, onChainJobId: ocId },\n '[Executor] job EXECUTED on-chain but DB was out of sync — marking executed'\n );\n await this.jobStore.markExecuted(job.id, 'chain-sync');\n } else {\n logger.warn(\n { jobId: job.id, onChainJobId: ocId, onChainStatus },\n '[Executor] job is CANCELLED/EXPIRED on-chain — marking cancelled in DB'\n );\n await this.jobStore.markCancelled(job.id);\n }\n return;\n }\n\n logger.error(`[Executor] job ${job.id} failed: ${message}`);\n const nextRetries = job.retries + 1;\n if (job.retries < job.maxRetries) {\n await this.jobStore.incrementRetry(job.id);\n }\n if (nextRetries < job.maxRetries) {\n this.retryQueue.enqueue({ ...job, retries: nextRetries });\n }\n await this.jobStore.markFailed(job.id, message);\n }\n }\n\n /**\n * Process all active jobs + due retries in one tick.\n */\n async runAllTicks(): Promise<void> {\n const activeJobs = await this.jobStore.getActiveJobs();\n const retries = this.retryQueue.drainDue();\n\n const all = [...activeJobs, ...retries];\n logger.info(\n `[Executor] tick – ${activeJobs.length} active, ${retries.length} retries`\n );\n\n await Promise.allSettled(all.map((job) => this.processTick(job)));\n }\n\n // ─── Private ────────────────────────────────────────────────────────────────\n\n private async _processLimitOrder(job: LimitOrderJob): Promise<void> {\n const priceResult = await this.priceChecker.checkLimitOrder(job);\n\n if (!priceResult.conditionMet) {\n logger.info(\n {\n jobId: job.id,\n currentPrice: priceResult.currentPrice.toString(),\n targetPrice: priceResult.targetPrice.toString(),\n direction: job.params.direction,\n },\n `[Executor] limit-order ${job.id}: condition not met – waiting`\n );\n return;\n }\n\n const safetyResult = this.safetyGuard.check(job, {\n swapUsd: priceResult.swapUsd,\n });\n if (!safetyResult.ok) {\n logger.warn(\n { violation: safetyResult.violation },\n `[Executor] limit-order ${job.id} blocked by safety guard`\n );\n return;\n }\n\n if (this.dryRun) {\n logger.info(`[Executor][dryRun] would execute limit-order ${job.id}`);\n return;\n }\n\n logger.info(`[Executor] executing limit-order ${job.id}`);\n const onChainJobId = job.onChainJobId;\n if (!onChainJobId) {\n logger.warn(\n `[Executor] limit-order ${job.id} has no onChainJobId – skipping until registered on-chain`\n );\n return;\n }\n const { txHash, amountOut } = await this.keeperClient.executeLimitOrder(\n onChainJobId,\n job.owner,\n job.params\n );\n await this.jobStore.markExecuted(job.id, txHash, amountOut);\n logger.info(`[Executor] limit-order ${job.id} executed – tx ${txHash}`);\n }\n\n private async _processDCA(job: DCAJob): Promise<void> {\n const priceResult = await this.priceChecker.checkDCA(job);\n\n if (!priceResult.conditionMet) {\n logger.info(\n {\n jobId: job.id,\n nextExecution: job.params.nextExecution,\n now: Date.now(),\n secsRemaining: Math.round(\n (job.params.nextExecution - Date.now()) / 1000\n ),\n },\n `[Executor] DCA ${job.id}: interval not reached`\n );\n return;\n }\n\n const safetyResult = this.safetyGuard.check(job, {\n swapUsd: priceResult.swapUsd,\n });\n if (!safetyResult.ok) {\n logger.warn(\n { violation: safetyResult.violation },\n `[Executor] DCA ${job.id} blocked by safety guard`\n );\n return;\n }\n\n if (this.dryRun) {\n logger.info(`[Executor][dryRun] would execute DCA tick for ${job.id}`);\n return;\n }\n\n logger.info(`[Executor] executing DCA tick ${job.id}`);\n const onChainJobId = job.onChainJobId;\n if (!onChainJobId) {\n logger.warn(\n `[Executor] DCA job ${job.id} has no onChainJobId – skipping until registered on-chain`\n );\n return;\n }\n const { txHash, amountOut, nextExecutionSec } =\n await this.keeperClient.executeDCATick(\n onChainJobId,\n job.owner,\n job.params\n );\n\n const newSwapsCompleted = job.params.swapsCompleted + 1;\n // Use the on-chain nextExecution (returned by KeeperClient after reading\n // the contract state post-tick) rather than computing it locally.\n const nextExecutionMs = nextExecutionSec * 1000;\n await this.jobStore.markDCATick(\n job.id,\n txHash,\n newSwapsCompleted,\n nextExecutionMs,\n amountOut\n );\n\n logger.info(\n {\n jobId: job.id,\n txHash,\n swapsCompleted: newSwapsCompleted,\n total: job.params.totalSwaps,\n },\n `[Executor] DCA tick executed – ${newSwapsCompleted}/${job.params.totalSwaps}`\n );\n }\n}\n","/**\n * KeeperClient — viem implementation of the KeeperClient interface.\n *\n * Wraps AutomationManager contract calls for executeLimitOrder and executeDCATick.\n * The executor wallet is a keeper (not a custodian) — it cannot move user funds\n * unless the user has set an on-chain allowance for that specific swap.\n */\n\nimport { AUTOMATION_MANAGER_ABI } from '@cfxdevkit/contracts';\nimport { logger } from '@cfxdevkit/core';\nimport {\n type Address,\n createPublicClient,\n createWalletClient,\n type Hash,\n http,\n} from 'viem';\nimport { privateKeyToAccount } from 'viem/accounts';\nimport type { KeeperClient as IKeeperClient } from './executor.js';\nimport type { DCAJob, LimitOrderJob } from './types.js';\n\n// --------------------------------------------------------------------------\n// Helpers\n// --------------------------------------------------------------------------\n\n/**\n * Decode the amountOut from a swap receipt by finding the last ERC-20\n * Transfer event whose `to` address is the job owner (the swap recipient).\n * Returns the raw amount as a decimal string, or null if not found.\n */\nfunction decodeAmountOut(\n logs: readonly { topics: readonly string[]; data: string; address: string }[],\n owner: Address\n): string | null {\n const TRANSFER_TOPIC =\n '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';\n const ownerLower = owner.toLowerCase();\n // Walk logs in reverse — the last Transfer to owner is tokenOut\n for (let i = logs.length - 1; i >= 0; i--) {\n const log = logs[i];\n if (\n log.topics[0]?.toLowerCase() === TRANSFER_TOPIC &&\n log.topics[2] &&\n `0x${log.topics[2].slice(26)}`.toLowerCase() === ownerLower\n ) {\n try {\n return BigInt(log.data).toString();\n } catch {\n return null;\n }\n }\n }\n return null;\n}\n\n// --------------------------------------------------------------------------\n// Config\n// --------------------------------------------------------------------------\nexport interface KeeperClientConfig {\n /** RPC endpoint for Conflux eSpace */\n rpcUrl: string;\n /** Hex private key (0x-prefixed) of the keeper wallet */\n privateKey: `0x${string}`;\n /** Deployed AutomationManager contract address */\n contractAddress: Address;\n /**\n * Swappi router address.\n * Testnet: 0x873789aaf553fd0b4252d0d2b72c6331c47aff2e\n * Mainnet: 0xE37B52296b0bAA91412cD0Cd97975B0805037B84 (Swappi v2 — only address with deployed code;\n * old 0x62B0873... has no bytecode)\n */\n swappiRouter: Address;\n /**\n * Maximum gas price in gwei before aborting execution (circuit breaker).\n * Defaults to 1000 gwei.\n */\n maxGasPriceGwei?: bigint;\n /** Chain definition — pass the viem chain object (espaceTestnet / espaceMainnet) */\n chain: Parameters<typeof createWalletClient>[0]['chain'];\n /** RPC request timeout in milliseconds (applies to read/simulate/write calls). */\n rpcTimeoutMs?: number;\n}\n\n// --------------------------------------------------------------------------\n// KeeperClientImpl\n// --------------------------------------------------------------------------\nexport class KeeperClientImpl implements IKeeperClient {\n private readonly walletClient;\n private readonly publicClient;\n private readonly contractAddress: Address;\n private readonly swappiRouter: Address;\n private readonly maxGasPriceGwei: bigint;\n private readonly rpcTimeoutMs: number;\n private readonly account;\n\n constructor(config: KeeperClientConfig) {\n this.account = privateKeyToAccount(config.privateKey);\n this.contractAddress = config.contractAddress;\n this.swappiRouter = config.swappiRouter;\n this.maxGasPriceGwei = config.maxGasPriceGwei ?? 1000n;\n this.rpcTimeoutMs = config.rpcTimeoutMs ?? 120_000; // default 2 minutes\n\n this.publicClient = createPublicClient({\n chain: config.chain,\n transport: http(config.rpcUrl),\n });\n\n this.walletClient = createWalletClient({\n chain: config.chain,\n transport: http(config.rpcUrl),\n account: this.account,\n });\n }\n\n private withTimeout<T>(\n fn: (signal: AbortSignal) => Promise<T>,\n timeoutMs = this.rpcTimeoutMs\n ): Promise<T> {\n const controller = new AbortController();\n const id = setTimeout(() => controller.abort(), timeoutMs);\n return fn(controller.signal).finally(() => clearTimeout(id));\n }\n\n // --------------------------------------------------------------------------\n // Helpers\n // --------------------------------------------------------------------------\n\n /**\n * Query the on-chain status of a job.\n * Returns one of: 'active' | 'executed' | 'cancelled' | 'expired'.\n * Throws if the contract call fails (e.g. job not found).\n */\n async getOnChainStatus(\n onChainJobId: `0x${string}`\n ): Promise<'active' | 'executed' | 'cancelled' | 'expired'> {\n const job = (await this.withTimeout((_signal) =>\n this.publicClient.readContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'getJob',\n args: [onChainJobId],\n })\n )) as { status: number };\n // JobStatus enum: 0=ACTIVE, 1=EXECUTED, 2=CANCELLED, 3=EXPIRED\n switch (job.status) {\n case 0:\n return 'active';\n case 1:\n return 'executed';\n case 2:\n return 'cancelled';\n case 3:\n return 'expired';\n default:\n return 'cancelled'; // unknown → treat as cancelled (stop retrying)\n }\n }\n\n /** Guard: abort if chain is paused or gas price is too high */\n private async preflightCheck(): Promise<void> {\n // Check on-chain pause flag\n const isPaused = await this.withTimeout((_signal) =>\n this.publicClient.readContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'paused',\n })\n );\n if (isPaused) {\n throw new Error('AutomationManager is paused on-chain');\n }\n\n // Check gas price circuit breaker\n const gasPrice = await Promise.race([\n this.publicClient.getGasPrice(),\n new Promise<never>((_, rej) =>\n setTimeout(\n () => rej(new Error('getGasPrice timeout')),\n this.rpcTimeoutMs\n )\n ),\n ]);\n const gasPriceGwei = gasPrice / 1_000_000_000n;\n if (gasPriceGwei > this.maxGasPriceGwei) {\n throw new Error(\n `Gas price ${gasPriceGwei} gwei exceeds cap ${this.maxGasPriceGwei} gwei`\n );\n }\n }\n\n /**\n * Build minimal swap calldata for a token-in/token-out pair via Swappi.\n *\n * In a production keeper the calldata would be built via a DEX aggregator\n * (e.g. OKX DEX API) to get optimal routing. For the MVP we encode the\n * simplest Swappi path: `swapExactTokensForTokens(amountIn, minOut, path, to, deadline)`.\n *\n * NOTE: The AutomationManager does NOT use this calldata for custody;\n * it only uses it to call the router on behalf of the user's pre-approved allowance.\n */\n private buildSwapCalldata(\n tokenIn: Address,\n tokenOut: Address,\n amountIn: bigint,\n minAmountOut: bigint,\n recipient: Address\n ): `0x${string}` {\n // swapExactTokensForTokens(uint256,uint256,address[],address,uint256)\n // selector = 0x38ed1739\n const selector = '38ed1739';\n const deadline = BigInt(Math.floor(Date.now() / 1000) + 1800); // 30 min\n const encode256 = (n: bigint) => n.toString(16).padStart(64, '0');\n const encodeAddr = (a: string) =>\n a.slice(2).toLowerCase().padStart(64, '0');\n\n // path is dynamic array: offset + length + [tokenIn, tokenOut]\n const pathOffset = (5 * 32).toString(16).padStart(64, '0'); // 5 static params before dynamic\n const pathLength = encode256(2n);\n const pathData = encodeAddr(tokenIn) + encodeAddr(tokenOut);\n\n const calldata =\n '0x' +\n selector +\n encode256(amountIn) +\n encode256(minAmountOut) +\n pathOffset +\n encodeAddr(recipient) +\n encode256(deadline) +\n pathLength +\n pathData;\n\n return calldata as `0x${string}`;\n }\n\n // --------------------------------------------------------------------------\n // IKeeperClient interface implementation\n // --------------------------------------------------------------------------\n\n async executeLimitOrder(\n jobId: string,\n owner: string,\n params: LimitOrderJob['params']\n ): Promise<{ txHash: string; amountOut: string | null }> {\n await this.preflightCheck();\n\n const amountIn = BigInt(params.amountIn);\n // Pass 0 for the router-level minAmountOut so the router never reverts due\n // to slippage — the AutomationManager contract enforces params.minAmountOut\n // via a post-swap balanceOf check, which is the canonical guard.\n const minAmountOut = 0n;\n\n const swapCalldata = this.buildSwapCalldata(\n params.tokenIn as Address,\n params.tokenOut as Address,\n amountIn,\n minAmountOut,\n owner as Address\n );\n\n logger.info(\n {\n jobId,\n tokenIn: params.tokenIn,\n tokenOut: params.tokenOut,\n amountIn: params.amountIn,\n },\n '[KeeperClient] executeLimitOrder'\n );\n\n // Simulate first — throws with a decoded revert reason if it would fail.\n try {\n await this.withTimeout((_signal) =>\n this.publicClient.simulateContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeLimitOrder',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n account: this.account.address,\n })\n );\n } catch (simErr: unknown) {\n const msg = simErr instanceof Error ? simErr.message : String(simErr);\n logger.error(\n { jobId, error: msg.slice(0, 500) },\n '[KeeperClient] executeLimitOrder simulation reverted'\n );\n throw simErr;\n }\n\n const hash: Hash = await this.withTimeout((_signal) =>\n this.walletClient.writeContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeLimitOrder',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n chain: undefined,\n account: this.account,\n })\n );\n\n logger.info({ jobId, hash }, '[KeeperClient] limitOrder tx submitted');\n\n const receipt = await Promise.race([\n this.publicClient.waitForTransactionReceipt({ hash } as Parameters<\n typeof this.publicClient.waitForTransactionReceipt\n >[0]),\n new Promise<never>((_, rej) =>\n setTimeout(\n () => rej(new Error('waitForTransactionReceipt timeout')),\n this.rpcTimeoutMs\n )\n ),\n ]);\n if ((receipt as { status: string }).status !== 'success') {\n let reason: string = (hash as string) ?? 'unknown';\n try {\n await this.withTimeout((_signal) =>\n this.publicClient.simulateContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeLimitOrder',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n account: this.account.address,\n })\n );\n } catch (simErr: unknown) {\n if (simErr instanceof Error) reason = simErr.message;\n }\n throw new Error(`Limit order tx reverted: ${reason}`);\n }\n\n const amountOut = decodeAmountOut(receipt.logs, owner as Address);\n logger.info(\n { jobId, hash, blockNumber: receipt.blockNumber.toString(), amountOut },\n '[KeeperClient] limitOrder confirmed'\n );\n return { txHash: hash, amountOut };\n }\n\n async executeDCATick(\n jobId: string,\n owner: string,\n params: DCAJob['params']\n ): Promise<{\n txHash: string;\n amountOut: string | null;\n nextExecutionSec: number;\n }> {\n await this.preflightCheck();\n\n // On-chain preflight: verify the DCA interval has actually been reached\n const onChainParams = (await this.publicClient.readContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'getDCAJob',\n args: [jobId as `0x${string}`],\n })) as {\n intervalSeconds: bigint;\n nextExecution: bigint;\n swapsCompleted: bigint;\n totalSwaps: bigint;\n };\n const nowSec = BigInt(Math.floor(Date.now() / 1000));\n if (nowSec < onChainParams.nextExecution) {\n throw new Error(\n `DCAIntervalNotReached(${onChainParams.nextExecution}) — on-chain interval not reached yet ` +\n `(now=${nowSec}, next=${onChainParams.nextExecution}, ` +\n `remaining=${onChainParams.nextExecution - nowSec}s)`\n );\n }\n\n const amountPerTick = BigInt(params.amountPerSwap);\n const minAmountOut = 0n;\n\n const swapCalldata = this.buildSwapCalldata(\n params.tokenIn as Address,\n params.tokenOut as Address,\n amountPerTick,\n minAmountOut,\n owner as Address\n );\n\n logger.info(\n {\n jobId,\n tokenIn: params.tokenIn,\n tokenOut: params.tokenOut,\n amountPerTick: params.amountPerSwap,\n },\n '[KeeperClient] executeDCATick'\n );\n\n try {\n await this.withTimeout((_signal) =>\n this.publicClient.simulateContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeDCATick',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n account: this.account.address,\n })\n );\n } catch (simErr: unknown) {\n const msg = simErr instanceof Error ? simErr.message : String(simErr);\n logger.error(\n { jobId, error: msg.slice(0, 500) },\n '[KeeperClient] executeDCATick simulation reverted'\n );\n throw simErr;\n }\n\n const hash: Hash = await this.withTimeout((_signal) =>\n this.walletClient.writeContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeDCATick',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n chain: undefined,\n account: this.account,\n })\n );\n\n logger.info({ jobId, hash }, '[KeeperClient] dca tx submitted');\n\n const receipt = await Promise.race([\n this.publicClient.waitForTransactionReceipt({ hash } as Parameters<\n typeof this.publicClient.waitForTransactionReceipt\n >[0]),\n new Promise<never>((_, rej) =>\n setTimeout(\n () => rej(new Error('waitForTransactionReceipt timeout')),\n this.rpcTimeoutMs\n )\n ),\n ]);\n if ((receipt as { status: string }).status !== 'success') {\n let reason: string = (hash as string) ?? 'unknown';\n try {\n await this.withTimeout((_signal) =>\n this.publicClient.simulateContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeDCATick',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n account: this.account.address,\n })\n );\n } catch (simErr: unknown) {\n if (simErr instanceof Error) reason = simErr.message;\n }\n throw new Error(`DCA tick tx reverted: ${reason}`);\n }\n\n const amountOut = decodeAmountOut(receipt.logs, owner as Address);\n // Re-read the contract's nextExecution after the tick so the DB is exactly\n // in sync with what the contract stored.\n const postTickParams = (await this.publicClient.readContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'getDCAJob',\n args: [jobId as `0x${string}`],\n })) as {\n intervalSeconds: bigint;\n nextExecution: bigint;\n swapsCompleted: bigint;\n totalSwaps: bigint;\n };\n const nextExecutionSec = Number(postTickParams.nextExecution);\n logger.info(\n {\n jobId,\n hash,\n blockNumber: receipt.blockNumber.toString(),\n amountOut,\n nextExecutionSec,\n },\n '[KeeperClient] dca confirmed'\n );\n return { txHash: hash, amountOut, nextExecutionSec };\n }\n}\n","/**\n * Minimal injectable logger interface for the automation module.\n *\n * The SDK ships with NO logging dependency. Consumers can pass any compatible\n * logger (pino, winston, console wrapper …) or omit it to get silent behaviour.\n */\nexport interface AutomationLogger {\n info(msg: string | object, ...args: unknown[]): void;\n warn(msg: string | object, ...args: unknown[]): void;\n debug(msg: string | object, ...args: unknown[]): void;\n error(msg: string | object, ...args: unknown[]): void;\n}\n\n/** Default no-op logger — used when no logger is supplied. */\nexport const noopLogger: AutomationLogger = {\n info: () => {},\n warn: () => {},\n debug: () => {},\n error: () => {},\n};\n","import type { AutomationLogger } from './logger.js';\nimport { noopLogger } from './logger.js';\nimport type { Job } from './types.js';\n\n/**\n * Price source adapter interface.\n *\n * Returns the spot price of `tokenIn` denominated in `tokenOut`, scaled by\n * 1e18. Implementations may call a DEX (Swappi, etc.), an oracle, or a mock.\n */\nexport interface PriceSource {\n /**\n * Returns 0n if the pair is unknown or price cannot be fetched.\n */\n getPrice(tokenIn: string, tokenOut: string): Promise<bigint>;\n}\n\nexport interface PriceCheckResult {\n conditionMet: boolean;\n currentPrice: bigint;\n targetPrice: bigint;\n swapUsd: number;\n}\n\n/**\n * Callback that resolves a token address to its ERC-20 decimal count.\n * Implementations should cache the result for performance.\n */\nexport type DecimalsResolver = (token: string) => Promise<number>;\n\n/** Default resolver — assumes 18 decimals for every token. */\nconst defaultDecimalsResolver: DecimalsResolver = async () => 18;\n\n/**\n * PriceChecker – queries a price source and evaluates whether a job's\n * trigger condition is currently met.\n */\nexport class PriceChecker {\n private source: PriceSource;\n private tokenPricesUsd: Map<string, number>;\n /**\n * Resolves a token's decimal count. Called lazily when _estimateUsd needs\n * to convert raw wei amounts into human-readable values. The resolver may\n * query the chain, a static map, or simply return 18.\n */\n private getDecimals: DecimalsResolver;\n private readonly log: AutomationLogger;\n\n constructor(\n source: PriceSource,\n tokenPricesUsd: Map<string, number> = new Map(),\n logger: AutomationLogger = noopLogger,\n getDecimals: DecimalsResolver = defaultDecimalsResolver\n ) {\n this.source = source;\n this.tokenPricesUsd = tokenPricesUsd;\n this.getDecimals = getDecimals;\n this.log = logger;\n }\n\n async checkLimitOrder(\n job: Job & { type: 'limit_order' }\n ): Promise<PriceCheckResult> {\n const params = job.params;\n const currentPrice = await this.source.getPrice(\n params.tokenIn,\n params.tokenOut\n );\n const targetPrice = BigInt(params.targetPrice);\n\n let conditionMet: boolean;\n if (params.direction === 'gte') {\n conditionMet = currentPrice >= targetPrice;\n } else {\n conditionMet = currentPrice <= targetPrice;\n }\n\n const swapUsd = await this._estimateUsd(params.tokenIn, params.amountIn);\n\n this.log.debug(\n {\n jobId: job.id,\n currentPrice: currentPrice.toString(),\n targetPrice: targetPrice.toString(),\n conditionMet,\n swapUsd,\n },\n '[PriceChecker] limit-order check'\n );\n\n return { conditionMet, currentPrice, targetPrice, swapUsd };\n }\n\n async checkDCA(job: Job & { type: 'dca' }): Promise<PriceCheckResult> {\n const params = job.params;\n // DCA has no price condition — just verify the interval has been reached.\n // We add a 15-second buffer before declaring conditionMet so that by the\n // time the transaction is mined the on-chain block.timestamp is also\n // reliably past nextExecution, avoiding DCAIntervalNotReached reverts at\n // the execution boundary.\n const DCA_EXECUTION_BUFFER_MS = 15_000;\n const conditionMet =\n Date.now() >= params.nextExecution + DCA_EXECUTION_BUFFER_MS;\n const currentPrice = await this.source.getPrice(\n params.tokenIn,\n params.tokenOut\n );\n const swapUsd = await this._estimateUsd(\n params.tokenIn,\n params.amountPerSwap\n );\n\n this.log.debug(\n {\n jobId: job.id,\n nextExecution: params.nextExecution,\n conditionMet,\n swapUsd,\n },\n '[PriceChecker] DCA check'\n );\n\n return { conditionMet, currentPrice, targetPrice: 0n, swapUsd };\n }\n\n updateTokenPrice(token: string, usdPrice: number): void {\n this.tokenPricesUsd.set(token.toLowerCase(), usdPrice);\n }\n\n private async _estimateUsd(\n token: string,\n amountWei: string\n ): Promise<number> {\n const usdPerToken = this.tokenPricesUsd.get(token.toLowerCase()) ?? 0;\n // Resolve the token's actual decimal count (on-chain or cached).\n const decimals = await this.getDecimals(token);\n const divisor = 10 ** decimals;\n const amount = Number(BigInt(amountWei)) / divisor;\n return amount * usdPerToken;\n }\n}\n","import type { AutomationLogger } from './logger.js';\nimport { noopLogger } from './logger.js';\nimport type { Job } from './types.js';\n\n/**\n * RetryQueue – wraps jobs for retry-with-exponential-backoff scheduling.\n *\n * Backoff formula:\n * delay = min(base × 2^attempt, maxDelay) × (1 + jitter × rand)\n */\nexport class RetryQueue {\n private readonly baseDelayMs: number;\n private readonly maxDelayMs: number;\n private readonly jitter: number;\n private readonly log: AutomationLogger;\n private queue: Map<\n string,\n { job: Job; nextRetryAt: number; attempt: number }\n > = new Map();\n\n constructor(\n options?: {\n baseDelayMs?: number;\n maxDelayMs?: number;\n jitter?: number;\n },\n logger: AutomationLogger = noopLogger\n ) {\n this.baseDelayMs = options?.baseDelayMs ?? 5_000; // 5 s\n this.maxDelayMs = options?.maxDelayMs ?? 300_000; // 5 min cap\n this.jitter = options?.jitter ?? 0.2; // 20 % jitter\n this.log = logger;\n }\n\n /** Enqueue a job for retry after a calculated backoff delay. */\n enqueue(job: Job): void {\n const existing = this.queue.get(job.id);\n const attempt = existing ? existing.attempt + 1 : 0;\n const delay = this._backoffDelay(attempt);\n const nextRetryAt = Date.now() + delay;\n\n this.queue.set(job.id, { job, nextRetryAt, attempt });\n this.log.info(\n `[RetryQueue] job ${job.id} enqueued, attempt=${attempt}, delay=${delay}ms`\n );\n }\n\n /** Remove a job from the queue (e.g. after success or manual cancel). */\n remove(jobId: string): void {\n this.queue.delete(jobId);\n }\n\n /** Return all jobs whose retry time has arrived; removes them from the queue. */\n drainDue(now = Date.now()): Job[] {\n const due: Job[] = [];\n for (const [jobId, entry] of this.queue) {\n if (now >= entry.nextRetryAt) {\n due.push(entry.job);\n this.queue.delete(jobId);\n }\n }\n return due;\n }\n\n size(): number {\n return this.queue.size;\n }\n\n // ─── Private ──────────────────────────────────────────────────────\n\n private _backoffDelay(attempt: number): number {\n const exponential = this.baseDelayMs * 2 ** attempt;\n const capped = Math.min(exponential, this.maxDelayMs);\n const withJitter = capped * (1 + this.jitter * Math.random());\n return Math.floor(withJitter);\n }\n}\n","import type { AutomationLogger } from './logger.js';\nimport { noopLogger } from './logger.js';\nimport type {\n Job,\n SafetyCheckResult,\n SafetyConfig,\n SafetyViolation,\n} from './types.js';\n\n/** Default on-chain safety limits. */\nexport const DEFAULT_SAFETY_CONFIG: SafetyConfig = {\n maxSwapUsd: 10_000,\n maxSlippageBps: 500,\n maxRetries: 5,\n minExecutionIntervalSeconds: 30,\n globalPause: false,\n};\n\n/**\n * SafetyGuard – off-chain complement to AutomationManager's on-chain checks.\n *\n * Responsibilities:\n * 1. Validate a job satisfies configured safety bounds before the keeper\n * submits a transaction.\n * 2. Provide a global pause switch (circuit-breaker).\n * 3. Log every violation for audit purposes.\n *\n * The logger is injected rather than imported — the SDK ships with no logging\n * dependency. Pass your pino/winston/console instance, or omit for silence.\n */\nexport class SafetyGuard {\n private config: SafetyConfig;\n private violations: SafetyViolation[] = [];\n private readonly log: AutomationLogger;\n\n constructor(\n config: Partial<SafetyConfig> = {},\n logger: AutomationLogger = noopLogger\n ) {\n this.config = { ...DEFAULT_SAFETY_CONFIG, ...config };\n this.log = logger;\n this.log.info(\n {\n maxSwapUsd: this.config.maxSwapUsd,\n maxSlippageBps: this.config.maxSlippageBps,\n maxRetries: this.config.maxRetries,\n globalPause: this.config.globalPause,\n },\n '[SafetyGuard] initialized'\n );\n }\n\n // ─── Core check ───────────────────────────────────────────────────\n\n /**\n * Run all configured safety checks against a job.\n * Returns `{ ok: true }` if all pass, or `{ ok: false, violation }` on first failure.\n */\n check(\n job: Job,\n context: { swapUsd: number; currentTime?: number }\n ): SafetyCheckResult {\n const now = context.currentTime ?? Date.now();\n\n // Global pause (circuit-breaker)\n if (this.config.globalPause) {\n return this._fail(\n job.id,\n 'globalPause',\n 'Global pause is active — all execution halted'\n );\n }\n\n // Job status guard — only pending and active are executable\n if (job.status !== 'active' && job.status !== 'pending') {\n return this._fail(\n job.id,\n 'globalPause',\n `Job ${job.id} cannot be executed (status: ${job.status})`\n );\n }\n\n // Retry cap\n if (job.retries >= job.maxRetries) {\n return this._fail(\n job.id,\n 'maxRetries',\n `Job ${job.id} has exhausted retries (${job.retries}/${job.maxRetries})`\n );\n }\n\n // Swap USD cap\n if (context.swapUsd > this.config.maxSwapUsd) {\n return this._fail(\n job.id,\n 'maxSwapUsd',\n `Swap value $${context.swapUsd.toFixed(2)} exceeds limit $${this.config.maxSwapUsd}`\n );\n }\n\n // Job expiry\n if (job.expiresAt !== null && now >= job.expiresAt) {\n return this._fail(job.id, 'globalPause', `Job ${job.id} has expired`);\n }\n\n // DCA interval check\n if (job.type === 'dca') {\n const dcaParams = job.params;\n if (now < dcaParams.nextExecution) {\n const waitSec = Math.ceil((dcaParams.nextExecution - now) / 1000);\n return this._fail(\n job.id,\n 'minExecutionIntervalSeconds',\n `DCA interval not yet reached (${waitSec}s remaining)`\n );\n }\n }\n\n return { ok: true };\n }\n\n // ─── Config management ────────────────────────────────────────────\n\n updateConfig(patch: Partial<SafetyConfig>): void {\n this.config = { ...this.config, ...patch };\n this.log.info(patch, '[SafetyGuard] config updated');\n }\n\n getConfig(): Readonly<SafetyConfig> {\n return { ...this.config };\n }\n\n pauseAll(): void {\n this.config.globalPause = true;\n this.log.warn('[SafetyGuard] GLOBAL PAUSE ACTIVATED');\n }\n\n resumeAll(): void {\n this.config.globalPause = false;\n this.log.info('[SafetyGuard] global pause lifted');\n }\n\n isPaused(): boolean {\n return this.config.globalPause;\n }\n\n // ─── Audit log ────────────────────────────────────────────────────\n\n getViolations(): readonly SafetyViolation[] {\n return this.violations;\n }\n\n clearViolations(): void {\n this.violations = [];\n }\n\n // ─── Private ──────────────────────────────────────────────────────\n\n private _fail(\n jobId: string,\n rule: keyof SafetyConfig,\n detail: string\n ): SafetyCheckResult {\n const violation: SafetyViolation = {\n jobId,\n rule,\n detail,\n timestamp: Date.now(),\n };\n this.violations.push(violation);\n this.log.warn({ jobId, detail }, `[SafetyGuard] violation – ${rule}`);\n return { ok: false, violation };\n }\n}\n"],"mappings":"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","names":["import_core","logger","logger","logger"]}
package/dist/index.js CHANGED
@@ -237,8 +237,8 @@ var Executor = class {
237
237
  };
238
238
 
239
239
  // src/keeper-client.ts
240
+ import { AUTOMATION_MANAGER_ABI } from "@cfxdevkit/contracts";
240
241
  import { logger as logger2 } from "@cfxdevkit/core";
241
- import { AUTOMATION_MANAGER_ABI } from "@cfxdevkit/protocol";
242
242
  import {
243
243
  createPublicClient,
244
244
  createWalletClient,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/executor.ts","../src/keeper-client.ts","../src/logger.ts","../src/price-checker.ts","../src/retry-queue.ts","../src/safety-guard.ts"],"sourcesContent":["import { logger } from '@cfxdevkit/core';\nimport type { PriceChecker } from './price-checker.js';\nimport type { RetryQueue } from './retry-queue.js';\nimport type { SafetyGuard } from './safety-guard.js';\nimport type { DCAJob, Job, LimitOrderJob } from './types.js';\n\n// Minimal on-chain execution interface – fulfilled by the viem-based KeeperClientImpl\nexport interface KeeperClient {\n executeLimitOrder(\n jobId: string,\n owner: string,\n params: LimitOrderJob['params']\n ): Promise<{ txHash: string; amountOut?: string | null }>;\n\n executeDCATick(\n jobId: string,\n owner: string,\n params: DCAJob['params']\n ): Promise<{\n txHash: string;\n amountOut?: string | null;\n nextExecutionSec: number;\n }>;\n\n /** Read the terminal status of a job from the contract. */\n getOnChainStatus(\n jobId: `0x${string}`\n ): Promise<'active' | 'executed' | 'cancelled' | 'expired'>;\n}\n\n// Persistent job store – fulfilled by the DB-backed store in the application layer\nexport interface JobStore {\n getActiveJobs(): Promise<Job[]>;\n markActive(jobId: string): Promise<void>;\n markExecuted(\n jobId: string,\n txHash: string,\n amountOut?: string | null\n ): Promise<void>;\n /**\n * Record one completed DCA tick.\n * Updates swapsCompleted + nextExecution in params_json and inserts an\n * execution record. Sets status → 'executed' when all swaps are done,\n * otherwise keeps the job 'active' so subsequent ticks can run.\n */\n markDCATick(\n jobId: string,\n txHash: string,\n newSwapsCompleted: number,\n nextExecution: number,\n amountOut?: string | null\n ): Promise<void>;\n markFailed(jobId: string, error: string): Promise<void>;\n incrementRetry(jobId: string): Promise<void>;\n markExpired(jobId: string): Promise<void>;\n /** Mark a job cancelled — use when on-chain status is CANCELLED. */\n markCancelled(jobId: string): Promise<void>;\n /** Record the latest error message without changing status or incrementing retries. */\n updateLastError(jobId: string, error: string): Promise<void>;\n}\n\nexport interface ExecutorOptions {\n dryRun?: boolean;\n}\n\n/**\n * Executor – evaluates active jobs and submits on-chain transactions when\n * conditions are met and the SafetyGuard approves.\n */\nexport class Executor {\n private priceChecker: PriceChecker;\n private safetyGuard: SafetyGuard;\n private retryQueue: RetryQueue;\n private keeperClient: KeeperClient;\n private jobStore: JobStore;\n private dryRun: boolean;\n\n constructor(\n priceChecker: PriceChecker,\n safetyGuard: SafetyGuard,\n retryQueue: RetryQueue,\n keeperClient: KeeperClient,\n jobStore: JobStore,\n options: ExecutorOptions = {}\n ) {\n this.priceChecker = priceChecker;\n this.safetyGuard = safetyGuard;\n this.retryQueue = retryQueue;\n this.keeperClient = keeperClient;\n this.jobStore = jobStore;\n this.dryRun = options.dryRun ?? false;\n }\n\n /**\n * Process a single job tick.\n */\n async processTick(job: Job): Promise<void> {\n // 1. Quick expiry guard\n if (job.expiresAt !== null && Date.now() >= job.expiresAt) {\n logger.info(`[Executor] job ${job.id} expired`);\n await this.jobStore.markExpired(job.id);\n return;\n }\n\n try {\n // Transition pending → active on first pickup\n if (job.status === 'pending') {\n await this.jobStore.markActive(job.id);\n }\n\n if (job.type === 'limit_order') {\n await this._processLimitOrder(job as LimitOrderJob);\n } else if (job.type === 'dca') {\n await this._processDCA(job as DCAJob);\n }\n // 'twap' and 'swap' are not yet implemented — silently skip\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n\n // ── Transient errors — handle silently without logging as ERROR ──────────\n\n // PriceConditionNotMet is a transient race: the off-chain check passed but\n // the on-chain oracle ticked back before the tx was mined.\n if (message.includes('PriceConditionNotMet')) {\n logger.debug(\n `[Executor] job ${job.id}: price condition no longer met at execution time — will retry next tick`\n );\n return;\n }\n\n // DCAIntervalNotReached means the on-chain interval timer hasn't elapsed yet.\n if (message.includes('DCAIntervalNotReached')) {\n logger.debug(\n `[Executor] job ${job.id}: DCA interval not yet reached at execution time — will retry next tick`\n );\n return;\n }\n\n // Receipt-not-found: the node didn't have the receipt indexed yet.\n if (\n message.includes('could not be found') ||\n message.includes('TransactionReceiptNotFoundError')\n ) {\n logger.debug(\n `[Executor] job ${job.id}: receipt not yet indexed — will retry next tick`\n );\n return;\n }\n\n // Slippage exceeded is transient: pool price moved between simulation and mine.\n if (message.includes('Slippage exceeded')) {\n logger.debug(\n `[Executor] job ${job.id}: slippage exceeded at execution time — will retry next tick`\n );\n await this.jobStore.incrementRetry(job.id);\n await this.jobStore.updateLastError(job.id, message);\n return;\n }\n\n // ── Unexpected errors — log full cause for diagnosis ─────────────────────\n if (err instanceof Error && (err as { cause?: unknown }).cause) {\n logger.error(\n { cause: (err as { cause?: unknown }).cause },\n '[Executor] raw error cause'\n );\n }\n\n // JobNotFound: on-chain job ID doesn't exist (contract redeployed).\n if (message.includes('JobNotFound')) {\n const ocId = (job as { onChainJobId?: string }).onChainJobId;\n logger.warn(\n { jobId: job.id, onChainJobId: ocId },\n '[Executor] JobNotFound — on_chain_job_id not found on current contract ' +\n '(likely left over from an old deployment) — marking cancelled'\n );\n await this.jobStore.markCancelled(job.id);\n return;\n }\n\n // JobNotActive: job is EXECUTED/CANCELLED/EXPIRED on-chain but DB is out of sync.\n if (message.includes('JobNotActive')) {\n const ocId = (job as { onChainJobId?: string }).onChainJobId as\n | `0x${string}`\n | undefined;\n let onChainStatus: 'active' | 'executed' | 'cancelled' | 'expired' =\n 'cancelled';\n if (ocId) {\n try {\n onChainStatus = await this.keeperClient.getOnChainStatus(ocId);\n } catch (statusErr) {\n logger.warn(\n { jobId: job.id, err: statusErr },\n '[Executor] could not read on-chain status — defaulting to cancelled'\n );\n }\n }\n if (onChainStatus === 'executed') {\n logger.warn(\n { jobId: job.id, onChainJobId: ocId },\n '[Executor] job EXECUTED on-chain but DB was out of sync — marking executed'\n );\n await this.jobStore.markExecuted(job.id, 'chain-sync');\n } else {\n logger.warn(\n { jobId: job.id, onChainJobId: ocId, onChainStatus },\n '[Executor] job is CANCELLED/EXPIRED on-chain — marking cancelled in DB'\n );\n await this.jobStore.markCancelled(job.id);\n }\n return;\n }\n\n logger.error(`[Executor] job ${job.id} failed: ${message}`);\n const nextRetries = job.retries + 1;\n if (job.retries < job.maxRetries) {\n await this.jobStore.incrementRetry(job.id);\n }\n if (nextRetries < job.maxRetries) {\n this.retryQueue.enqueue({ ...job, retries: nextRetries });\n }\n await this.jobStore.markFailed(job.id, message);\n }\n }\n\n /**\n * Process all active jobs + due retries in one tick.\n */\n async runAllTicks(): Promise<void> {\n const activeJobs = await this.jobStore.getActiveJobs();\n const retries = this.retryQueue.drainDue();\n\n const all = [...activeJobs, ...retries];\n logger.info(\n `[Executor] tick – ${activeJobs.length} active, ${retries.length} retries`\n );\n\n await Promise.allSettled(all.map((job) => this.processTick(job)));\n }\n\n // ─── Private ────────────────────────────────────────────────────────────────\n\n private async _processLimitOrder(job: LimitOrderJob): Promise<void> {\n const priceResult = await this.priceChecker.checkLimitOrder(job);\n\n if (!priceResult.conditionMet) {\n logger.info(\n {\n jobId: job.id,\n currentPrice: priceResult.currentPrice.toString(),\n targetPrice: priceResult.targetPrice.toString(),\n direction: job.params.direction,\n },\n `[Executor] limit-order ${job.id}: condition not met – waiting`\n );\n return;\n }\n\n const safetyResult = this.safetyGuard.check(job, {\n swapUsd: priceResult.swapUsd,\n });\n if (!safetyResult.ok) {\n logger.warn(\n { violation: safetyResult.violation },\n `[Executor] limit-order ${job.id} blocked by safety guard`\n );\n return;\n }\n\n if (this.dryRun) {\n logger.info(`[Executor][dryRun] would execute limit-order ${job.id}`);\n return;\n }\n\n logger.info(`[Executor] executing limit-order ${job.id}`);\n const onChainJobId = job.onChainJobId;\n if (!onChainJobId) {\n logger.warn(\n `[Executor] limit-order ${job.id} has no onChainJobId – skipping until registered on-chain`\n );\n return;\n }\n const { txHash, amountOut } = await this.keeperClient.executeLimitOrder(\n onChainJobId,\n job.owner,\n job.params\n );\n await this.jobStore.markExecuted(job.id, txHash, amountOut);\n logger.info(`[Executor] limit-order ${job.id} executed – tx ${txHash}`);\n }\n\n private async _processDCA(job: DCAJob): Promise<void> {\n const priceResult = await this.priceChecker.checkDCA(job);\n\n if (!priceResult.conditionMet) {\n logger.info(\n {\n jobId: job.id,\n nextExecution: job.params.nextExecution,\n now: Date.now(),\n secsRemaining: Math.round(\n (job.params.nextExecution - Date.now()) / 1000\n ),\n },\n `[Executor] DCA ${job.id}: interval not reached`\n );\n return;\n }\n\n const safetyResult = this.safetyGuard.check(job, {\n swapUsd: priceResult.swapUsd,\n });\n if (!safetyResult.ok) {\n logger.warn(\n { violation: safetyResult.violation },\n `[Executor] DCA ${job.id} blocked by safety guard`\n );\n return;\n }\n\n if (this.dryRun) {\n logger.info(`[Executor][dryRun] would execute DCA tick for ${job.id}`);\n return;\n }\n\n logger.info(`[Executor] executing DCA tick ${job.id}`);\n const onChainJobId = job.onChainJobId;\n if (!onChainJobId) {\n logger.warn(\n `[Executor] DCA job ${job.id} has no onChainJobId – skipping until registered on-chain`\n );\n return;\n }\n const { txHash, amountOut, nextExecutionSec } =\n await this.keeperClient.executeDCATick(\n onChainJobId,\n job.owner,\n job.params\n );\n\n const newSwapsCompleted = job.params.swapsCompleted + 1;\n // Use the on-chain nextExecution (returned by KeeperClient after reading\n // the contract state post-tick) rather than computing it locally.\n const nextExecutionMs = nextExecutionSec * 1000;\n await this.jobStore.markDCATick(\n job.id,\n txHash,\n newSwapsCompleted,\n nextExecutionMs,\n amountOut\n );\n\n logger.info(\n {\n jobId: job.id,\n txHash,\n swapsCompleted: newSwapsCompleted,\n total: job.params.totalSwaps,\n },\n `[Executor] DCA tick executed – ${newSwapsCompleted}/${job.params.totalSwaps}`\n );\n }\n}\n","/**\n * KeeperClient — viem implementation of the KeeperClient interface.\n *\n * Wraps AutomationManager contract calls for executeLimitOrder and executeDCATick.\n * The executor wallet is a keeper (not a custodian) — it cannot move user funds\n * unless the user has set an on-chain allowance for that specific swap.\n */\n\nimport { logger } from '@cfxdevkit/core';\nimport { AUTOMATION_MANAGER_ABI } from '@cfxdevkit/protocol';\nimport {\n type Address,\n createPublicClient,\n createWalletClient,\n type Hash,\n http,\n} from 'viem';\nimport { privateKeyToAccount } from 'viem/accounts';\nimport type { KeeperClient as IKeeperClient } from './executor.js';\nimport type { DCAJob, LimitOrderJob } from './types.js';\n\n// --------------------------------------------------------------------------\n// Helpers\n// --------------------------------------------------------------------------\n\n/**\n * Decode the amountOut from a swap receipt by finding the last ERC-20\n * Transfer event whose `to` address is the job owner (the swap recipient).\n * Returns the raw amount as a decimal string, or null if not found.\n */\nfunction decodeAmountOut(\n logs: readonly { topics: readonly string[]; data: string; address: string }[],\n owner: Address\n): string | null {\n const TRANSFER_TOPIC =\n '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';\n const ownerLower = owner.toLowerCase();\n // Walk logs in reverse — the last Transfer to owner is tokenOut\n for (let i = logs.length - 1; i >= 0; i--) {\n const log = logs[i];\n if (\n log.topics[0]?.toLowerCase() === TRANSFER_TOPIC &&\n log.topics[2] &&\n `0x${log.topics[2].slice(26)}`.toLowerCase() === ownerLower\n ) {\n try {\n return BigInt(log.data).toString();\n } catch {\n return null;\n }\n }\n }\n return null;\n}\n\n// --------------------------------------------------------------------------\n// Config\n// --------------------------------------------------------------------------\nexport interface KeeperClientConfig {\n /** RPC endpoint for Conflux eSpace */\n rpcUrl: string;\n /** Hex private key (0x-prefixed) of the keeper wallet */\n privateKey: `0x${string}`;\n /** Deployed AutomationManager contract address */\n contractAddress: Address;\n /**\n * Swappi router address.\n * Testnet: 0x873789aaf553fd0b4252d0d2b72c6331c47aff2e\n * Mainnet: 0xE37B52296b0bAA91412cD0Cd97975B0805037B84 (Swappi v2 — only address with deployed code;\n * old 0x62B0873... has no bytecode)\n */\n swappiRouter: Address;\n /**\n * Maximum gas price in gwei before aborting execution (circuit breaker).\n * Defaults to 1000 gwei.\n */\n maxGasPriceGwei?: bigint;\n /** Chain definition — pass the viem chain object (espaceTestnet / espaceMainnet) */\n chain: Parameters<typeof createWalletClient>[0]['chain'];\n /** RPC request timeout in milliseconds (applies to read/simulate/write calls). */\n rpcTimeoutMs?: number;\n}\n\n// --------------------------------------------------------------------------\n// KeeperClientImpl\n// --------------------------------------------------------------------------\nexport class KeeperClientImpl implements IKeeperClient {\n private readonly walletClient;\n private readonly publicClient;\n private readonly contractAddress: Address;\n private readonly swappiRouter: Address;\n private readonly maxGasPriceGwei: bigint;\n private readonly rpcTimeoutMs: number;\n private readonly account;\n\n constructor(config: KeeperClientConfig) {\n this.account = privateKeyToAccount(config.privateKey);\n this.contractAddress = config.contractAddress;\n this.swappiRouter = config.swappiRouter;\n this.maxGasPriceGwei = config.maxGasPriceGwei ?? 1000n;\n this.rpcTimeoutMs = config.rpcTimeoutMs ?? 120_000; // default 2 minutes\n\n this.publicClient = createPublicClient({\n chain: config.chain,\n transport: http(config.rpcUrl),\n });\n\n this.walletClient = createWalletClient({\n chain: config.chain,\n transport: http(config.rpcUrl),\n account: this.account,\n });\n }\n\n private withTimeout<T>(\n fn: (signal: AbortSignal) => Promise<T>,\n timeoutMs = this.rpcTimeoutMs\n ): Promise<T> {\n const controller = new AbortController();\n const id = setTimeout(() => controller.abort(), timeoutMs);\n return fn(controller.signal).finally(() => clearTimeout(id));\n }\n\n // --------------------------------------------------------------------------\n // Helpers\n // --------------------------------------------------------------------------\n\n /**\n * Query the on-chain status of a job.\n * Returns one of: 'active' | 'executed' | 'cancelled' | 'expired'.\n * Throws if the contract call fails (e.g. job not found).\n */\n async getOnChainStatus(\n onChainJobId: `0x${string}`\n ): Promise<'active' | 'executed' | 'cancelled' | 'expired'> {\n const job = (await this.withTimeout((_signal) =>\n this.publicClient.readContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'getJob',\n args: [onChainJobId],\n })\n )) as { status: number };\n // JobStatus enum: 0=ACTIVE, 1=EXECUTED, 2=CANCELLED, 3=EXPIRED\n switch (job.status) {\n case 0:\n return 'active';\n case 1:\n return 'executed';\n case 2:\n return 'cancelled';\n case 3:\n return 'expired';\n default:\n return 'cancelled'; // unknown → treat as cancelled (stop retrying)\n }\n }\n\n /** Guard: abort if chain is paused or gas price is too high */\n private async preflightCheck(): Promise<void> {\n // Check on-chain pause flag\n const isPaused = await this.withTimeout((_signal) =>\n this.publicClient.readContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'paused',\n })\n );\n if (isPaused) {\n throw new Error('AutomationManager is paused on-chain');\n }\n\n // Check gas price circuit breaker\n const gasPrice = await Promise.race([\n this.publicClient.getGasPrice(),\n new Promise<never>((_, rej) =>\n setTimeout(\n () => rej(new Error('getGasPrice timeout')),\n this.rpcTimeoutMs\n )\n ),\n ]);\n const gasPriceGwei = gasPrice / 1_000_000_000n;\n if (gasPriceGwei > this.maxGasPriceGwei) {\n throw new Error(\n `Gas price ${gasPriceGwei} gwei exceeds cap ${this.maxGasPriceGwei} gwei`\n );\n }\n }\n\n /**\n * Build minimal swap calldata for a token-in/token-out pair via Swappi.\n *\n * In a production keeper the calldata would be built via a DEX aggregator\n * (e.g. OKX DEX API) to get optimal routing. For the MVP we encode the\n * simplest Swappi path: `swapExactTokensForTokens(amountIn, minOut, path, to, deadline)`.\n *\n * NOTE: The AutomationManager does NOT use this calldata for custody;\n * it only uses it to call the router on behalf of the user's pre-approved allowance.\n */\n private buildSwapCalldata(\n tokenIn: Address,\n tokenOut: Address,\n amountIn: bigint,\n minAmountOut: bigint,\n recipient: Address\n ): `0x${string}` {\n // swapExactTokensForTokens(uint256,uint256,address[],address,uint256)\n // selector = 0x38ed1739\n const selector = '38ed1739';\n const deadline = BigInt(Math.floor(Date.now() / 1000) + 1800); // 30 min\n const encode256 = (n: bigint) => n.toString(16).padStart(64, '0');\n const encodeAddr = (a: string) =>\n a.slice(2).toLowerCase().padStart(64, '0');\n\n // path is dynamic array: offset + length + [tokenIn, tokenOut]\n const pathOffset = (5 * 32).toString(16).padStart(64, '0'); // 5 static params before dynamic\n const pathLength = encode256(2n);\n const pathData = encodeAddr(tokenIn) + encodeAddr(tokenOut);\n\n const calldata =\n '0x' +\n selector +\n encode256(amountIn) +\n encode256(minAmountOut) +\n pathOffset +\n encodeAddr(recipient) +\n encode256(deadline) +\n pathLength +\n pathData;\n\n return calldata as `0x${string}`;\n }\n\n // --------------------------------------------------------------------------\n // IKeeperClient interface implementation\n // --------------------------------------------------------------------------\n\n async executeLimitOrder(\n jobId: string,\n owner: string,\n params: LimitOrderJob['params']\n ): Promise<{ txHash: string; amountOut: string | null }> {\n await this.preflightCheck();\n\n const amountIn = BigInt(params.amountIn);\n // Pass 0 for the router-level minAmountOut so the router never reverts due\n // to slippage — the AutomationManager contract enforces params.minAmountOut\n // via a post-swap balanceOf check, which is the canonical guard.\n const minAmountOut = 0n;\n\n const swapCalldata = this.buildSwapCalldata(\n params.tokenIn as Address,\n params.tokenOut as Address,\n amountIn,\n minAmountOut,\n owner as Address\n );\n\n logger.info(\n {\n jobId,\n tokenIn: params.tokenIn,\n tokenOut: params.tokenOut,\n amountIn: params.amountIn,\n },\n '[KeeperClient] executeLimitOrder'\n );\n\n // Simulate first — throws with a decoded revert reason if it would fail.\n try {\n await this.withTimeout((_signal) =>\n this.publicClient.simulateContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeLimitOrder',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n account: this.account.address,\n })\n );\n } catch (simErr: unknown) {\n const msg = simErr instanceof Error ? simErr.message : String(simErr);\n logger.error(\n { jobId, error: msg.slice(0, 500) },\n '[KeeperClient] executeLimitOrder simulation reverted'\n );\n throw simErr;\n }\n\n const hash: Hash = await this.withTimeout((_signal) =>\n this.walletClient.writeContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeLimitOrder',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n chain: undefined,\n account: this.account,\n })\n );\n\n logger.info({ jobId, hash }, '[KeeperClient] limitOrder tx submitted');\n\n const receipt = await Promise.race([\n this.publicClient.waitForTransactionReceipt({ hash } as Parameters<\n typeof this.publicClient.waitForTransactionReceipt\n >[0]),\n new Promise<never>((_, rej) =>\n setTimeout(\n () => rej(new Error('waitForTransactionReceipt timeout')),\n this.rpcTimeoutMs\n )\n ),\n ]);\n if ((receipt as { status: string }).status !== 'success') {\n let reason: string = (hash as string) ?? 'unknown';\n try {\n await this.withTimeout((_signal) =>\n this.publicClient.simulateContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeLimitOrder',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n account: this.account.address,\n })\n );\n } catch (simErr: unknown) {\n if (simErr instanceof Error) reason = simErr.message;\n }\n throw new Error(`Limit order tx reverted: ${reason}`);\n }\n\n const amountOut = decodeAmountOut(receipt.logs, owner as Address);\n logger.info(\n { jobId, hash, blockNumber: receipt.blockNumber.toString(), amountOut },\n '[KeeperClient] limitOrder confirmed'\n );\n return { txHash: hash, amountOut };\n }\n\n async executeDCATick(\n jobId: string,\n owner: string,\n params: DCAJob['params']\n ): Promise<{\n txHash: string;\n amountOut: string | null;\n nextExecutionSec: number;\n }> {\n await this.preflightCheck();\n\n // On-chain preflight: verify the DCA interval has actually been reached\n const onChainParams = (await this.publicClient.readContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'getDCAJob',\n args: [jobId as `0x${string}`],\n })) as {\n intervalSeconds: bigint;\n nextExecution: bigint;\n swapsCompleted: bigint;\n totalSwaps: bigint;\n };\n const nowSec = BigInt(Math.floor(Date.now() / 1000));\n if (nowSec < onChainParams.nextExecution) {\n throw new Error(\n `DCAIntervalNotReached(${onChainParams.nextExecution}) — on-chain interval not reached yet ` +\n `(now=${nowSec}, next=${onChainParams.nextExecution}, ` +\n `remaining=${onChainParams.nextExecution - nowSec}s)`\n );\n }\n\n const amountPerTick = BigInt(params.amountPerSwap);\n const minAmountOut = 0n;\n\n const swapCalldata = this.buildSwapCalldata(\n params.tokenIn as Address,\n params.tokenOut as Address,\n amountPerTick,\n minAmountOut,\n owner as Address\n );\n\n logger.info(\n {\n jobId,\n tokenIn: params.tokenIn,\n tokenOut: params.tokenOut,\n amountPerTick: params.amountPerSwap,\n },\n '[KeeperClient] executeDCATick'\n );\n\n try {\n await this.withTimeout((_signal) =>\n this.publicClient.simulateContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeDCATick',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n account: this.account.address,\n })\n );\n } catch (simErr: unknown) {\n const msg = simErr instanceof Error ? simErr.message : String(simErr);\n logger.error(\n { jobId, error: msg.slice(0, 500) },\n '[KeeperClient] executeDCATick simulation reverted'\n );\n throw simErr;\n }\n\n const hash: Hash = await this.withTimeout((_signal) =>\n this.walletClient.writeContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeDCATick',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n chain: undefined,\n account: this.account,\n })\n );\n\n logger.info({ jobId, hash }, '[KeeperClient] dca tx submitted');\n\n const receipt = await Promise.race([\n this.publicClient.waitForTransactionReceipt({ hash } as Parameters<\n typeof this.publicClient.waitForTransactionReceipt\n >[0]),\n new Promise<never>((_, rej) =>\n setTimeout(\n () => rej(new Error('waitForTransactionReceipt timeout')),\n this.rpcTimeoutMs\n )\n ),\n ]);\n if ((receipt as { status: string }).status !== 'success') {\n let reason: string = (hash as string) ?? 'unknown';\n try {\n await this.withTimeout((_signal) =>\n this.publicClient.simulateContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeDCATick',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n account: this.account.address,\n })\n );\n } catch (simErr: unknown) {\n if (simErr instanceof Error) reason = simErr.message;\n }\n throw new Error(`DCA tick tx reverted: ${reason}`);\n }\n\n const amountOut = decodeAmountOut(receipt.logs, owner as Address);\n // Re-read the contract's nextExecution after the tick so the DB is exactly\n // in sync with what the contract stored.\n const postTickParams = (await this.publicClient.readContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'getDCAJob',\n args: [jobId as `0x${string}`],\n })) as {\n intervalSeconds: bigint;\n nextExecution: bigint;\n swapsCompleted: bigint;\n totalSwaps: bigint;\n };\n const nextExecutionSec = Number(postTickParams.nextExecution);\n logger.info(\n {\n jobId,\n hash,\n blockNumber: receipt.blockNumber.toString(),\n amountOut,\n nextExecutionSec,\n },\n '[KeeperClient] dca confirmed'\n );\n return { txHash: hash, amountOut, nextExecutionSec };\n }\n}\n","/**\n * Minimal injectable logger interface for the automation module.\n *\n * The SDK ships with NO logging dependency. Consumers can pass any compatible\n * logger (pino, winston, console wrapper …) or omit it to get silent behaviour.\n */\nexport interface AutomationLogger {\n info(msg: string | object, ...args: unknown[]): void;\n warn(msg: string | object, ...args: unknown[]): void;\n debug(msg: string | object, ...args: unknown[]): void;\n error(msg: string | object, ...args: unknown[]): void;\n}\n\n/** Default no-op logger — used when no logger is supplied. */\nexport const noopLogger: AutomationLogger = {\n info: () => {},\n warn: () => {},\n debug: () => {},\n error: () => {},\n};\n","import type { AutomationLogger } from './logger.js';\nimport { noopLogger } from './logger.js';\nimport type { Job } from './types.js';\n\n/**\n * Price source adapter interface.\n *\n * Returns the spot price of `tokenIn` denominated in `tokenOut`, scaled by\n * 1e18. Implementations may call a DEX (Swappi, etc.), an oracle, or a mock.\n */\nexport interface PriceSource {\n /**\n * Returns 0n if the pair is unknown or price cannot be fetched.\n */\n getPrice(tokenIn: string, tokenOut: string): Promise<bigint>;\n}\n\nexport interface PriceCheckResult {\n conditionMet: boolean;\n currentPrice: bigint;\n targetPrice: bigint;\n swapUsd: number;\n}\n\n/**\n * Callback that resolves a token address to its ERC-20 decimal count.\n * Implementations should cache the result for performance.\n */\nexport type DecimalsResolver = (token: string) => Promise<number>;\n\n/** Default resolver — assumes 18 decimals for every token. */\nconst defaultDecimalsResolver: DecimalsResolver = async () => 18;\n\n/**\n * PriceChecker – queries a price source and evaluates whether a job's\n * trigger condition is currently met.\n */\nexport class PriceChecker {\n private source: PriceSource;\n private tokenPricesUsd: Map<string, number>;\n /**\n * Resolves a token's decimal count. Called lazily when _estimateUsd needs\n * to convert raw wei amounts into human-readable values. The resolver may\n * query the chain, a static map, or simply return 18.\n */\n private getDecimals: DecimalsResolver;\n private readonly log: AutomationLogger;\n\n constructor(\n source: PriceSource,\n tokenPricesUsd: Map<string, number> = new Map(),\n logger: AutomationLogger = noopLogger,\n getDecimals: DecimalsResolver = defaultDecimalsResolver\n ) {\n this.source = source;\n this.tokenPricesUsd = tokenPricesUsd;\n this.getDecimals = getDecimals;\n this.log = logger;\n }\n\n async checkLimitOrder(\n job: Job & { type: 'limit_order' }\n ): Promise<PriceCheckResult> {\n const params = job.params;\n const currentPrice = await this.source.getPrice(\n params.tokenIn,\n params.tokenOut\n );\n const targetPrice = BigInt(params.targetPrice);\n\n let conditionMet: boolean;\n if (params.direction === 'gte') {\n conditionMet = currentPrice >= targetPrice;\n } else {\n conditionMet = currentPrice <= targetPrice;\n }\n\n const swapUsd = await this._estimateUsd(params.tokenIn, params.amountIn);\n\n this.log.debug(\n {\n jobId: job.id,\n currentPrice: currentPrice.toString(),\n targetPrice: targetPrice.toString(),\n conditionMet,\n swapUsd,\n },\n '[PriceChecker] limit-order check'\n );\n\n return { conditionMet, currentPrice, targetPrice, swapUsd };\n }\n\n async checkDCA(job: Job & { type: 'dca' }): Promise<PriceCheckResult> {\n const params = job.params;\n // DCA has no price condition — just verify the interval has been reached.\n // We add a 15-second buffer before declaring conditionMet so that by the\n // time the transaction is mined the on-chain block.timestamp is also\n // reliably past nextExecution, avoiding DCAIntervalNotReached reverts at\n // the execution boundary.\n const DCA_EXECUTION_BUFFER_MS = 15_000;\n const conditionMet =\n Date.now() >= params.nextExecution + DCA_EXECUTION_BUFFER_MS;\n const currentPrice = await this.source.getPrice(\n params.tokenIn,\n params.tokenOut\n );\n const swapUsd = await this._estimateUsd(\n params.tokenIn,\n params.amountPerSwap\n );\n\n this.log.debug(\n {\n jobId: job.id,\n nextExecution: params.nextExecution,\n conditionMet,\n swapUsd,\n },\n '[PriceChecker] DCA check'\n );\n\n return { conditionMet, currentPrice, targetPrice: 0n, swapUsd };\n }\n\n updateTokenPrice(token: string, usdPrice: number): void {\n this.tokenPricesUsd.set(token.toLowerCase(), usdPrice);\n }\n\n private async _estimateUsd(\n token: string,\n amountWei: string\n ): Promise<number> {\n const usdPerToken = this.tokenPricesUsd.get(token.toLowerCase()) ?? 0;\n // Resolve the token's actual decimal count (on-chain or cached).\n const decimals = await this.getDecimals(token);\n const divisor = 10 ** decimals;\n const amount = Number(BigInt(amountWei)) / divisor;\n return amount * usdPerToken;\n }\n}\n","import type { AutomationLogger } from './logger.js';\nimport { noopLogger } from './logger.js';\nimport type { Job } from './types.js';\n\n/**\n * RetryQueue – wraps jobs for retry-with-exponential-backoff scheduling.\n *\n * Backoff formula:\n * delay = min(base × 2^attempt, maxDelay) × (1 + jitter × rand)\n */\nexport class RetryQueue {\n private readonly baseDelayMs: number;\n private readonly maxDelayMs: number;\n private readonly jitter: number;\n private readonly log: AutomationLogger;\n private queue: Map<\n string,\n { job: Job; nextRetryAt: number; attempt: number }\n > = new Map();\n\n constructor(\n options?: {\n baseDelayMs?: number;\n maxDelayMs?: number;\n jitter?: number;\n },\n logger: AutomationLogger = noopLogger\n ) {\n this.baseDelayMs = options?.baseDelayMs ?? 5_000; // 5 s\n this.maxDelayMs = options?.maxDelayMs ?? 300_000; // 5 min cap\n this.jitter = options?.jitter ?? 0.2; // 20 % jitter\n this.log = logger;\n }\n\n /** Enqueue a job for retry after a calculated backoff delay. */\n enqueue(job: Job): void {\n const existing = this.queue.get(job.id);\n const attempt = existing ? existing.attempt + 1 : 0;\n const delay = this._backoffDelay(attempt);\n const nextRetryAt = Date.now() + delay;\n\n this.queue.set(job.id, { job, nextRetryAt, attempt });\n this.log.info(\n `[RetryQueue] job ${job.id} enqueued, attempt=${attempt}, delay=${delay}ms`\n );\n }\n\n /** Remove a job from the queue (e.g. after success or manual cancel). */\n remove(jobId: string): void {\n this.queue.delete(jobId);\n }\n\n /** Return all jobs whose retry time has arrived; removes them from the queue. */\n drainDue(now = Date.now()): Job[] {\n const due: Job[] = [];\n for (const [jobId, entry] of this.queue) {\n if (now >= entry.nextRetryAt) {\n due.push(entry.job);\n this.queue.delete(jobId);\n }\n }\n return due;\n }\n\n size(): number {\n return this.queue.size;\n }\n\n // ─── Private ──────────────────────────────────────────────────────\n\n private _backoffDelay(attempt: number): number {\n const exponential = this.baseDelayMs * 2 ** attempt;\n const capped = Math.min(exponential, this.maxDelayMs);\n const withJitter = capped * (1 + this.jitter * Math.random());\n return Math.floor(withJitter);\n }\n}\n","import type { AutomationLogger } from './logger.js';\nimport { noopLogger } from './logger.js';\nimport type {\n Job,\n SafetyCheckResult,\n SafetyConfig,\n SafetyViolation,\n} from './types.js';\n\n/** Default on-chain safety limits. */\nexport const DEFAULT_SAFETY_CONFIG: SafetyConfig = {\n maxSwapUsd: 10_000,\n maxSlippageBps: 500,\n maxRetries: 5,\n minExecutionIntervalSeconds: 30,\n globalPause: false,\n};\n\n/**\n * SafetyGuard – off-chain complement to AutomationManager's on-chain checks.\n *\n * Responsibilities:\n * 1. Validate a job satisfies configured safety bounds before the keeper\n * submits a transaction.\n * 2. Provide a global pause switch (circuit-breaker).\n * 3. Log every violation for audit purposes.\n *\n * The logger is injected rather than imported — the SDK ships with no logging\n * dependency. Pass your pino/winston/console instance, or omit for silence.\n */\nexport class SafetyGuard {\n private config: SafetyConfig;\n private violations: SafetyViolation[] = [];\n private readonly log: AutomationLogger;\n\n constructor(\n config: Partial<SafetyConfig> = {},\n logger: AutomationLogger = noopLogger\n ) {\n this.config = { ...DEFAULT_SAFETY_CONFIG, ...config };\n this.log = logger;\n this.log.info(\n {\n maxSwapUsd: this.config.maxSwapUsd,\n maxSlippageBps: this.config.maxSlippageBps,\n maxRetries: this.config.maxRetries,\n globalPause: this.config.globalPause,\n },\n '[SafetyGuard] initialized'\n );\n }\n\n // ─── Core check ───────────────────────────────────────────────────\n\n /**\n * Run all configured safety checks against a job.\n * Returns `{ ok: true }` if all pass, or `{ ok: false, violation }` on first failure.\n */\n check(\n job: Job,\n context: { swapUsd: number; currentTime?: number }\n ): SafetyCheckResult {\n const now = context.currentTime ?? Date.now();\n\n // Global pause (circuit-breaker)\n if (this.config.globalPause) {\n return this._fail(\n job.id,\n 'globalPause',\n 'Global pause is active — all execution halted'\n );\n }\n\n // Job status guard — only pending and active are executable\n if (job.status !== 'active' && job.status !== 'pending') {\n return this._fail(\n job.id,\n 'globalPause',\n `Job ${job.id} cannot be executed (status: ${job.status})`\n );\n }\n\n // Retry cap\n if (job.retries >= job.maxRetries) {\n return this._fail(\n job.id,\n 'maxRetries',\n `Job ${job.id} has exhausted retries (${job.retries}/${job.maxRetries})`\n );\n }\n\n // Swap USD cap\n if (context.swapUsd > this.config.maxSwapUsd) {\n return this._fail(\n job.id,\n 'maxSwapUsd',\n `Swap value $${context.swapUsd.toFixed(2)} exceeds limit $${this.config.maxSwapUsd}`\n );\n }\n\n // Job expiry\n if (job.expiresAt !== null && now >= job.expiresAt) {\n return this._fail(job.id, 'globalPause', `Job ${job.id} has expired`);\n }\n\n // DCA interval check\n if (job.type === 'dca') {\n const dcaParams = job.params;\n if (now < dcaParams.nextExecution) {\n const waitSec = Math.ceil((dcaParams.nextExecution - now) / 1000);\n return this._fail(\n job.id,\n 'minExecutionIntervalSeconds',\n `DCA interval not yet reached (${waitSec}s remaining)`\n );\n }\n }\n\n return { ok: true };\n }\n\n // ─── Config management ────────────────────────────────────────────\n\n updateConfig(patch: Partial<SafetyConfig>): void {\n this.config = { ...this.config, ...patch };\n this.log.info(patch, '[SafetyGuard] config updated');\n }\n\n getConfig(): Readonly<SafetyConfig> {\n return { ...this.config };\n }\n\n pauseAll(): void {\n this.config.globalPause = true;\n this.log.warn('[SafetyGuard] GLOBAL PAUSE ACTIVATED');\n }\n\n resumeAll(): void {\n this.config.globalPause = false;\n this.log.info('[SafetyGuard] global pause lifted');\n }\n\n isPaused(): boolean {\n return this.config.globalPause;\n }\n\n // ─── Audit log ────────────────────────────────────────────────────\n\n getViolations(): readonly SafetyViolation[] {\n return this.violations;\n }\n\n clearViolations(): void {\n this.violations = [];\n }\n\n // ─── Private ──────────────────────────────────────────────────────\n\n private _fail(\n jobId: string,\n rule: keyof SafetyConfig,\n detail: string\n ): SafetyCheckResult {\n const violation: SafetyViolation = {\n jobId,\n rule,\n detail,\n timestamp: Date.now(),\n };\n this.violations.push(violation);\n this.log.warn({ jobId, detail }, `[SafetyGuard] violation – ${rule}`);\n return { ok: false, violation };\n }\n}\n"],"mappings":"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","names":["logger","logger","logger","logger"]}
1
+ {"version":3,"sources":["../src/executor.ts","../src/keeper-client.ts","../src/logger.ts","../src/price-checker.ts","../src/retry-queue.ts","../src/safety-guard.ts"],"sourcesContent":["import { logger } from '@cfxdevkit/core';\nimport type { PriceChecker } from './price-checker.js';\nimport type { RetryQueue } from './retry-queue.js';\nimport type { SafetyGuard } from './safety-guard.js';\nimport type { DCAJob, Job, LimitOrderJob } from './types.js';\n\n// Minimal on-chain execution interface – fulfilled by the viem-based KeeperClientImpl\nexport interface KeeperClient {\n executeLimitOrder(\n jobId: string,\n owner: string,\n params: LimitOrderJob['params']\n ): Promise<{ txHash: string; amountOut?: string | null }>;\n\n executeDCATick(\n jobId: string,\n owner: string,\n params: DCAJob['params']\n ): Promise<{\n txHash: string;\n amountOut?: string | null;\n nextExecutionSec: number;\n }>;\n\n /** Read the terminal status of a job from the contract. */\n getOnChainStatus(\n jobId: `0x${string}`\n ): Promise<'active' | 'executed' | 'cancelled' | 'expired'>;\n}\n\n// Persistent job store – fulfilled by the DB-backed store in the application layer\nexport interface JobStore {\n getActiveJobs(): Promise<Job[]>;\n markActive(jobId: string): Promise<void>;\n markExecuted(\n jobId: string,\n txHash: string,\n amountOut?: string | null\n ): Promise<void>;\n /**\n * Record one completed DCA tick.\n * Updates swapsCompleted + nextExecution in params_json and inserts an\n * execution record. Sets status → 'executed' when all swaps are done,\n * otherwise keeps the job 'active' so subsequent ticks can run.\n */\n markDCATick(\n jobId: string,\n txHash: string,\n newSwapsCompleted: number,\n nextExecution: number,\n amountOut?: string | null\n ): Promise<void>;\n markFailed(jobId: string, error: string): Promise<void>;\n incrementRetry(jobId: string): Promise<void>;\n markExpired(jobId: string): Promise<void>;\n /** Mark a job cancelled — use when on-chain status is CANCELLED. */\n markCancelled(jobId: string): Promise<void>;\n /** Record the latest error message without changing status or incrementing retries. */\n updateLastError(jobId: string, error: string): Promise<void>;\n}\n\nexport interface ExecutorOptions {\n dryRun?: boolean;\n}\n\n/**\n * Executor – evaluates active jobs and submits on-chain transactions when\n * conditions are met and the SafetyGuard approves.\n */\nexport class Executor {\n private priceChecker: PriceChecker;\n private safetyGuard: SafetyGuard;\n private retryQueue: RetryQueue;\n private keeperClient: KeeperClient;\n private jobStore: JobStore;\n private dryRun: boolean;\n\n constructor(\n priceChecker: PriceChecker,\n safetyGuard: SafetyGuard,\n retryQueue: RetryQueue,\n keeperClient: KeeperClient,\n jobStore: JobStore,\n options: ExecutorOptions = {}\n ) {\n this.priceChecker = priceChecker;\n this.safetyGuard = safetyGuard;\n this.retryQueue = retryQueue;\n this.keeperClient = keeperClient;\n this.jobStore = jobStore;\n this.dryRun = options.dryRun ?? false;\n }\n\n /**\n * Process a single job tick.\n */\n async processTick(job: Job): Promise<void> {\n // 1. Quick expiry guard\n if (job.expiresAt !== null && Date.now() >= job.expiresAt) {\n logger.info(`[Executor] job ${job.id} expired`);\n await this.jobStore.markExpired(job.id);\n return;\n }\n\n try {\n // Transition pending → active on first pickup\n if (job.status === 'pending') {\n await this.jobStore.markActive(job.id);\n }\n\n if (job.type === 'limit_order') {\n await this._processLimitOrder(job as LimitOrderJob);\n } else if (job.type === 'dca') {\n await this._processDCA(job as DCAJob);\n }\n // 'twap' and 'swap' are not yet implemented — silently skip\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n\n // ── Transient errors — handle silently without logging as ERROR ──────────\n\n // PriceConditionNotMet is a transient race: the off-chain check passed but\n // the on-chain oracle ticked back before the tx was mined.\n if (message.includes('PriceConditionNotMet')) {\n logger.debug(\n `[Executor] job ${job.id}: price condition no longer met at execution time — will retry next tick`\n );\n return;\n }\n\n // DCAIntervalNotReached means the on-chain interval timer hasn't elapsed yet.\n if (message.includes('DCAIntervalNotReached')) {\n logger.debug(\n `[Executor] job ${job.id}: DCA interval not yet reached at execution time — will retry next tick`\n );\n return;\n }\n\n // Receipt-not-found: the node didn't have the receipt indexed yet.\n if (\n message.includes('could not be found') ||\n message.includes('TransactionReceiptNotFoundError')\n ) {\n logger.debug(\n `[Executor] job ${job.id}: receipt not yet indexed — will retry next tick`\n );\n return;\n }\n\n // Slippage exceeded is transient: pool price moved between simulation and mine.\n if (message.includes('Slippage exceeded')) {\n logger.debug(\n `[Executor] job ${job.id}: slippage exceeded at execution time — will retry next tick`\n );\n await this.jobStore.incrementRetry(job.id);\n await this.jobStore.updateLastError(job.id, message);\n return;\n }\n\n // ── Unexpected errors — log full cause for diagnosis ─────────────────────\n if (err instanceof Error && (err as { cause?: unknown }).cause) {\n logger.error(\n { cause: (err as { cause?: unknown }).cause },\n '[Executor] raw error cause'\n );\n }\n\n // JobNotFound: on-chain job ID doesn't exist (contract redeployed).\n if (message.includes('JobNotFound')) {\n const ocId = (job as { onChainJobId?: string }).onChainJobId;\n logger.warn(\n { jobId: job.id, onChainJobId: ocId },\n '[Executor] JobNotFound — on_chain_job_id not found on current contract ' +\n '(likely left over from an old deployment) — marking cancelled'\n );\n await this.jobStore.markCancelled(job.id);\n return;\n }\n\n // JobNotActive: job is EXECUTED/CANCELLED/EXPIRED on-chain but DB is out of sync.\n if (message.includes('JobNotActive')) {\n const ocId = (job as { onChainJobId?: string }).onChainJobId as\n | `0x${string}`\n | undefined;\n let onChainStatus: 'active' | 'executed' | 'cancelled' | 'expired' =\n 'cancelled';\n if (ocId) {\n try {\n onChainStatus = await this.keeperClient.getOnChainStatus(ocId);\n } catch (statusErr) {\n logger.warn(\n { jobId: job.id, err: statusErr },\n '[Executor] could not read on-chain status — defaulting to cancelled'\n );\n }\n }\n if (onChainStatus === 'executed') {\n logger.warn(\n { jobId: job.id, onChainJobId: ocId },\n '[Executor] job EXECUTED on-chain but DB was out of sync — marking executed'\n );\n await this.jobStore.markExecuted(job.id, 'chain-sync');\n } else {\n logger.warn(\n { jobId: job.id, onChainJobId: ocId, onChainStatus },\n '[Executor] job is CANCELLED/EXPIRED on-chain — marking cancelled in DB'\n );\n await this.jobStore.markCancelled(job.id);\n }\n return;\n }\n\n logger.error(`[Executor] job ${job.id} failed: ${message}`);\n const nextRetries = job.retries + 1;\n if (job.retries < job.maxRetries) {\n await this.jobStore.incrementRetry(job.id);\n }\n if (nextRetries < job.maxRetries) {\n this.retryQueue.enqueue({ ...job, retries: nextRetries });\n }\n await this.jobStore.markFailed(job.id, message);\n }\n }\n\n /**\n * Process all active jobs + due retries in one tick.\n */\n async runAllTicks(): Promise<void> {\n const activeJobs = await this.jobStore.getActiveJobs();\n const retries = this.retryQueue.drainDue();\n\n const all = [...activeJobs, ...retries];\n logger.info(\n `[Executor] tick – ${activeJobs.length} active, ${retries.length} retries`\n );\n\n await Promise.allSettled(all.map((job) => this.processTick(job)));\n }\n\n // ─── Private ────────────────────────────────────────────────────────────────\n\n private async _processLimitOrder(job: LimitOrderJob): Promise<void> {\n const priceResult = await this.priceChecker.checkLimitOrder(job);\n\n if (!priceResult.conditionMet) {\n logger.info(\n {\n jobId: job.id,\n currentPrice: priceResult.currentPrice.toString(),\n targetPrice: priceResult.targetPrice.toString(),\n direction: job.params.direction,\n },\n `[Executor] limit-order ${job.id}: condition not met – waiting`\n );\n return;\n }\n\n const safetyResult = this.safetyGuard.check(job, {\n swapUsd: priceResult.swapUsd,\n });\n if (!safetyResult.ok) {\n logger.warn(\n { violation: safetyResult.violation },\n `[Executor] limit-order ${job.id} blocked by safety guard`\n );\n return;\n }\n\n if (this.dryRun) {\n logger.info(`[Executor][dryRun] would execute limit-order ${job.id}`);\n return;\n }\n\n logger.info(`[Executor] executing limit-order ${job.id}`);\n const onChainJobId = job.onChainJobId;\n if (!onChainJobId) {\n logger.warn(\n `[Executor] limit-order ${job.id} has no onChainJobId – skipping until registered on-chain`\n );\n return;\n }\n const { txHash, amountOut } = await this.keeperClient.executeLimitOrder(\n onChainJobId,\n job.owner,\n job.params\n );\n await this.jobStore.markExecuted(job.id, txHash, amountOut);\n logger.info(`[Executor] limit-order ${job.id} executed – tx ${txHash}`);\n }\n\n private async _processDCA(job: DCAJob): Promise<void> {\n const priceResult = await this.priceChecker.checkDCA(job);\n\n if (!priceResult.conditionMet) {\n logger.info(\n {\n jobId: job.id,\n nextExecution: job.params.nextExecution,\n now: Date.now(),\n secsRemaining: Math.round(\n (job.params.nextExecution - Date.now()) / 1000\n ),\n },\n `[Executor] DCA ${job.id}: interval not reached`\n );\n return;\n }\n\n const safetyResult = this.safetyGuard.check(job, {\n swapUsd: priceResult.swapUsd,\n });\n if (!safetyResult.ok) {\n logger.warn(\n { violation: safetyResult.violation },\n `[Executor] DCA ${job.id} blocked by safety guard`\n );\n return;\n }\n\n if (this.dryRun) {\n logger.info(`[Executor][dryRun] would execute DCA tick for ${job.id}`);\n return;\n }\n\n logger.info(`[Executor] executing DCA tick ${job.id}`);\n const onChainJobId = job.onChainJobId;\n if (!onChainJobId) {\n logger.warn(\n `[Executor] DCA job ${job.id} has no onChainJobId – skipping until registered on-chain`\n );\n return;\n }\n const { txHash, amountOut, nextExecutionSec } =\n await this.keeperClient.executeDCATick(\n onChainJobId,\n job.owner,\n job.params\n );\n\n const newSwapsCompleted = job.params.swapsCompleted + 1;\n // Use the on-chain nextExecution (returned by KeeperClient after reading\n // the contract state post-tick) rather than computing it locally.\n const nextExecutionMs = nextExecutionSec * 1000;\n await this.jobStore.markDCATick(\n job.id,\n txHash,\n newSwapsCompleted,\n nextExecutionMs,\n amountOut\n );\n\n logger.info(\n {\n jobId: job.id,\n txHash,\n swapsCompleted: newSwapsCompleted,\n total: job.params.totalSwaps,\n },\n `[Executor] DCA tick executed – ${newSwapsCompleted}/${job.params.totalSwaps}`\n );\n }\n}\n","/**\n * KeeperClient — viem implementation of the KeeperClient interface.\n *\n * Wraps AutomationManager contract calls for executeLimitOrder and executeDCATick.\n * The executor wallet is a keeper (not a custodian) — it cannot move user funds\n * unless the user has set an on-chain allowance for that specific swap.\n */\n\nimport { AUTOMATION_MANAGER_ABI } from '@cfxdevkit/contracts';\nimport { logger } from '@cfxdevkit/core';\nimport {\n type Address,\n createPublicClient,\n createWalletClient,\n type Hash,\n http,\n} from 'viem';\nimport { privateKeyToAccount } from 'viem/accounts';\nimport type { KeeperClient as IKeeperClient } from './executor.js';\nimport type { DCAJob, LimitOrderJob } from './types.js';\n\n// --------------------------------------------------------------------------\n// Helpers\n// --------------------------------------------------------------------------\n\n/**\n * Decode the amountOut from a swap receipt by finding the last ERC-20\n * Transfer event whose `to` address is the job owner (the swap recipient).\n * Returns the raw amount as a decimal string, or null if not found.\n */\nfunction decodeAmountOut(\n logs: readonly { topics: readonly string[]; data: string; address: string }[],\n owner: Address\n): string | null {\n const TRANSFER_TOPIC =\n '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';\n const ownerLower = owner.toLowerCase();\n // Walk logs in reverse — the last Transfer to owner is tokenOut\n for (let i = logs.length - 1; i >= 0; i--) {\n const log = logs[i];\n if (\n log.topics[0]?.toLowerCase() === TRANSFER_TOPIC &&\n log.topics[2] &&\n `0x${log.topics[2].slice(26)}`.toLowerCase() === ownerLower\n ) {\n try {\n return BigInt(log.data).toString();\n } catch {\n return null;\n }\n }\n }\n return null;\n}\n\n// --------------------------------------------------------------------------\n// Config\n// --------------------------------------------------------------------------\nexport interface KeeperClientConfig {\n /** RPC endpoint for Conflux eSpace */\n rpcUrl: string;\n /** Hex private key (0x-prefixed) of the keeper wallet */\n privateKey: `0x${string}`;\n /** Deployed AutomationManager contract address */\n contractAddress: Address;\n /**\n * Swappi router address.\n * Testnet: 0x873789aaf553fd0b4252d0d2b72c6331c47aff2e\n * Mainnet: 0xE37B52296b0bAA91412cD0Cd97975B0805037B84 (Swappi v2 — only address with deployed code;\n * old 0x62B0873... has no bytecode)\n */\n swappiRouter: Address;\n /**\n * Maximum gas price in gwei before aborting execution (circuit breaker).\n * Defaults to 1000 gwei.\n */\n maxGasPriceGwei?: bigint;\n /** Chain definition — pass the viem chain object (espaceTestnet / espaceMainnet) */\n chain: Parameters<typeof createWalletClient>[0]['chain'];\n /** RPC request timeout in milliseconds (applies to read/simulate/write calls). */\n rpcTimeoutMs?: number;\n}\n\n// --------------------------------------------------------------------------\n// KeeperClientImpl\n// --------------------------------------------------------------------------\nexport class KeeperClientImpl implements IKeeperClient {\n private readonly walletClient;\n private readonly publicClient;\n private readonly contractAddress: Address;\n private readonly swappiRouter: Address;\n private readonly maxGasPriceGwei: bigint;\n private readonly rpcTimeoutMs: number;\n private readonly account;\n\n constructor(config: KeeperClientConfig) {\n this.account = privateKeyToAccount(config.privateKey);\n this.contractAddress = config.contractAddress;\n this.swappiRouter = config.swappiRouter;\n this.maxGasPriceGwei = config.maxGasPriceGwei ?? 1000n;\n this.rpcTimeoutMs = config.rpcTimeoutMs ?? 120_000; // default 2 minutes\n\n this.publicClient = createPublicClient({\n chain: config.chain,\n transport: http(config.rpcUrl),\n });\n\n this.walletClient = createWalletClient({\n chain: config.chain,\n transport: http(config.rpcUrl),\n account: this.account,\n });\n }\n\n private withTimeout<T>(\n fn: (signal: AbortSignal) => Promise<T>,\n timeoutMs = this.rpcTimeoutMs\n ): Promise<T> {\n const controller = new AbortController();\n const id = setTimeout(() => controller.abort(), timeoutMs);\n return fn(controller.signal).finally(() => clearTimeout(id));\n }\n\n // --------------------------------------------------------------------------\n // Helpers\n // --------------------------------------------------------------------------\n\n /**\n * Query the on-chain status of a job.\n * Returns one of: 'active' | 'executed' | 'cancelled' | 'expired'.\n * Throws if the contract call fails (e.g. job not found).\n */\n async getOnChainStatus(\n onChainJobId: `0x${string}`\n ): Promise<'active' | 'executed' | 'cancelled' | 'expired'> {\n const job = (await this.withTimeout((_signal) =>\n this.publicClient.readContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'getJob',\n args: [onChainJobId],\n })\n )) as { status: number };\n // JobStatus enum: 0=ACTIVE, 1=EXECUTED, 2=CANCELLED, 3=EXPIRED\n switch (job.status) {\n case 0:\n return 'active';\n case 1:\n return 'executed';\n case 2:\n return 'cancelled';\n case 3:\n return 'expired';\n default:\n return 'cancelled'; // unknown → treat as cancelled (stop retrying)\n }\n }\n\n /** Guard: abort if chain is paused or gas price is too high */\n private async preflightCheck(): Promise<void> {\n // Check on-chain pause flag\n const isPaused = await this.withTimeout((_signal) =>\n this.publicClient.readContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'paused',\n })\n );\n if (isPaused) {\n throw new Error('AutomationManager is paused on-chain');\n }\n\n // Check gas price circuit breaker\n const gasPrice = await Promise.race([\n this.publicClient.getGasPrice(),\n new Promise<never>((_, rej) =>\n setTimeout(\n () => rej(new Error('getGasPrice timeout')),\n this.rpcTimeoutMs\n )\n ),\n ]);\n const gasPriceGwei = gasPrice / 1_000_000_000n;\n if (gasPriceGwei > this.maxGasPriceGwei) {\n throw new Error(\n `Gas price ${gasPriceGwei} gwei exceeds cap ${this.maxGasPriceGwei} gwei`\n );\n }\n }\n\n /**\n * Build minimal swap calldata for a token-in/token-out pair via Swappi.\n *\n * In a production keeper the calldata would be built via a DEX aggregator\n * (e.g. OKX DEX API) to get optimal routing. For the MVP we encode the\n * simplest Swappi path: `swapExactTokensForTokens(amountIn, minOut, path, to, deadline)`.\n *\n * NOTE: The AutomationManager does NOT use this calldata for custody;\n * it only uses it to call the router on behalf of the user's pre-approved allowance.\n */\n private buildSwapCalldata(\n tokenIn: Address,\n tokenOut: Address,\n amountIn: bigint,\n minAmountOut: bigint,\n recipient: Address\n ): `0x${string}` {\n // swapExactTokensForTokens(uint256,uint256,address[],address,uint256)\n // selector = 0x38ed1739\n const selector = '38ed1739';\n const deadline = BigInt(Math.floor(Date.now() / 1000) + 1800); // 30 min\n const encode256 = (n: bigint) => n.toString(16).padStart(64, '0');\n const encodeAddr = (a: string) =>\n a.slice(2).toLowerCase().padStart(64, '0');\n\n // path is dynamic array: offset + length + [tokenIn, tokenOut]\n const pathOffset = (5 * 32).toString(16).padStart(64, '0'); // 5 static params before dynamic\n const pathLength = encode256(2n);\n const pathData = encodeAddr(tokenIn) + encodeAddr(tokenOut);\n\n const calldata =\n '0x' +\n selector +\n encode256(amountIn) +\n encode256(minAmountOut) +\n pathOffset +\n encodeAddr(recipient) +\n encode256(deadline) +\n pathLength +\n pathData;\n\n return calldata as `0x${string}`;\n }\n\n // --------------------------------------------------------------------------\n // IKeeperClient interface implementation\n // --------------------------------------------------------------------------\n\n async executeLimitOrder(\n jobId: string,\n owner: string,\n params: LimitOrderJob['params']\n ): Promise<{ txHash: string; amountOut: string | null }> {\n await this.preflightCheck();\n\n const amountIn = BigInt(params.amountIn);\n // Pass 0 for the router-level minAmountOut so the router never reverts due\n // to slippage — the AutomationManager contract enforces params.minAmountOut\n // via a post-swap balanceOf check, which is the canonical guard.\n const minAmountOut = 0n;\n\n const swapCalldata = this.buildSwapCalldata(\n params.tokenIn as Address,\n params.tokenOut as Address,\n amountIn,\n minAmountOut,\n owner as Address\n );\n\n logger.info(\n {\n jobId,\n tokenIn: params.tokenIn,\n tokenOut: params.tokenOut,\n amountIn: params.amountIn,\n },\n '[KeeperClient] executeLimitOrder'\n );\n\n // Simulate first — throws with a decoded revert reason if it would fail.\n try {\n await this.withTimeout((_signal) =>\n this.publicClient.simulateContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeLimitOrder',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n account: this.account.address,\n })\n );\n } catch (simErr: unknown) {\n const msg = simErr instanceof Error ? simErr.message : String(simErr);\n logger.error(\n { jobId, error: msg.slice(0, 500) },\n '[KeeperClient] executeLimitOrder simulation reverted'\n );\n throw simErr;\n }\n\n const hash: Hash = await this.withTimeout((_signal) =>\n this.walletClient.writeContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeLimitOrder',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n chain: undefined,\n account: this.account,\n })\n );\n\n logger.info({ jobId, hash }, '[KeeperClient] limitOrder tx submitted');\n\n const receipt = await Promise.race([\n this.publicClient.waitForTransactionReceipt({ hash } as Parameters<\n typeof this.publicClient.waitForTransactionReceipt\n >[0]),\n new Promise<never>((_, rej) =>\n setTimeout(\n () => rej(new Error('waitForTransactionReceipt timeout')),\n this.rpcTimeoutMs\n )\n ),\n ]);\n if ((receipt as { status: string }).status !== 'success') {\n let reason: string = (hash as string) ?? 'unknown';\n try {\n await this.withTimeout((_signal) =>\n this.publicClient.simulateContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeLimitOrder',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n account: this.account.address,\n })\n );\n } catch (simErr: unknown) {\n if (simErr instanceof Error) reason = simErr.message;\n }\n throw new Error(`Limit order tx reverted: ${reason}`);\n }\n\n const amountOut = decodeAmountOut(receipt.logs, owner as Address);\n logger.info(\n { jobId, hash, blockNumber: receipt.blockNumber.toString(), amountOut },\n '[KeeperClient] limitOrder confirmed'\n );\n return { txHash: hash, amountOut };\n }\n\n async executeDCATick(\n jobId: string,\n owner: string,\n params: DCAJob['params']\n ): Promise<{\n txHash: string;\n amountOut: string | null;\n nextExecutionSec: number;\n }> {\n await this.preflightCheck();\n\n // On-chain preflight: verify the DCA interval has actually been reached\n const onChainParams = (await this.publicClient.readContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'getDCAJob',\n args: [jobId as `0x${string}`],\n })) as {\n intervalSeconds: bigint;\n nextExecution: bigint;\n swapsCompleted: bigint;\n totalSwaps: bigint;\n };\n const nowSec = BigInt(Math.floor(Date.now() / 1000));\n if (nowSec < onChainParams.nextExecution) {\n throw new Error(\n `DCAIntervalNotReached(${onChainParams.nextExecution}) — on-chain interval not reached yet ` +\n `(now=${nowSec}, next=${onChainParams.nextExecution}, ` +\n `remaining=${onChainParams.nextExecution - nowSec}s)`\n );\n }\n\n const amountPerTick = BigInt(params.amountPerSwap);\n const minAmountOut = 0n;\n\n const swapCalldata = this.buildSwapCalldata(\n params.tokenIn as Address,\n params.tokenOut as Address,\n amountPerTick,\n minAmountOut,\n owner as Address\n );\n\n logger.info(\n {\n jobId,\n tokenIn: params.tokenIn,\n tokenOut: params.tokenOut,\n amountPerTick: params.amountPerSwap,\n },\n '[KeeperClient] executeDCATick'\n );\n\n try {\n await this.withTimeout((_signal) =>\n this.publicClient.simulateContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeDCATick',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n account: this.account.address,\n })\n );\n } catch (simErr: unknown) {\n const msg = simErr instanceof Error ? simErr.message : String(simErr);\n logger.error(\n { jobId, error: msg.slice(0, 500) },\n '[KeeperClient] executeDCATick simulation reverted'\n );\n throw simErr;\n }\n\n const hash: Hash = await this.withTimeout((_signal) =>\n this.walletClient.writeContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeDCATick',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n chain: undefined,\n account: this.account,\n })\n );\n\n logger.info({ jobId, hash }, '[KeeperClient] dca tx submitted');\n\n const receipt = await Promise.race([\n this.publicClient.waitForTransactionReceipt({ hash } as Parameters<\n typeof this.publicClient.waitForTransactionReceipt\n >[0]),\n new Promise<never>((_, rej) =>\n setTimeout(\n () => rej(new Error('waitForTransactionReceipt timeout')),\n this.rpcTimeoutMs\n )\n ),\n ]);\n if ((receipt as { status: string }).status !== 'success') {\n let reason: string = (hash as string) ?? 'unknown';\n try {\n await this.withTimeout((_signal) =>\n this.publicClient.simulateContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'executeDCATick',\n args: [jobId as `0x${string}`, this.swappiRouter, swapCalldata],\n account: this.account.address,\n })\n );\n } catch (simErr: unknown) {\n if (simErr instanceof Error) reason = simErr.message;\n }\n throw new Error(`DCA tick tx reverted: ${reason}`);\n }\n\n const amountOut = decodeAmountOut(receipt.logs, owner as Address);\n // Re-read the contract's nextExecution after the tick so the DB is exactly\n // in sync with what the contract stored.\n const postTickParams = (await this.publicClient.readContract({\n address: this.contractAddress,\n abi: AUTOMATION_MANAGER_ABI,\n functionName: 'getDCAJob',\n args: [jobId as `0x${string}`],\n })) as {\n intervalSeconds: bigint;\n nextExecution: bigint;\n swapsCompleted: bigint;\n totalSwaps: bigint;\n };\n const nextExecutionSec = Number(postTickParams.nextExecution);\n logger.info(\n {\n jobId,\n hash,\n blockNumber: receipt.blockNumber.toString(),\n amountOut,\n nextExecutionSec,\n },\n '[KeeperClient] dca confirmed'\n );\n return { txHash: hash, amountOut, nextExecutionSec };\n }\n}\n","/**\n * Minimal injectable logger interface for the automation module.\n *\n * The SDK ships with NO logging dependency. Consumers can pass any compatible\n * logger (pino, winston, console wrapper …) or omit it to get silent behaviour.\n */\nexport interface AutomationLogger {\n info(msg: string | object, ...args: unknown[]): void;\n warn(msg: string | object, ...args: unknown[]): void;\n debug(msg: string | object, ...args: unknown[]): void;\n error(msg: string | object, ...args: unknown[]): void;\n}\n\n/** Default no-op logger — used when no logger is supplied. */\nexport const noopLogger: AutomationLogger = {\n info: () => {},\n warn: () => {},\n debug: () => {},\n error: () => {},\n};\n","import type { AutomationLogger } from './logger.js';\nimport { noopLogger } from './logger.js';\nimport type { Job } from './types.js';\n\n/**\n * Price source adapter interface.\n *\n * Returns the spot price of `tokenIn` denominated in `tokenOut`, scaled by\n * 1e18. Implementations may call a DEX (Swappi, etc.), an oracle, or a mock.\n */\nexport interface PriceSource {\n /**\n * Returns 0n if the pair is unknown or price cannot be fetched.\n */\n getPrice(tokenIn: string, tokenOut: string): Promise<bigint>;\n}\n\nexport interface PriceCheckResult {\n conditionMet: boolean;\n currentPrice: bigint;\n targetPrice: bigint;\n swapUsd: number;\n}\n\n/**\n * Callback that resolves a token address to its ERC-20 decimal count.\n * Implementations should cache the result for performance.\n */\nexport type DecimalsResolver = (token: string) => Promise<number>;\n\n/** Default resolver — assumes 18 decimals for every token. */\nconst defaultDecimalsResolver: DecimalsResolver = async () => 18;\n\n/**\n * PriceChecker – queries a price source and evaluates whether a job's\n * trigger condition is currently met.\n */\nexport class PriceChecker {\n private source: PriceSource;\n private tokenPricesUsd: Map<string, number>;\n /**\n * Resolves a token's decimal count. Called lazily when _estimateUsd needs\n * to convert raw wei amounts into human-readable values. The resolver may\n * query the chain, a static map, or simply return 18.\n */\n private getDecimals: DecimalsResolver;\n private readonly log: AutomationLogger;\n\n constructor(\n source: PriceSource,\n tokenPricesUsd: Map<string, number> = new Map(),\n logger: AutomationLogger = noopLogger,\n getDecimals: DecimalsResolver = defaultDecimalsResolver\n ) {\n this.source = source;\n this.tokenPricesUsd = tokenPricesUsd;\n this.getDecimals = getDecimals;\n this.log = logger;\n }\n\n async checkLimitOrder(\n job: Job & { type: 'limit_order' }\n ): Promise<PriceCheckResult> {\n const params = job.params;\n const currentPrice = await this.source.getPrice(\n params.tokenIn,\n params.tokenOut\n );\n const targetPrice = BigInt(params.targetPrice);\n\n let conditionMet: boolean;\n if (params.direction === 'gte') {\n conditionMet = currentPrice >= targetPrice;\n } else {\n conditionMet = currentPrice <= targetPrice;\n }\n\n const swapUsd = await this._estimateUsd(params.tokenIn, params.amountIn);\n\n this.log.debug(\n {\n jobId: job.id,\n currentPrice: currentPrice.toString(),\n targetPrice: targetPrice.toString(),\n conditionMet,\n swapUsd,\n },\n '[PriceChecker] limit-order check'\n );\n\n return { conditionMet, currentPrice, targetPrice, swapUsd };\n }\n\n async checkDCA(job: Job & { type: 'dca' }): Promise<PriceCheckResult> {\n const params = job.params;\n // DCA has no price condition — just verify the interval has been reached.\n // We add a 15-second buffer before declaring conditionMet so that by the\n // time the transaction is mined the on-chain block.timestamp is also\n // reliably past nextExecution, avoiding DCAIntervalNotReached reverts at\n // the execution boundary.\n const DCA_EXECUTION_BUFFER_MS = 15_000;\n const conditionMet =\n Date.now() >= params.nextExecution + DCA_EXECUTION_BUFFER_MS;\n const currentPrice = await this.source.getPrice(\n params.tokenIn,\n params.tokenOut\n );\n const swapUsd = await this._estimateUsd(\n params.tokenIn,\n params.amountPerSwap\n );\n\n this.log.debug(\n {\n jobId: job.id,\n nextExecution: params.nextExecution,\n conditionMet,\n swapUsd,\n },\n '[PriceChecker] DCA check'\n );\n\n return { conditionMet, currentPrice, targetPrice: 0n, swapUsd };\n }\n\n updateTokenPrice(token: string, usdPrice: number): void {\n this.tokenPricesUsd.set(token.toLowerCase(), usdPrice);\n }\n\n private async _estimateUsd(\n token: string,\n amountWei: string\n ): Promise<number> {\n const usdPerToken = this.tokenPricesUsd.get(token.toLowerCase()) ?? 0;\n // Resolve the token's actual decimal count (on-chain or cached).\n const decimals = await this.getDecimals(token);\n const divisor = 10 ** decimals;\n const amount = Number(BigInt(amountWei)) / divisor;\n return amount * usdPerToken;\n }\n}\n","import type { AutomationLogger } from './logger.js';\nimport { noopLogger } from './logger.js';\nimport type { Job } from './types.js';\n\n/**\n * RetryQueue – wraps jobs for retry-with-exponential-backoff scheduling.\n *\n * Backoff formula:\n * delay = min(base × 2^attempt, maxDelay) × (1 + jitter × rand)\n */\nexport class RetryQueue {\n private readonly baseDelayMs: number;\n private readonly maxDelayMs: number;\n private readonly jitter: number;\n private readonly log: AutomationLogger;\n private queue: Map<\n string,\n { job: Job; nextRetryAt: number; attempt: number }\n > = new Map();\n\n constructor(\n options?: {\n baseDelayMs?: number;\n maxDelayMs?: number;\n jitter?: number;\n },\n logger: AutomationLogger = noopLogger\n ) {\n this.baseDelayMs = options?.baseDelayMs ?? 5_000; // 5 s\n this.maxDelayMs = options?.maxDelayMs ?? 300_000; // 5 min cap\n this.jitter = options?.jitter ?? 0.2; // 20 % jitter\n this.log = logger;\n }\n\n /** Enqueue a job for retry after a calculated backoff delay. */\n enqueue(job: Job): void {\n const existing = this.queue.get(job.id);\n const attempt = existing ? existing.attempt + 1 : 0;\n const delay = this._backoffDelay(attempt);\n const nextRetryAt = Date.now() + delay;\n\n this.queue.set(job.id, { job, nextRetryAt, attempt });\n this.log.info(\n `[RetryQueue] job ${job.id} enqueued, attempt=${attempt}, delay=${delay}ms`\n );\n }\n\n /** Remove a job from the queue (e.g. after success or manual cancel). */\n remove(jobId: string): void {\n this.queue.delete(jobId);\n }\n\n /** Return all jobs whose retry time has arrived; removes them from the queue. */\n drainDue(now = Date.now()): Job[] {\n const due: Job[] = [];\n for (const [jobId, entry] of this.queue) {\n if (now >= entry.nextRetryAt) {\n due.push(entry.job);\n this.queue.delete(jobId);\n }\n }\n return due;\n }\n\n size(): number {\n return this.queue.size;\n }\n\n // ─── Private ──────────────────────────────────────────────────────\n\n private _backoffDelay(attempt: number): number {\n const exponential = this.baseDelayMs * 2 ** attempt;\n const capped = Math.min(exponential, this.maxDelayMs);\n const withJitter = capped * (1 + this.jitter * Math.random());\n return Math.floor(withJitter);\n }\n}\n","import type { AutomationLogger } from './logger.js';\nimport { noopLogger } from './logger.js';\nimport type {\n Job,\n SafetyCheckResult,\n SafetyConfig,\n SafetyViolation,\n} from './types.js';\n\n/** Default on-chain safety limits. */\nexport const DEFAULT_SAFETY_CONFIG: SafetyConfig = {\n maxSwapUsd: 10_000,\n maxSlippageBps: 500,\n maxRetries: 5,\n minExecutionIntervalSeconds: 30,\n globalPause: false,\n};\n\n/**\n * SafetyGuard – off-chain complement to AutomationManager's on-chain checks.\n *\n * Responsibilities:\n * 1. Validate a job satisfies configured safety bounds before the keeper\n * submits a transaction.\n * 2. Provide a global pause switch (circuit-breaker).\n * 3. Log every violation for audit purposes.\n *\n * The logger is injected rather than imported — the SDK ships with no logging\n * dependency. Pass your pino/winston/console instance, or omit for silence.\n */\nexport class SafetyGuard {\n private config: SafetyConfig;\n private violations: SafetyViolation[] = [];\n private readonly log: AutomationLogger;\n\n constructor(\n config: Partial<SafetyConfig> = {},\n logger: AutomationLogger = noopLogger\n ) {\n this.config = { ...DEFAULT_SAFETY_CONFIG, ...config };\n this.log = logger;\n this.log.info(\n {\n maxSwapUsd: this.config.maxSwapUsd,\n maxSlippageBps: this.config.maxSlippageBps,\n maxRetries: this.config.maxRetries,\n globalPause: this.config.globalPause,\n },\n '[SafetyGuard] initialized'\n );\n }\n\n // ─── Core check ───────────────────────────────────────────────────\n\n /**\n * Run all configured safety checks against a job.\n * Returns `{ ok: true }` if all pass, or `{ ok: false, violation }` on first failure.\n */\n check(\n job: Job,\n context: { swapUsd: number; currentTime?: number }\n ): SafetyCheckResult {\n const now = context.currentTime ?? Date.now();\n\n // Global pause (circuit-breaker)\n if (this.config.globalPause) {\n return this._fail(\n job.id,\n 'globalPause',\n 'Global pause is active — all execution halted'\n );\n }\n\n // Job status guard — only pending and active are executable\n if (job.status !== 'active' && job.status !== 'pending') {\n return this._fail(\n job.id,\n 'globalPause',\n `Job ${job.id} cannot be executed (status: ${job.status})`\n );\n }\n\n // Retry cap\n if (job.retries >= job.maxRetries) {\n return this._fail(\n job.id,\n 'maxRetries',\n `Job ${job.id} has exhausted retries (${job.retries}/${job.maxRetries})`\n );\n }\n\n // Swap USD cap\n if (context.swapUsd > this.config.maxSwapUsd) {\n return this._fail(\n job.id,\n 'maxSwapUsd',\n `Swap value $${context.swapUsd.toFixed(2)} exceeds limit $${this.config.maxSwapUsd}`\n );\n }\n\n // Job expiry\n if (job.expiresAt !== null && now >= job.expiresAt) {\n return this._fail(job.id, 'globalPause', `Job ${job.id} has expired`);\n }\n\n // DCA interval check\n if (job.type === 'dca') {\n const dcaParams = job.params;\n if (now < dcaParams.nextExecution) {\n const waitSec = Math.ceil((dcaParams.nextExecution - now) / 1000);\n return this._fail(\n job.id,\n 'minExecutionIntervalSeconds',\n `DCA interval not yet reached (${waitSec}s remaining)`\n );\n }\n }\n\n return { ok: true };\n }\n\n // ─── Config management ────────────────────────────────────────────\n\n updateConfig(patch: Partial<SafetyConfig>): void {\n this.config = { ...this.config, ...patch };\n this.log.info(patch, '[SafetyGuard] config updated');\n }\n\n getConfig(): Readonly<SafetyConfig> {\n return { ...this.config };\n }\n\n pauseAll(): void {\n this.config.globalPause = true;\n this.log.warn('[SafetyGuard] GLOBAL PAUSE ACTIVATED');\n }\n\n resumeAll(): void {\n this.config.globalPause = false;\n this.log.info('[SafetyGuard] global pause lifted');\n }\n\n isPaused(): boolean {\n return this.config.globalPause;\n }\n\n // ─── Audit log ────────────────────────────────────────────────────\n\n getViolations(): readonly SafetyViolation[] {\n return this.violations;\n }\n\n clearViolations(): void {\n this.violations = [];\n }\n\n // ─── Private ──────────────────────────────────────────────────────\n\n private _fail(\n jobId: string,\n rule: keyof SafetyConfig,\n detail: string\n ): SafetyCheckResult {\n const violation: SafetyViolation = {\n jobId,\n rule,\n detail,\n timestamp: Date.now(),\n };\n this.violations.push(violation);\n this.log.warn({ jobId, detail }, `[SafetyGuard] violation – ${rule}`);\n return { ok: false, violation };\n }\n}\n"],"mappings":"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","names":["logger","logger","logger","logger"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfxdevkit/executor",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Conflux DevKit – on-chain strategy execution engine (limit orders, DCA, and more)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -39,6 +39,7 @@
39
39
  "check:fix": "biome check --write src/"
40
40
  },
41
41
  "dependencies": {
42
+ "@cfxdevkit/contracts": "workspace:*",
42
43
  "@cfxdevkit/core": "workspace:*",
43
44
  "@cfxdevkit/protocol": "workspace:*",
44
45
  "viem": ">=2.0.0"