@developeruche/tx-spammer-sdk 1.0.0 → 1.0.1

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.
@@ -1,3 +1,4 @@
1
+ import { type Hash } from 'viem';
1
2
  import { SpamSequenceConfig } from './types';
2
3
  /**
3
4
  * The central coordinator for the spam sequence.
@@ -37,5 +38,8 @@ export declare class SpamOrchestrator {
37
38
  *
38
39
  * Stops when the duration expires or gas limits are reached.
39
40
  */
40
- start(): Promise<void>;
41
+ start(): Promise<{
42
+ blockNumber: bigint;
43
+ txHash: Hash | null;
44
+ }>;
41
45
  }
@@ -33,7 +33,11 @@ class SpamOrchestrator {
33
33
  constructor(config, rootPrivateKey) {
34
34
  // Validate config
35
35
  this.config = types_1.SpamSequenceConfigSchema.parse(config);
36
- this.chain = { ...chains_1.mainnet, id: this.config.chainId, rpcUrls: { default: { http: [this.config.rpcUrl] } } };
36
+ this.chain = {
37
+ ...chains_1.mainnet,
38
+ id: this.config.chainId,
39
+ rpcUrls: { default: { http: [this.config.rpcUrl] } },
40
+ };
37
41
  this.rootAccount = (0, accounts_1.privateKeyToAccount)(rootPrivateKey);
38
42
  this.rootClient = (0, viem_1.createWalletClient)({
39
43
  account: this.rootAccount,
@@ -60,7 +64,9 @@ class SpamOrchestrator {
60
64
  // Fund workers
61
65
  console.log(`Funding workers with ${fundingAmount} wei each...`);
62
66
  const fundingTxs = [];
63
- const nonce = await this.publicClient.getTransactionCount({ address: this.rootAccount.address });
67
+ const nonce = await this.publicClient.getTransactionCount({
68
+ address: this.rootAccount.address,
69
+ });
64
70
  for (let i = 0; i < this.workers.length; i++) {
65
71
  const tx = await this.rootClient.sendTransaction({
66
72
  to: this.workers[i].address,
@@ -72,14 +78,14 @@ class SpamOrchestrator {
72
78
  }
73
79
  console.log(`Waiting for ${fundingTxs.length} funding transactions to confirm...`);
74
80
  // Ideally we wait for all receipt. For MVP, we wait for Promise.all(waitForTransactionReceipt)
75
- await Promise.all(fundingTxs.map(hash => this.publicClient.waitForTransactionReceipt({ hash })));
76
- console.log("All workers funded. Initializing worker nonces...");
81
+ await Promise.all(fundingTxs.map((hash) => this.publicClient.waitForTransactionReceipt({ hash })));
82
+ console.log('All workers funded. Initializing worker nonces...');
77
83
  // Initialize nonces
78
84
  await Promise.all(this.workers.map(async (worker) => {
79
85
  const n = await this.publicClient.getTransactionCount({ address: worker.address });
80
86
  worker.setNonce(n);
81
87
  }));
82
- console.log("Setup complete.");
88
+ console.log('Setup complete.');
83
89
  }
84
90
  /**
85
91
  * Starts the spam sequence based on the configured strategy.
@@ -91,10 +97,13 @@ class SpamOrchestrator {
91
97
  * Stops when the duration expires or gas limits are reached.
92
98
  */
93
99
  async start() {
94
- console.log("Starting spam sequence...");
100
+ console.log('Starting spam sequence...');
95
101
  const strategy = this.config.strategy;
96
- const duration = this.config.durationSeconds ? this.config.durationSeconds * 1000 : Infinity;
102
+ const duration = this.config.durationSeconds
103
+ ? this.config.durationSeconds * 1000
104
+ : Infinity;
97
105
  const startTime = Date.now();
106
+ let lastTxHash = null;
98
107
  // Helper to run a strategy loop for a subset of workers
99
108
  const runLoop = async (workers, stratConfig, guardian) => {
100
109
  const loopTasks = workers.map(async (worker, index) => {
@@ -104,17 +113,21 @@ class SpamOrchestrator {
104
113
  if (Date.now() - startTime > duration)
105
114
  break;
106
115
  try {
116
+ let txHash;
107
117
  if (stratConfig.mode === 'transfer') {
108
- await (0, strategies_1.executeEthTransfer)(worker, stratConfig, guardian, this.publicClient);
118
+ txHash = await (0, strategies_1.executeEthTransfer)(worker, stratConfig, guardian, this.publicClient);
109
119
  }
110
120
  else if (stratConfig.mode === 'deploy') {
111
- await (0, strategies_1.executeContractDeploy)(worker, stratConfig, guardian, this.publicClient);
121
+ txHash = await (0, strategies_1.executeContractDeploy)(worker, stratConfig, guardian, this.publicClient);
112
122
  }
113
123
  else if (stratConfig.mode === 'read') {
114
124
  await (0, strategies_1.executeContractRead)(worker, stratConfig, guardian, this.publicClient);
115
125
  }
116
126
  else if (stratConfig.mode === 'write') {
117
- await (0, strategies_1.executeContractWrite)(worker, stratConfig, guardian, this.publicClient);
127
+ txHash = await (0, strategies_1.executeContractWrite)(worker, stratConfig, guardian, this.publicClient);
128
+ }
129
+ if (txHash) {
130
+ lastTxHash = txHash;
118
131
  }
119
132
  }
120
133
  catch (e) {
@@ -130,7 +143,7 @@ class SpamOrchestrator {
130
143
  await Promise.all(loopTasks);
131
144
  };
132
145
  if (strategy.mode === 'mixed') {
133
- console.log("Executing Mixed Strategy...");
146
+ console.log('Executing Mixed Strategy...');
134
147
  let workerOffset = 0;
135
148
  const tasks = [];
136
149
  // Sort strategies to handle remainder logic deterministically? Or just iterate.
@@ -150,7 +163,7 @@ class SpamOrchestrator {
150
163
  const assignedWorkers = this.workers.slice(workerOffset, workerOffset + count);
151
164
  workerOffset += count;
152
165
  // Gas Limit
153
- // For 'read', gas limit is effectively infinity/ignored by strategy logic,
166
+ // For 'read', gas limit is effectively infinity/ignored by strategy logic,
154
167
  // but we can assign a portion of the max limit to its guardian just in case.
155
168
  const gasLimitShare = BigInt(Math.floor(Number(this.config.maxGasLimit) * sharePercent));
156
169
  const subGuardian = new GasGuardian_1.GasGuardian(gasLimitShare);
@@ -163,7 +176,16 @@ class SpamOrchestrator {
163
176
  // Single Mode
164
177
  await runLoop(this.workers, strategy, this.gasGuardian);
165
178
  }
166
- console.log("Spam sequence finished.");
179
+ console.log('Spam sequence finished.');
180
+ if (lastTxHash) {
181
+ console.log(`Waiting for last transaction ${lastTxHash} to be mined...`);
182
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash: lastTxHash });
183
+ return { blockNumber: receipt.blockNumber, txHash: lastTxHash };
184
+ }
185
+ else {
186
+ const blockNumber = await this.publicClient.getBlockNumber();
187
+ return { blockNumber, txHash: null };
188
+ }
167
189
  }
168
190
  }
169
191
  exports.SpamOrchestrator = SpamOrchestrator;
package/dist/Worker.js CHANGED
@@ -51,7 +51,7 @@ class Worker {
51
51
  */
52
52
  getAndIncrementNonce() {
53
53
  if (this.nonce === null) {
54
- throw new Error("Nonce not initialized for worker");
54
+ throw new Error('Nonce not initialized for worker');
55
55
  }
56
56
  return this.nonce++;
57
57
  }
@@ -69,7 +69,7 @@ class Worker {
69
69
  value,
70
70
  data,
71
71
  // @ts-ignore - nonce override is valid but viem types might be strict depending on version
72
- nonce: this.getAndIncrementNonce()
72
+ nonce: this.getAndIncrementNonce(),
73
73
  });
74
74
  }
75
75
  }
@@ -1,7 +1,7 @@
1
1
  import { Worker } from '../Worker';
2
2
  import { ContractDeployConfig } from '../types';
3
3
  import { GasGuardian } from '../GasGuardian';
4
- import { type PublicClient } from 'viem';
4
+ import { type PublicClient, type Hash } from 'viem';
5
5
  /**
6
6
  * Executes a contract deployment transaction.
7
7
  * Estimates gas for deployment and enforces limits before broadcasting.
@@ -11,4 +11,4 @@ import { type PublicClient } from 'viem';
11
11
  * @param gasGuardian The gas guardian to track usage.
12
12
  * @param publicClient The viem public client for gas estimation.
13
13
  */
14
- export declare function executeContractDeploy(worker: Worker, config: ContractDeployConfig, gasGuardian: GasGuardian, publicClient: PublicClient): Promise<void>;
14
+ export declare function executeContractDeploy(worker: Worker, config: ContractDeployConfig, gasGuardian: GasGuardian, publicClient: PublicClient): Promise<Hash>;
@@ -19,7 +19,7 @@ async function executeContractDeploy(worker, config, gasGuardian, publicClient)
19
19
  const estimatedGas = await publicClient.estimateGas({
20
20
  account: worker.account,
21
21
  data: bytecode,
22
- // TODO: handle args encoding for estimation if strict?
22
+ // TODO: handle args encoding for estimation if strict?
23
23
  // Typically just data + args encoded is fine, but estimateGas with 'data' works for deploy
24
24
  });
25
25
  gasGuardian.checkLimit(estimatedGas);
@@ -33,6 +33,7 @@ async function executeContractDeploy(worker, config, gasGuardian, publicClient)
33
33
  nonce: worker.getAndIncrementNonce(),
34
34
  });
35
35
  gasGuardian.recordUsage(estimatedGas);
36
+ return hash;
36
37
  }
37
38
  catch (error) {
38
39
  throw error;
@@ -11,13 +11,13 @@ exports.executeContractRead = executeContractRead;
11
11
  * @param publicClient The viem client to perform the call.
12
12
  */
13
13
  async function executeContractRead(worker, config, gasGuardian, publicClient) {
14
- // Reads are "eth_call" and do not consume gas on-chain in the same way,
14
+ // Reads are "eth_call" and do not consume gas on-chain in the same way,
15
15
  // but they do put load on the node. The user prompt says "High-frequency eth_call operations".
16
16
  // The GasGuardian tracks "Gas Cap". Usually gas cap refers to on-chain gas used.
17
17
  // However, if we want to limit the *spam load*, we might count it or not.
18
- // Given the "29M Gas Cap" constraint usually applies to blocks/throughput,
18
+ // Given the "29M Gas Cap" constraint usually applies to blocks/throughput,
19
19
  // and reads don't fill blocks, maybe we don't count them against the Guardian?
20
- // BUT the prompt tracks cumulative gas used.
20
+ // BUT the prompt tracks cumulative gas used.
21
21
  // eth_calls don't use gas on chain. I will NOT count them towards the cap unless specified.
22
22
  try {
23
23
  await publicClient.readContract({
@@ -1,7 +1,7 @@
1
1
  import { Worker } from '../Worker';
2
2
  import { ContractWriteConfig } from '../types';
3
3
  import { GasGuardian } from '../GasGuardian';
4
- import { type PublicClient } from 'viem';
4
+ import { type PublicClient, type Hash } from 'viem';
5
5
  /**
6
6
  * Executes a state-changing contract write transaction.
7
7
  * Estimates gas and enforces limits. Supports dynamic arguments via generator or static args.
@@ -11,4 +11,4 @@ import { type PublicClient } from 'viem';
11
11
  * @param gasGuardian The gas guardian to track usage.
12
12
  * @param publicClient The viem public client for gas estimation.
13
13
  */
14
- export declare function executeContractWrite(worker: Worker, config: ContractWriteConfig, gasGuardian: GasGuardian, publicClient: PublicClient): Promise<void>;
14
+ export declare function executeContractWrite(worker: Worker, config: ContractWriteConfig, gasGuardian: GasGuardian, publicClient: PublicClient): Promise<Hash>;
@@ -22,7 +22,7 @@ async function executeContractWrite(worker, config, gasGuardian, publicClient) {
22
22
  abi: config.abi,
23
23
  functionName: config.functionName,
24
24
  args: args,
25
- account: worker.account
25
+ account: worker.account,
26
26
  });
27
27
  gasGuardian.checkLimit(estimatedGas);
28
28
  const hash = await worker.client.writeContract({
@@ -36,6 +36,7 @@ async function executeContractWrite(worker, config, gasGuardian, publicClient) {
36
36
  nonce: worker.getAndIncrementNonce(),
37
37
  });
38
38
  gasGuardian.recordUsage(estimatedGas);
39
+ return hash;
39
40
  }
40
41
  catch (error) {
41
42
  throw error;
@@ -1,7 +1,7 @@
1
1
  import { Worker } from '../Worker';
2
2
  import { EthTransferConfig } from '../types';
3
3
  import { GasGuardian } from '../GasGuardian';
4
- import { type PublicClient } from 'viem';
4
+ import { type PublicClient, type Hash } from 'viem';
5
5
  /**
6
6
  * Executes a single ETH transfer transaction.
7
7
  *
@@ -10,4 +10,4 @@ import { type PublicClient } from 'viem';
10
10
  * @param gasGuardian The gas guardian to check and record usage.
11
11
  * @param publicClient The viem public client for gas estimation.
12
12
  */
13
- export declare function executeEthTransfer(worker: Worker, config: EthTransferConfig, gasGuardian: GasGuardian, publicClient: PublicClient): Promise<void>;
13
+ export declare function executeEthTransfer(worker: Worker, config: EthTransferConfig, gasGuardian: GasGuardian, publicClient: PublicClient): Promise<Hash>;
@@ -16,16 +16,17 @@ async function executeEthTransfer(worker, config, gasGuardian, publicClient) {
16
16
  const estimatedGas = await publicClient.estimateGas({
17
17
  account: worker.account,
18
18
  to: recipient,
19
- value: amount
19
+ value: amount,
20
20
  });
21
21
  gasGuardian.checkLimit(estimatedGas);
22
22
  const hash = await worker.sendTransaction(recipient, amount);
23
- // We assume the tx will consume 21000 gas for simplicity in tracking,
24
- // though in reality we'd wait for receipt to be exact.
23
+ // We assume the tx will consume 21000 gas for simplicity in tracking,
24
+ // though in reality we'd wait for receipt to be exact.
25
25
  // For high-performance spamming, we pre-check and post-record.
26
26
  gasGuardian.recordUsage(estimatedGas);
27
- // In a real recursive scenario (depth > 1), we would chain additional transfers here
28
- // or wait for this one to mine. For high-volume spam, we often fire-and-forget
27
+ return hash;
28
+ // In a real recursive scenario (depth > 1), we would chain additional transfers here
29
+ // or wait for this one to mine. For high-volume spam, we often fire-and-forget
29
30
  // or batch them if nonces allow.
30
31
  // The requirement mentions A -> B -> C.
31
32
  // If depth is > 1, this specific call might just be one step.
package/dist/types.js CHANGED
@@ -76,13 +76,15 @@ exports.MixedStrategyConfigSchema = zod_1.z.object({
76
76
  * List of strategies with their respective percentage allocation.
77
77
  * Percentages must sum to 100.
78
78
  */
79
- strategies: zod_1.z.array(zod_1.z.object({
79
+ strategies: zod_1.z
80
+ .array(zod_1.z.object({
80
81
  config: SingleStrategySchema,
81
82
  percentage: zod_1.z.number().nonnegative().max(100),
82
- })).refine((items) => {
83
+ }))
84
+ .refine((items) => {
83
85
  const sum = items.reduce((acc, item) => acc + item.percentage, 0);
84
86
  return Math.abs(sum - 100) < 0.1; // Allow small float error
85
- }, { message: "Percentages must sum to 100" }),
87
+ }, { message: 'Percentages must sum to 100' }),
86
88
  });
87
89
  /**
88
90
  * Union schema for all possible spam strategies.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@developeruche/tx-spammer-sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [
@@ -26,6 +26,7 @@
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^25.1.0",
29
+ "prettier": "^3.8.1",
29
30
  "ts-node": "^10.9.2",
30
31
  "typescript": "^5.9.3"
31
32
  }