@developeruche/tx-spammer-sdk 1.0.0

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.
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Responsible for tracking and limiting gas usage during the spam sequence.
3
+ * Ensures the total estimated gas does not exceed the configured maximum.
4
+ */
5
+ export declare class GasGuardian {
6
+ private totalGasUsed;
7
+ private readonly maxGasLimit;
8
+ private _isLimitReached;
9
+ /**
10
+ * @param maxGasLimit The total gas limit allowed for this guardian instance.
11
+ */
12
+ constructor(maxGasLimit: bigint);
13
+ /**
14
+ * Checks if the estimated gas would exceed the limit.
15
+ * Throws an error if the limit is exceeded to prevent the transaction.
16
+ *
17
+ * @param estimatedGas The gas estimated for the pending transaction.
18
+ * @throws Error if the limit is already reached or would be exceeded.
19
+ */
20
+ checkLimit(estimatedGas: bigint): void;
21
+ /**
22
+ * Records the actual gas used by a transaction (or the estimate if actual usage is not available).
23
+ * Updates the internal counter and checks if the limit has been hit.
24
+ *
25
+ * @param gasUsed The amount of gas to add to the total usage.
26
+ */
27
+ recordUsage(gasUsed: bigint): void;
28
+ /**
29
+ * Returns the total amount of gas recorded so far.
30
+ */
31
+ get gasUsed(): bigint;
32
+ /**
33
+ * Returns true if the gas limit has been reached or exceeded.
34
+ */
35
+ get isLimitReached(): boolean;
36
+ /**
37
+ * Resets the gas usage counter and limit flag.
38
+ */
39
+ reset(): void;
40
+ }
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GasGuardian = void 0;
4
+ /**
5
+ * Responsible for tracking and limiting gas usage during the spam sequence.
6
+ * Ensures the total estimated gas does not exceed the configured maximum.
7
+ */
8
+ class GasGuardian {
9
+ totalGasUsed = 0n;
10
+ maxGasLimit;
11
+ _isLimitReached = false;
12
+ /**
13
+ * @param maxGasLimit The total gas limit allowed for this guardian instance.
14
+ */
15
+ constructor(maxGasLimit) {
16
+ this.maxGasLimit = maxGasLimit;
17
+ }
18
+ /**
19
+ * Checks if the estimated gas would exceed the limit.
20
+ * Throws an error if the limit is exceeded to prevent the transaction.
21
+ *
22
+ * @param estimatedGas The gas estimated for the pending transaction.
23
+ * @throws Error if the limit is already reached or would be exceeded.
24
+ */
25
+ checkLimit(estimatedGas) {
26
+ if (this._isLimitReached) {
27
+ throw new Error('Gas limit already reached. No further transactions allowed.');
28
+ }
29
+ if (this.totalGasUsed + estimatedGas > this.maxGasLimit) {
30
+ this._isLimitReached = true;
31
+ throw new Error(`Gas limit exceeded. Total: ${this.totalGasUsed}, Attempting: ${estimatedGas}, Max: ${this.maxGasLimit}`);
32
+ }
33
+ }
34
+ /**
35
+ * Records the actual gas used by a transaction (or the estimate if actual usage is not available).
36
+ * Updates the internal counter and checks if the limit has been hit.
37
+ *
38
+ * @param gasUsed The amount of gas to add to the total usage.
39
+ */
40
+ recordUsage(gasUsed) {
41
+ this.totalGasUsed += gasUsed;
42
+ if (this.totalGasUsed >= this.maxGasLimit) {
43
+ this._isLimitReached = true;
44
+ }
45
+ }
46
+ /**
47
+ * Returns the total amount of gas recorded so far.
48
+ */
49
+ get gasUsed() {
50
+ return this.totalGasUsed;
51
+ }
52
+ /**
53
+ * Returns true if the gas limit has been reached or exceeded.
54
+ */
55
+ get isLimitReached() {
56
+ return this._isLimitReached;
57
+ }
58
+ /**
59
+ * Resets the gas usage counter and limit flag.
60
+ */
61
+ reset() {
62
+ this.totalGasUsed = 0n;
63
+ this._isLimitReached = false;
64
+ }
65
+ }
66
+ exports.GasGuardian = GasGuardian;
@@ -0,0 +1,41 @@
1
+ import { SpamSequenceConfig } from './types';
2
+ /**
3
+ * The central coordinator for the spam sequence.
4
+ *
5
+ * Responsibilities:
6
+ * - Validates configuration.
7
+ * - Initializes the root wallet and worker wallets.
8
+ * - Funds workers from the root account.
9
+ * - Manages the lifecycle of the spam sequence.
10
+ * - Orchestrates different strategies (Single or Mixed) by dispatching workers.
11
+ */
12
+ export declare class SpamOrchestrator {
13
+ private config;
14
+ private rootAccount;
15
+ private rootClient;
16
+ private publicClient;
17
+ private workers;
18
+ private gasGuardian;
19
+ private chain;
20
+ /**
21
+ * @param config The spam sequence configuration.
22
+ * @param rootPrivateKey The private key of the funding account.
23
+ */
24
+ constructor(config: SpamSequenceConfig, rootPrivateKey: `0x${string}`);
25
+ /**
26
+ * Sets up the spam environment by creating worker wallets and funding them.
27
+ *
28
+ * @param fundingAmount The amount of ETH (in wei) to send to each worker. Defaults to 1 ETH.
29
+ */
30
+ setup(fundingAmount?: bigint): Promise<void>;
31
+ /**
32
+ * Starts the spam sequence based on the configured strategy.
33
+ *
34
+ * Supports:
35
+ * - Single Strategy: All workers execute the same task.
36
+ * - Mixed Strategy: Workers are partitioned based on percentage allocation.
37
+ *
38
+ * Stops when the duration expires or gas limits are reached.
39
+ */
40
+ start(): Promise<void>;
41
+ }
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SpamOrchestrator = void 0;
4
+ const viem_1 = require("viem");
5
+ const accounts_1 = require("viem/accounts");
6
+ const chains_1 = require("viem/chains");
7
+ const types_1 = require("./types");
8
+ const GasGuardian_1 = require("./GasGuardian");
9
+ const Worker_1 = require("./Worker");
10
+ const strategies_1 = require("./strategies");
11
+ /**
12
+ * The central coordinator for the spam sequence.
13
+ *
14
+ * Responsibilities:
15
+ * - Validates configuration.
16
+ * - Initializes the root wallet and worker wallets.
17
+ * - Funds workers from the root account.
18
+ * - Manages the lifecycle of the spam sequence.
19
+ * - Orchestrates different strategies (Single or Mixed) by dispatching workers.
20
+ */
21
+ class SpamOrchestrator {
22
+ config;
23
+ rootAccount;
24
+ rootClient;
25
+ publicClient;
26
+ workers = [];
27
+ gasGuardian;
28
+ chain;
29
+ /**
30
+ * @param config The spam sequence configuration.
31
+ * @param rootPrivateKey The private key of the funding account.
32
+ */
33
+ constructor(config, rootPrivateKey) {
34
+ // Validate config
35
+ this.config = types_1.SpamSequenceConfigSchema.parse(config);
36
+ this.chain = { ...chains_1.mainnet, id: this.config.chainId, rpcUrls: { default: { http: [this.config.rpcUrl] } } };
37
+ this.rootAccount = (0, accounts_1.privateKeyToAccount)(rootPrivateKey);
38
+ this.rootClient = (0, viem_1.createWalletClient)({
39
+ account: this.rootAccount,
40
+ chain: this.chain,
41
+ transport: (0, viem_1.http)(this.config.rpcUrl),
42
+ });
43
+ this.publicClient = (0, viem_1.createPublicClient)({
44
+ chain: this.chain,
45
+ transport: (0, viem_1.http)(this.config.rpcUrl),
46
+ });
47
+ this.gasGuardian = new GasGuardian_1.GasGuardian(this.config.maxGasLimit);
48
+ }
49
+ /**
50
+ * Sets up the spam environment by creating worker wallets and funding them.
51
+ *
52
+ * @param fundingAmount The amount of ETH (in wei) to send to each worker. Defaults to 1 ETH.
53
+ */
54
+ async setup(fundingAmount = (0, viem_1.parseEther)('1')) {
55
+ console.log(`Setting up ${this.config.concurrency} workers...`);
56
+ for (let i = 0; i < this.config.concurrency; i++) {
57
+ const worker = new Worker_1.Worker(this.config.rpcUrl, this.chain);
58
+ this.workers.push(worker);
59
+ }
60
+ // Fund workers
61
+ console.log(`Funding workers with ${fundingAmount} wei each...`);
62
+ const fundingTxs = [];
63
+ const nonce = await this.publicClient.getTransactionCount({ address: this.rootAccount.address });
64
+ for (let i = 0; i < this.workers.length; i++) {
65
+ const tx = await this.rootClient.sendTransaction({
66
+ to: this.workers[i].address,
67
+ value: fundingAmount,
68
+ // @ts-ignore
69
+ nonce: nonce + i,
70
+ });
71
+ fundingTxs.push(tx);
72
+ }
73
+ console.log(`Waiting for ${fundingTxs.length} funding transactions to confirm...`);
74
+ // 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...");
77
+ // Initialize nonces
78
+ await Promise.all(this.workers.map(async (worker) => {
79
+ const n = await this.publicClient.getTransactionCount({ address: worker.address });
80
+ worker.setNonce(n);
81
+ }));
82
+ console.log("Setup complete.");
83
+ }
84
+ /**
85
+ * Starts the spam sequence based on the configured strategy.
86
+ *
87
+ * Supports:
88
+ * - Single Strategy: All workers execute the same task.
89
+ * - Mixed Strategy: Workers are partitioned based on percentage allocation.
90
+ *
91
+ * Stops when the duration expires or gas limits are reached.
92
+ */
93
+ async start() {
94
+ console.log("Starting spam sequence...");
95
+ const strategy = this.config.strategy;
96
+ const duration = this.config.durationSeconds ? this.config.durationSeconds * 1000 : Infinity;
97
+ const startTime = Date.now();
98
+ // Helper to run a strategy loop for a subset of workers
99
+ const runLoop = async (workers, stratConfig, guardian) => {
100
+ const loopTasks = workers.map(async (worker, index) => {
101
+ while (true) {
102
+ if (guardian.isLimitReached)
103
+ break;
104
+ if (Date.now() - startTime > duration)
105
+ break;
106
+ try {
107
+ if (stratConfig.mode === 'transfer') {
108
+ await (0, strategies_1.executeEthTransfer)(worker, stratConfig, guardian, this.publicClient);
109
+ }
110
+ else if (stratConfig.mode === 'deploy') {
111
+ await (0, strategies_1.executeContractDeploy)(worker, stratConfig, guardian, this.publicClient);
112
+ }
113
+ else if (stratConfig.mode === 'read') {
114
+ await (0, strategies_1.executeContractRead)(worker, stratConfig, guardian, this.publicClient);
115
+ }
116
+ else if (stratConfig.mode === 'write') {
117
+ await (0, strategies_1.executeContractWrite)(worker, stratConfig, guardian, this.publicClient);
118
+ }
119
+ }
120
+ catch (e) {
121
+ // console.error(`Worker execution failed:`, e.message);
122
+ // Stop this worker's loop if critical constraint hit
123
+ if (guardian.isLimitReached)
124
+ break;
125
+ // Optimization: Wait a bit on error to avoid hot-looping crashes?
126
+ // await new Promise(r => setTimeout(r, 100));
127
+ }
128
+ }
129
+ });
130
+ await Promise.all(loopTasks);
131
+ };
132
+ if (strategy.mode === 'mixed') {
133
+ console.log("Executing Mixed Strategy...");
134
+ let workerOffset = 0;
135
+ const tasks = [];
136
+ // Sort strategies to handle remainder logic deterministically? Or just iterate.
137
+ for (let i = 0; i < strategy.strategies.length; i++) {
138
+ const subStrat = strategy.strategies[i];
139
+ // Calculate split
140
+ const isLast = i === strategy.strategies.length - 1;
141
+ const sharePercent = subStrat.percentage / 100;
142
+ // Workers
143
+ let count = Math.floor(this.config.concurrency * sharePercent);
144
+ if (isLast) {
145
+ // Assign all remaining workers to the last group to avoid rounding loss
146
+ count = this.workers.length - workerOffset;
147
+ }
148
+ if (count <= 0)
149
+ continue;
150
+ const assignedWorkers = this.workers.slice(workerOffset, workerOffset + count);
151
+ workerOffset += count;
152
+ // Gas Limit
153
+ // For 'read', gas limit is effectively infinity/ignored by strategy logic,
154
+ // but we can assign a portion of the max limit to its guardian just in case.
155
+ const gasLimitShare = BigInt(Math.floor(Number(this.config.maxGasLimit) * sharePercent));
156
+ const subGuardian = new GasGuardian_1.GasGuardian(gasLimitShare);
157
+ console.log(`- Sub-strategy '${subStrat.config.mode}': ${count} workers, ~${gasLimitShare} gas limit`);
158
+ tasks.push(runLoop(assignedWorkers, subStrat.config, subGuardian));
159
+ }
160
+ await Promise.all(tasks);
161
+ }
162
+ else {
163
+ // Single Mode
164
+ await runLoop(this.workers, strategy, this.gasGuardian);
165
+ }
166
+ console.log("Spam sequence finished.");
167
+ }
168
+ }
169
+ exports.SpamOrchestrator = SpamOrchestrator;
@@ -0,0 +1,45 @@
1
+ import { type WalletClient, type Account, type Chain, type Transport, type Hash, Address } from 'viem';
2
+ /**
3
+ * Represents a single worker wallet in the spam system.
4
+ * Handles local nonce management to enable high-concurrency transaction submission.
5
+ */
6
+ export declare class Worker {
7
+ account: Account;
8
+ client: WalletClient<Transport, Chain, Account>;
9
+ private nonce;
10
+ /**
11
+ * Creates a new Worker instance with a fresh random private key.
12
+ *
13
+ * @param rpcUrl The RPC URL to connect to.
14
+ * @param chain The chain definition to use.
15
+ */
16
+ constructor(rpcUrl: string, chain: Chain);
17
+ /**
18
+ * Returns the address of the worker's wallet.
19
+ */
20
+ get address(): Address;
21
+ /**
22
+ * Initializes or updates the local nonce.
23
+ * Must be called before sending transactions, typically after fetching the current nonce from the network.
24
+ *
25
+ * @param nonce The starting nonce value.
26
+ */
27
+ setNonce(nonce: number): void;
28
+ /**
29
+ * Gets the current local nonce and increments it for the next transaction.
30
+ * Use this to avoid 'nonce too low' errors during rapid fire submission.
31
+ *
32
+ * @returns The nonce to use for the current transaction.
33
+ * @throws Error if the nonce has not been initialized via setNonce().
34
+ */
35
+ getAndIncrementNonce(): number;
36
+ /**
37
+ * Sends a transaction from this worker's wallet using the managed local nonce.
38
+ *
39
+ * @param to The recipient address.
40
+ * @param value The value in wei to transfer (default 0).
41
+ * @param data The calldata for the transaction (default empty).
42
+ * @returns The hash of the submitted transaction.
43
+ */
44
+ sendTransaction(to: Address, value?: bigint, data?: Hash): Promise<Hash>;
45
+ }
package/dist/Worker.js ADDED
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Worker = void 0;
4
+ const viem_1 = require("viem");
5
+ const accounts_1 = require("viem/accounts");
6
+ /**
7
+ * Represents a single worker wallet in the spam system.
8
+ * Handles local nonce management to enable high-concurrency transaction submission.
9
+ */
10
+ class Worker {
11
+ account;
12
+ client;
13
+ nonce = null;
14
+ /**
15
+ * Creates a new Worker instance with a fresh random private key.
16
+ *
17
+ * @param rpcUrl The RPC URL to connect to.
18
+ * @param chain The chain definition to use.
19
+ */
20
+ constructor(rpcUrl, chain) {
21
+ // Generate a fresh random private key for this worker
22
+ const privateKey = (0, accounts_1.generatePrivateKey)();
23
+ this.account = (0, accounts_1.privateKeyToAccount)(privateKey);
24
+ this.client = (0, viem_1.createWalletClient)({
25
+ account: this.account,
26
+ chain: chain,
27
+ transport: (0, viem_1.http)(rpcUrl),
28
+ });
29
+ }
30
+ /**
31
+ * Returns the address of the worker's wallet.
32
+ */
33
+ get address() {
34
+ return this.account.address;
35
+ }
36
+ /**
37
+ * Initializes or updates the local nonce.
38
+ * Must be called before sending transactions, typically after fetching the current nonce from the network.
39
+ *
40
+ * @param nonce The starting nonce value.
41
+ */
42
+ setNonce(nonce) {
43
+ this.nonce = nonce;
44
+ }
45
+ /**
46
+ * Gets the current local nonce and increments it for the next transaction.
47
+ * Use this to avoid 'nonce too low' errors during rapid fire submission.
48
+ *
49
+ * @returns The nonce to use for the current transaction.
50
+ * @throws Error if the nonce has not been initialized via setNonce().
51
+ */
52
+ getAndIncrementNonce() {
53
+ if (this.nonce === null) {
54
+ throw new Error("Nonce not initialized for worker");
55
+ }
56
+ return this.nonce++;
57
+ }
58
+ /**
59
+ * Sends a transaction from this worker's wallet using the managed local nonce.
60
+ *
61
+ * @param to The recipient address.
62
+ * @param value The value in wei to transfer (default 0).
63
+ * @param data The calldata for the transaction (default empty).
64
+ * @returns The hash of the submitted transaction.
65
+ */
66
+ async sendTransaction(to, value = 0n, data = '0x') {
67
+ return this.client.sendTransaction({
68
+ to,
69
+ value,
70
+ data,
71
+ // @ts-ignore - nonce override is valid but viem types might be strict depending on version
72
+ nonce: this.getAndIncrementNonce()
73
+ });
74
+ }
75
+ }
76
+ exports.Worker = Worker;
@@ -0,0 +1,14 @@
1
+ import { Worker } from '../Worker';
2
+ import { ContractDeployConfig } from '../types';
3
+ import { GasGuardian } from '../GasGuardian';
4
+ import { type PublicClient } from 'viem';
5
+ /**
6
+ * Executes a contract deployment transaction.
7
+ * Estimates gas for deployment and enforces limits before broadcasting.
8
+ *
9
+ * @param worker The worker instance performing the deployment.
10
+ * @param config The deployment configuration (bytecode, args).
11
+ * @param gasGuardian The gas guardian to track usage.
12
+ * @param publicClient The viem public client for gas estimation.
13
+ */
14
+ export declare function executeContractDeploy(worker: Worker, config: ContractDeployConfig, gasGuardian: GasGuardian, publicClient: PublicClient): Promise<void>;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeContractDeploy = executeContractDeploy;
4
+ /**
5
+ * Executes a contract deployment transaction.
6
+ * Estimates gas for deployment and enforces limits before broadcasting.
7
+ *
8
+ * @param worker The worker instance performing the deployment.
9
+ * @param config The deployment configuration (bytecode, args).
10
+ * @param gasGuardian The gas guardian to track usage.
11
+ * @param publicClient The viem public client for gas estimation.
12
+ */
13
+ async function executeContractDeploy(worker, config, gasGuardian, publicClient) {
14
+ const bytecode = config.bytecode;
15
+ const args = config.args || [];
16
+ try {
17
+ // Estimate deployment gas
18
+ // Note: deployContract is a helper on WalletClient, but estimateGas needs explicit call
19
+ const estimatedGas = await publicClient.estimateGas({
20
+ account: worker.account,
21
+ data: bytecode,
22
+ // TODO: handle args encoding for estimation if strict?
23
+ // Typically just data + args encoded is fine, but estimateGas with 'data' works for deploy
24
+ });
25
+ gasGuardian.checkLimit(estimatedGas);
26
+ const hash = await worker.client.deployContract({
27
+ abi: [],
28
+ bytecode: bytecode,
29
+ account: worker.account,
30
+ args: args,
31
+ chain: worker.client.chain,
32
+ // @ts-ignore
33
+ nonce: worker.getAndIncrementNonce(),
34
+ });
35
+ gasGuardian.recordUsage(estimatedGas);
36
+ }
37
+ catch (error) {
38
+ throw error;
39
+ }
40
+ }
@@ -0,0 +1,13 @@
1
+ import { Worker } from '../Worker';
2
+ import { ContractReadConfig } from '../types';
3
+ import { GasGuardian } from '../GasGuardian';
4
+ /**
5
+ * Executes a read-only contract call (eth_call).
6
+ * Does not check gas limits since it's off-chain, but puts load on the RPC node.
7
+ *
8
+ * @param worker The worker instance performing the read.
9
+ * @param config The read configuration (contract, method, args).
10
+ * @param gasGuardian The gas guardian (unused for blocking reads).
11
+ * @param publicClient The viem client to perform the call.
12
+ */
13
+ export declare function executeContractRead(worker: Worker, config: ContractReadConfig, gasGuardian: GasGuardian, publicClient: any): Promise<void>;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeContractRead = executeContractRead;
4
+ /**
5
+ * Executes a read-only contract call (eth_call).
6
+ * Does not check gas limits since it's off-chain, but puts load on the RPC node.
7
+ *
8
+ * @param worker The worker instance performing the read.
9
+ * @param config The read configuration (contract, method, args).
10
+ * @param gasGuardian The gas guardian (unused for blocking reads).
11
+ * @param publicClient The viem client to perform the call.
12
+ */
13
+ async function executeContractRead(worker, config, gasGuardian, publicClient) {
14
+ // Reads are "eth_call" and do not consume gas on-chain in the same way,
15
+ // but they do put load on the node. The user prompt says "High-frequency eth_call operations".
16
+ // The GasGuardian tracks "Gas Cap". Usually gas cap refers to on-chain gas used.
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,
19
+ // and reads don't fill blocks, maybe we don't count them against the Guardian?
20
+ // BUT the prompt tracks cumulative gas used.
21
+ // eth_calls don't use gas on chain. I will NOT count them towards the cap unless specified.
22
+ try {
23
+ await publicClient.readContract({
24
+ address: config.targetContract,
25
+ abi: config.abi,
26
+ functionName: config.functionName,
27
+ args: config.args || [],
28
+ });
29
+ }
30
+ catch (error) {
31
+ throw error;
32
+ }
33
+ }
@@ -0,0 +1,14 @@
1
+ import { Worker } from '../Worker';
2
+ import { ContractWriteConfig } from '../types';
3
+ import { GasGuardian } from '../GasGuardian';
4
+ import { type PublicClient } from 'viem';
5
+ /**
6
+ * Executes a state-changing contract write transaction.
7
+ * Estimates gas and enforces limits. Supports dynamic arguments via generator or static args.
8
+ *
9
+ * @param worker The worker instance performing the write.
10
+ * @param config The write configuration (target, method, args).
11
+ * @param gasGuardian The gas guardian to track usage.
12
+ * @param publicClient The viem public client for gas estimation.
13
+ */
14
+ export declare function executeContractWrite(worker: Worker, config: ContractWriteConfig, gasGuardian: GasGuardian, publicClient: PublicClient): Promise<void>;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeContractWrite = executeContractWrite;
4
+ /**
5
+ * Executes a state-changing contract write transaction.
6
+ * Estimates gas and enforces limits. Supports dynamic arguments via generator or static args.
7
+ *
8
+ * @param worker The worker instance performing the write.
9
+ * @param config The write configuration (target, method, args).
10
+ * @param gasGuardian The gas guardian to track usage.
11
+ * @param publicClient The viem public client for gas estimation.
12
+ */
13
+ async function executeContractWrite(worker, config, gasGuardian, publicClient) {
14
+ try {
15
+ // Determine args
16
+ let args = config.staticArgs || [];
17
+ if (config.argsGenerator) {
18
+ args = config.argsGenerator();
19
+ }
20
+ const estimatedGas = await publicClient.estimateContractGas({
21
+ address: config.targetContract,
22
+ abi: config.abi,
23
+ functionName: config.functionName,
24
+ args: args,
25
+ account: worker.account
26
+ });
27
+ gasGuardian.checkLimit(estimatedGas);
28
+ const hash = await worker.client.writeContract({
29
+ address: config.targetContract,
30
+ abi: config.abi,
31
+ functionName: config.functionName,
32
+ args: args,
33
+ account: worker.account,
34
+ chain: worker.client.chain,
35
+ // @ts-ignore
36
+ nonce: worker.getAndIncrementNonce(),
37
+ });
38
+ gasGuardian.recordUsage(estimatedGas);
39
+ }
40
+ catch (error) {
41
+ throw error;
42
+ }
43
+ }
@@ -0,0 +1,13 @@
1
+ import { Worker } from '../Worker';
2
+ import { EthTransferConfig } from '../types';
3
+ import { GasGuardian } from '../GasGuardian';
4
+ import { type PublicClient } from 'viem';
5
+ /**
6
+ * Executes a single ETH transfer transaction.
7
+ *
8
+ * @param worker The worker instance sending the transaction.
9
+ * @param config The transfer configuration (amount, recipient).
10
+ * @param gasGuardian The gas guardian to check and record usage.
11
+ * @param publicClient The viem public client for gas estimation.
12
+ */
13
+ export declare function executeEthTransfer(worker: Worker, config: EthTransferConfig, gasGuardian: GasGuardian, publicClient: PublicClient): Promise<void>;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeEthTransfer = executeEthTransfer;
4
+ /**
5
+ * Executes a single ETH transfer transaction.
6
+ *
7
+ * @param worker The worker instance sending the transaction.
8
+ * @param config The transfer configuration (amount, recipient).
9
+ * @param gasGuardian The gas guardian to check and record usage.
10
+ * @param publicClient The viem public client for gas estimation.
11
+ */
12
+ async function executeEthTransfer(worker, config, gasGuardian, publicClient) {
13
+ const amount = config.amountPerTx;
14
+ const recipient = config.recipient || worker.address;
15
+ try {
16
+ const estimatedGas = await publicClient.estimateGas({
17
+ account: worker.account,
18
+ to: recipient,
19
+ value: amount
20
+ });
21
+ gasGuardian.checkLimit(estimatedGas);
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.
25
+ // For high-performance spamming, we pre-check and post-record.
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
29
+ // or batch them if nonces allow.
30
+ // The requirement mentions A -> B -> C.
31
+ // If depth is > 1, this specific call might just be one step.
32
+ // However, for simple spamming, we might just loop this function.
33
+ }
34
+ catch (error) {
35
+ // If gas limit reached or other error, we stop this worker's current attempt
36
+ throw error;
37
+ }
38
+ }
@@ -0,0 +1,4 @@
1
+ export * from './EthTransfer';
2
+ export * from './ContractDeploy';
3
+ export * from './ContractRead';
4
+ export * from './ContractWrite';
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./EthTransfer"), exports);
18
+ __exportStar(require("./ContractDeploy"), exports);
19
+ __exportStar(require("./ContractRead"), exports);
20
+ __exportStar(require("./ContractWrite"), exports);
@@ -0,0 +1,206 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Configuration for ETH transfer spam strategy.
4
+ * Includes depth for recursive transfers (if implemented) and recipient handling.
5
+ */
6
+ export declare const EthTransferConfigSchema: z.ZodObject<{
7
+ mode: z.ZodLiteral<"transfer">;
8
+ depth: z.ZodDefault<z.ZodNumber>;
9
+ amountPerTx: z.ZodDefault<z.ZodBigInt>;
10
+ recipient: z.ZodOptional<z.ZodString>;
11
+ }, z.core.$strip>;
12
+ /**
13
+ * Configuration for contract deployment spam strategy.
14
+ * Requires bytecode and optional constructor arguments.
15
+ */
16
+ export declare const ContractDeployConfigSchema: z.ZodObject<{
17
+ mode: z.ZodLiteral<"deploy">;
18
+ bytecode: z.ZodString;
19
+ args: z.ZodOptional<z.ZodArray<z.ZodAny>>;
20
+ }, z.core.$strip>;
21
+ /**
22
+ * Configuration for contract read operations (eth_call).
23
+ * Does not consume gas on-chain but stresses the RPC node.
24
+ */
25
+ export declare const ContractReadConfigSchema: z.ZodObject<{
26
+ mode: z.ZodLiteral<"read">;
27
+ targetContract: z.ZodString;
28
+ functionName: z.ZodString;
29
+ abi: z.ZodArray<z.ZodAny>;
30
+ args: z.ZodOptional<z.ZodArray<z.ZodAny>>;
31
+ }, z.core.$strip>;
32
+ /**
33
+ * Configuration for contract write operations (state-changing transactions).
34
+ * Consumes gas and stresses block building.
35
+ */
36
+ export declare const ContractWriteConfigSchema: z.ZodObject<{
37
+ mode: z.ZodLiteral<"write">;
38
+ targetContract: z.ZodString;
39
+ functionName: z.ZodString;
40
+ abi: z.ZodArray<z.ZodAny>;
41
+ argsGenerator: z.ZodOptional<z.ZodCustom<() => any[], () => any[]>>;
42
+ staticArgs: z.ZodOptional<z.ZodArray<z.ZodAny>>;
43
+ }, z.core.$strip>;
44
+ /**
45
+ * Configuration for mixed strategy mode.
46
+ * Allows defining multiple sub-strategies with percentage-based resource allocation.
47
+ */
48
+ export declare const MixedStrategyConfigSchema: z.ZodObject<{
49
+ mode: z.ZodLiteral<"mixed">;
50
+ strategies: z.ZodArray<z.ZodObject<{
51
+ config: z.ZodDiscriminatedUnion<[z.ZodObject<{
52
+ mode: z.ZodLiteral<"transfer">;
53
+ depth: z.ZodDefault<z.ZodNumber>;
54
+ amountPerTx: z.ZodDefault<z.ZodBigInt>;
55
+ recipient: z.ZodOptional<z.ZodString>;
56
+ }, z.core.$strip>, z.ZodObject<{
57
+ mode: z.ZodLiteral<"deploy">;
58
+ bytecode: z.ZodString;
59
+ args: z.ZodOptional<z.ZodArray<z.ZodAny>>;
60
+ }, z.core.$strip>, z.ZodObject<{
61
+ mode: z.ZodLiteral<"read">;
62
+ targetContract: z.ZodString;
63
+ functionName: z.ZodString;
64
+ abi: z.ZodArray<z.ZodAny>;
65
+ args: z.ZodOptional<z.ZodArray<z.ZodAny>>;
66
+ }, z.core.$strip>, z.ZodObject<{
67
+ mode: z.ZodLiteral<"write">;
68
+ targetContract: z.ZodString;
69
+ functionName: z.ZodString;
70
+ abi: z.ZodArray<z.ZodAny>;
71
+ argsGenerator: z.ZodOptional<z.ZodCustom<() => any[], () => any[]>>;
72
+ staticArgs: z.ZodOptional<z.ZodArray<z.ZodAny>>;
73
+ }, z.core.$strip>], "mode">;
74
+ percentage: z.ZodNumber;
75
+ }, z.core.$strip>>;
76
+ }, z.core.$strip>;
77
+ /**
78
+ * Union schema for all possible spam strategies.
79
+ */
80
+ export declare const SpamStrategySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
81
+ mode: z.ZodLiteral<"transfer">;
82
+ depth: z.ZodDefault<z.ZodNumber>;
83
+ amountPerTx: z.ZodDefault<z.ZodBigInt>;
84
+ recipient: z.ZodOptional<z.ZodString>;
85
+ }, z.core.$strip>, z.ZodObject<{
86
+ mode: z.ZodLiteral<"deploy">;
87
+ bytecode: z.ZodString;
88
+ args: z.ZodOptional<z.ZodArray<z.ZodAny>>;
89
+ }, z.core.$strip>, z.ZodObject<{
90
+ mode: z.ZodLiteral<"read">;
91
+ targetContract: z.ZodString;
92
+ functionName: z.ZodString;
93
+ abi: z.ZodArray<z.ZodAny>;
94
+ args: z.ZodOptional<z.ZodArray<z.ZodAny>>;
95
+ }, z.core.$strip>, z.ZodObject<{
96
+ mode: z.ZodLiteral<"write">;
97
+ targetContract: z.ZodString;
98
+ functionName: z.ZodString;
99
+ abi: z.ZodArray<z.ZodAny>;
100
+ argsGenerator: z.ZodOptional<z.ZodCustom<() => any[], () => any[]>>;
101
+ staticArgs: z.ZodOptional<z.ZodArray<z.ZodAny>>;
102
+ }, z.core.$strip>, z.ZodObject<{
103
+ mode: z.ZodLiteral<"mixed">;
104
+ strategies: z.ZodArray<z.ZodObject<{
105
+ config: z.ZodDiscriminatedUnion<[z.ZodObject<{
106
+ mode: z.ZodLiteral<"transfer">;
107
+ depth: z.ZodDefault<z.ZodNumber>;
108
+ amountPerTx: z.ZodDefault<z.ZodBigInt>;
109
+ recipient: z.ZodOptional<z.ZodString>;
110
+ }, z.core.$strip>, z.ZodObject<{
111
+ mode: z.ZodLiteral<"deploy">;
112
+ bytecode: z.ZodString;
113
+ args: z.ZodOptional<z.ZodArray<z.ZodAny>>;
114
+ }, z.core.$strip>, z.ZodObject<{
115
+ mode: z.ZodLiteral<"read">;
116
+ targetContract: z.ZodString;
117
+ functionName: z.ZodString;
118
+ abi: z.ZodArray<z.ZodAny>;
119
+ args: z.ZodOptional<z.ZodArray<z.ZodAny>>;
120
+ }, z.core.$strip>, z.ZodObject<{
121
+ mode: z.ZodLiteral<"write">;
122
+ targetContract: z.ZodString;
123
+ functionName: z.ZodString;
124
+ abi: z.ZodArray<z.ZodAny>;
125
+ argsGenerator: z.ZodOptional<z.ZodCustom<() => any[], () => any[]>>;
126
+ staticArgs: z.ZodOptional<z.ZodArray<z.ZodAny>>;
127
+ }, z.core.$strip>], "mode">;
128
+ percentage: z.ZodNumber;
129
+ }, z.core.$strip>>;
130
+ }, z.core.$strip>], "mode">;
131
+ /**
132
+ * Top-level configuration for a spam sequence.
133
+ */
134
+ export declare const SpamSequenceConfigSchema: z.ZodObject<{
135
+ rpcUrl: z.ZodString;
136
+ chainId: z.ZodNumber;
137
+ maxGasLimit: z.ZodDefault<z.ZodBigInt>;
138
+ concurrency: z.ZodDefault<z.ZodNumber>;
139
+ strategy: z.ZodDiscriminatedUnion<[z.ZodObject<{
140
+ mode: z.ZodLiteral<"transfer">;
141
+ depth: z.ZodDefault<z.ZodNumber>;
142
+ amountPerTx: z.ZodDefault<z.ZodBigInt>;
143
+ recipient: z.ZodOptional<z.ZodString>;
144
+ }, z.core.$strip>, z.ZodObject<{
145
+ mode: z.ZodLiteral<"deploy">;
146
+ bytecode: z.ZodString;
147
+ args: z.ZodOptional<z.ZodArray<z.ZodAny>>;
148
+ }, z.core.$strip>, z.ZodObject<{
149
+ mode: z.ZodLiteral<"read">;
150
+ targetContract: z.ZodString;
151
+ functionName: z.ZodString;
152
+ abi: z.ZodArray<z.ZodAny>;
153
+ args: z.ZodOptional<z.ZodArray<z.ZodAny>>;
154
+ }, z.core.$strip>, z.ZodObject<{
155
+ mode: z.ZodLiteral<"write">;
156
+ targetContract: z.ZodString;
157
+ functionName: z.ZodString;
158
+ abi: z.ZodArray<z.ZodAny>;
159
+ argsGenerator: z.ZodOptional<z.ZodCustom<() => any[], () => any[]>>;
160
+ staticArgs: z.ZodOptional<z.ZodArray<z.ZodAny>>;
161
+ }, z.core.$strip>, z.ZodObject<{
162
+ mode: z.ZodLiteral<"mixed">;
163
+ strategies: z.ZodArray<z.ZodObject<{
164
+ config: z.ZodDiscriminatedUnion<[z.ZodObject<{
165
+ mode: z.ZodLiteral<"transfer">;
166
+ depth: z.ZodDefault<z.ZodNumber>;
167
+ amountPerTx: z.ZodDefault<z.ZodBigInt>;
168
+ recipient: z.ZodOptional<z.ZodString>;
169
+ }, z.core.$strip>, z.ZodObject<{
170
+ mode: z.ZodLiteral<"deploy">;
171
+ bytecode: z.ZodString;
172
+ args: z.ZodOptional<z.ZodArray<z.ZodAny>>;
173
+ }, z.core.$strip>, z.ZodObject<{
174
+ mode: z.ZodLiteral<"read">;
175
+ targetContract: z.ZodString;
176
+ functionName: z.ZodString;
177
+ abi: z.ZodArray<z.ZodAny>;
178
+ args: z.ZodOptional<z.ZodArray<z.ZodAny>>;
179
+ }, z.core.$strip>, z.ZodObject<{
180
+ mode: z.ZodLiteral<"write">;
181
+ targetContract: z.ZodString;
182
+ functionName: z.ZodString;
183
+ abi: z.ZodArray<z.ZodAny>;
184
+ argsGenerator: z.ZodOptional<z.ZodCustom<() => any[], () => any[]>>;
185
+ staticArgs: z.ZodOptional<z.ZodArray<z.ZodAny>>;
186
+ }, z.core.$strip>], "mode">;
187
+ percentage: z.ZodNumber;
188
+ }, z.core.$strip>>;
189
+ }, z.core.$strip>], "mode">;
190
+ durationSeconds: z.ZodOptional<z.ZodNumber>;
191
+ totalTxs: z.ZodOptional<z.ZodNumber>;
192
+ }, z.core.$strip>;
193
+ /** TypeScript type alias for ETH Transfer config. */
194
+ export type EthTransferConfig = z.infer<typeof EthTransferConfigSchema>;
195
+ /** TypeScript type alias for Contract Deploy config. */
196
+ export type ContractDeployConfig = z.infer<typeof ContractDeployConfigSchema>;
197
+ /** TypeScript type alias for Contract Read config. */
198
+ export type ContractReadConfig = z.infer<typeof ContractReadConfigSchema>;
199
+ /** TypeScript type alias for Contract Write config. */
200
+ export type ContractWriteConfig = z.infer<typeof ContractWriteConfigSchema>;
201
+ /** TypeScript type alias for Mixed Strategy config. */
202
+ export type MixedStrategyConfig = z.infer<typeof MixedStrategyConfigSchema>;
203
+ /** TypeScript type alias for any Spam Strategy config. */
204
+ export type SpamStrategyConfig = z.infer<typeof SpamStrategySchema>;
205
+ /** TypeScript type alias for the full Spam Sequence config. */
206
+ export type SpamSequenceConfig = z.infer<typeof SpamSequenceConfigSchema>;
package/dist/types.js ADDED
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SpamSequenceConfigSchema = exports.SpamStrategySchema = exports.MixedStrategyConfigSchema = exports.ContractWriteConfigSchema = exports.ContractReadConfigSchema = exports.ContractDeployConfigSchema = exports.EthTransferConfigSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ // --- Zod Schemas ---
6
+ /**
7
+ * Configuration for ETH transfer spam strategy.
8
+ * Includes depth for recursive transfers (if implemented) and recipient handling.
9
+ */
10
+ exports.EthTransferConfigSchema = zod_1.z.object({
11
+ mode: zod_1.z.literal('transfer'),
12
+ /** Chain depth for recursive transfers (e.g. A -> B -> C). Default is 1. */
13
+ depth: zod_1.z.number().int().positive().default(1),
14
+ /** Amount of ETH to transfer per transaction in wei. Default is 1 wei. */
15
+ amountPerTx: zod_1.z.bigint().positive().default(1n),
16
+ /** Optional recipient address. If not set, may self-transfer or generate random address. */
17
+ recipient: zod_1.z.string().optional(),
18
+ });
19
+ /**
20
+ * Configuration for contract deployment spam strategy.
21
+ * Requires bytecode and optional constructor arguments.
22
+ */
23
+ exports.ContractDeployConfigSchema = zod_1.z.object({
24
+ mode: zod_1.z.literal('deploy'),
25
+ /** Hex string of the contract bytecode to deploy. */
26
+ bytecode: zod_1.z.string().startsWith('0x'),
27
+ /** Optional constructor arguments for the deployment. */
28
+ args: zod_1.z.array(zod_1.z.any()).optional(),
29
+ });
30
+ /**
31
+ * Configuration for contract read operations (eth_call).
32
+ * Does not consume gas on-chain but stresses the RPC node.
33
+ */
34
+ exports.ContractReadConfigSchema = zod_1.z.object({
35
+ mode: zod_1.z.literal('read'),
36
+ /** The target contract address to read from. */
37
+ targetContract: zod_1.z.string().startsWith('0x'),
38
+ /** The name of the function to call. */
39
+ functionName: zod_1.z.string(),
40
+ /** The contract ABI in JSON format. */
41
+ abi: zod_1.z.array(zod_1.z.any()),
42
+ /** Optional arguments for the function call. */
43
+ args: zod_1.z.array(zod_1.z.any()).optional(),
44
+ });
45
+ /**
46
+ * Configuration for contract write operations (state-changing transactions).
47
+ * Consumes gas and stresses block building.
48
+ */
49
+ exports.ContractWriteConfigSchema = zod_1.z.object({
50
+ mode: zod_1.z.literal('write'),
51
+ /** The target contract address to write to. */
52
+ targetContract: zod_1.z.string().startsWith('0x'),
53
+ /** The name of the function to call. */
54
+ functionName: zod_1.z.string(),
55
+ /** The contract ABI in JSON format. */
56
+ abi: zod_1.z.array(zod_1.z.any()),
57
+ /** Optional generator function for dynamic arguments per call. */
58
+ argsGenerator: zod_1.z.custom().optional(),
59
+ /** Optional static arguments for the function call (used if generator not provided). */
60
+ staticArgs: zod_1.z.array(zod_1.z.any()).optional(),
61
+ });
62
+ // Single strategy types for reuse
63
+ const SingleStrategySchema = zod_1.z.discriminatedUnion('mode', [
64
+ exports.EthTransferConfigSchema,
65
+ exports.ContractDeployConfigSchema,
66
+ exports.ContractReadConfigSchema,
67
+ exports.ContractWriteConfigSchema,
68
+ ]);
69
+ /**
70
+ * Configuration for mixed strategy mode.
71
+ * Allows defining multiple sub-strategies with percentage-based resource allocation.
72
+ */
73
+ exports.MixedStrategyConfigSchema = zod_1.z.object({
74
+ mode: zod_1.z.literal('mixed'),
75
+ /**
76
+ * List of strategies with their respective percentage allocation.
77
+ * Percentages must sum to 100.
78
+ */
79
+ strategies: zod_1.z.array(zod_1.z.object({
80
+ config: SingleStrategySchema,
81
+ percentage: zod_1.z.number().nonnegative().max(100),
82
+ })).refine((items) => {
83
+ const sum = items.reduce((acc, item) => acc + item.percentage, 0);
84
+ return Math.abs(sum - 100) < 0.1; // Allow small float error
85
+ }, { message: "Percentages must sum to 100" }),
86
+ });
87
+ /**
88
+ * Union schema for all possible spam strategies.
89
+ */
90
+ exports.SpamStrategySchema = zod_1.z.discriminatedUnion('mode', [
91
+ exports.EthTransferConfigSchema,
92
+ exports.ContractDeployConfigSchema,
93
+ exports.ContractReadConfigSchema,
94
+ exports.ContractWriteConfigSchema,
95
+ exports.MixedStrategyConfigSchema,
96
+ ]);
97
+ /**
98
+ * Top-level configuration for a spam sequence.
99
+ */
100
+ exports.SpamSequenceConfigSchema = zod_1.z.object({
101
+ /** RPC URL of the target node. */
102
+ rpcUrl: zod_1.z.string().url(),
103
+ /** Chain ID of the target network. */
104
+ chainId: zod_1.z.number().int().positive(),
105
+ /** Maximum cumulative gas limit for the sequence. Execution stops if reached. */
106
+ maxGasLimit: zod_1.z.bigint().default(29000000n),
107
+ /** Number of concurrent workers (wallets) to use. */
108
+ concurrency: zod_1.z.number().int().positive().default(10),
109
+ /** The selected spam strategy configuration. */
110
+ strategy: exports.SpamStrategySchema,
111
+ /** Optional duration in seconds to run the spam sequence. */
112
+ durationSeconds: zod_1.z.number().int().positive().optional(),
113
+ /** Optional maximum total transactions to attempts (if implemented). */
114
+ totalTxs: zod_1.z.number().int().positive().optional(),
115
+ });
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@developeruche/tx-spammer-sdk",
3
+ "version": "1.0.0",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "prepublishOnly": "pnpm run build",
12
+ "test": "echo \"Error: no test specified\" && exit 1"
13
+ },
14
+ "keywords": [
15
+ "ethereum",
16
+ "spammer",
17
+ "viem",
18
+ "stress-test"
19
+ ],
20
+ "author": "",
21
+ "license": "ISC",
22
+ "description": "SDK for high-performance EVM node stress testing",
23
+ "dependencies": {
24
+ "viem": "^2.45.1",
25
+ "zod": "^4.3.6"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^25.1.0",
29
+ "ts-node": "^10.9.2",
30
+ "typescript": "^5.9.3"
31
+ }
32
+ }