@developeruche/tx-spammer-sdk 1.0.0 → 1.0.2
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/SpamOrchestrator.d.ts +5 -1
- package/dist/SpamOrchestrator.js +35 -13
- package/dist/Worker.js +2 -2
- package/dist/strategies/ContractDeploy.d.ts +2 -2
- package/dist/strategies/ContractDeploy.js +2 -1
- package/dist/strategies/ContractRead.js +3 -3
- package/dist/strategies/ContractWrite.d.ts +2 -2
- package/dist/strategies/ContractWrite.js +2 -1
- package/dist/strategies/EthTransfer.d.ts +2 -2
- package/dist/strategies/EthTransfer.js +6 -5
- package/dist/types.js +5 -3
- package/package.json +3 -2
|
@@ -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<
|
|
41
|
+
start(): Promise<{
|
|
42
|
+
blockNumber: bigint;
|
|
43
|
+
txHash: Hash | null;
|
|
44
|
+
}>;
|
|
41
45
|
}
|
package/dist/SpamOrchestrator.js
CHANGED
|
@@ -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 = {
|
|
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({
|
|
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(
|
|
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(
|
|
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(
|
|
100
|
+
console.log('Starting spam sequence...');
|
|
95
101
|
const strategy = this.config.strategy;
|
|
96
|
-
const duration = this.config.durationSeconds
|
|
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(
|
|
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(
|
|
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(
|
|
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<
|
|
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<
|
|
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<
|
|
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
|
-
|
|
28
|
-
//
|
|
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
|
|
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
|
-
}))
|
|
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:
|
|
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.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -26,7 +26,8 @@
|
|
|
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
|
}
|
|
32
|
-
}
|
|
33
|
+
}
|