diff --git a/.gitignore b/.gitignore index bcff4ca..186f3b2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ /.idea .env .DS_Store +git/ diff --git a/tps-monitoring/.gitignore b/tps-monitoring/.gitignore new file mode 100644 index 0000000..ccc2384 --- /dev/null +++ b/tps-monitoring/.gitignore @@ -0,0 +1,9 @@ +node_modules/ + + + +*.log +.DS_Store + +# Drafts folder for temporary files and notes +drafts/ diff --git a/tps-monitoring/README.md b/tps-monitoring/README.md new file mode 100644 index 0000000..ecfd315 --- /dev/null +++ b/tps-monitoring/README.md @@ -0,0 +1,379 @@ +# πŸš€ Simple TPS Monitor: Substrate TPS Measurement Tool + +A simple tool for measuring real TPS (transactions per second) in Polkadot/Substrate blockchains with modular architecture. + +## 🎯 Features + +- βœ… **Accurate TPS measurement** - analyzes blocks, not sending speed +- βœ… **Real balance transactions** - uses transferKeepAlive +- βœ… **Automatic nonce management** - proper handling of multiple transactions +- βœ… **Continuous monitoring** - tracks blocks in real-time +- βœ… **Configurable load** - --tps parameter for controlling sending frequency +- βœ… **Monitor-only mode** - --tps 0 for analysis without load generation +- βœ… **Simple CLI interface** - one command to run +- βœ… **Graceful shutdown** - proper termination with Ctrl+C +- βœ… **Modular architecture** - clean separation of concerns + +## πŸ“¦ Installation + +```bash +# Clone the repository +git clone https://github.com/your-username/tps-monitoring.git +cd tps-monitoring + +# Install dependencies +npm install +``` + +## πŸ”§ Requirements + +- Node.js >= 16.0.0 +- Access to Polkadot/Substrate node via WebSocket +- Alice account with balance for testing + +## πŸ—οΈ How It Works - Modular Architecture + +### Project Structure +``` +tps-monitoring/ +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ index.js # CLI entry point +β”‚ β”œβ”€β”€ modules/ +β”‚ β”‚ β”œβ”€β”€ TPSMonitor.js # Main coordinator +β”‚ β”‚ β”œβ”€β”€ TransactionSender.js # Transaction generation +β”‚ β”‚ └── BlockMonitor.js # Block monitoring & TPS calculation +β”‚ └── simple-monitor.js # Legacy monolithic version +``` + +### System Flow + +#### 1. **Entry Point (index.js)** +```javascript +// Parse CLI arguments and create TPSMonitor instance +const monitor = new TPSMonitor() +await monitor.start(options) +``` + +**What happens:** +- Parses command line arguments (`--node`, `--tps`) +- Creates TPSMonitor instance +- Starts the monitoring system + +#### 2. **Main Coordinator (TPSMonitor.js)** +```javascript +// Initialize blockchain connection and components +await this.initialize(options.node) +this.blockMonitor.startMonitoring(this.api) +if (options.tps > 0) { + await this.transactionSender.startSending(options.tps) +} +``` + +**What happens:** +- Connects to blockchain via WebSocket +- Initializes cryptography and keyring +- Creates TransactionSender and BlockMonitor instances +- Starts both monitoring and transaction sending (if enabled) +- Handles graceful shutdown on Ctrl+C + +#### 3. **Transaction Sender (TransactionSender.js)** +```javascript +// Send transactions at specified TPS rate +const intervalMs = 1000 / tpsTarget +const sendTx = async () => { + const transfer = this.api.tx.balances.transferKeepAlive(this.alice.address, 1) + await transfer.signAndSend(this.alice) + setTimeout(sendTx, intervalMs) +} +``` + +**What happens:** +- Creates Alice test account +- Sends `transferKeepAlive` transactions to self +- Maintains specified TPS rate using `setTimeout` +- Logs progress every 10 transactions +- Can be stopped gracefully + +#### 4. **Block Monitor (BlockMonitor.js)** +```javascript +// Subscribe to new blocks and calculate TPS +api.derive.chain.subscribeNewHeads(async (header) => { + const block = await api.rpc.chain.getBlock(header.hash) + const txCount = block.block.extrinsics.length + + // Store data and calculate TPS + this.blockTimes.push(now) + this.txCounts.push(txCount) + const avgTPS = this.calculateTPS() +}) +``` + +**What happens:** +- Subscribes to new blocks via WebSocket +- Extracts transaction count from each block +- Maintains sliding window of last 10 blocks +- Calculates real-time TPS: `total_transactions / time_span` +- Displays statistics for each block + +### Component Interaction + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ index.js │───▢│ TPSMonitor.js │───▢│TransactionSenderβ”‚ +β”‚ (CLI) β”‚ β”‚ (Coordinator) β”‚ β”‚ .js β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ + β–Ό β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ + β”‚ BlockMonitor.js β”‚β—€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ (TPS Calculator)β”‚ (API shared) + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Key Benefits of Modular Design + +#### **Separation of Concerns** +- **TransactionSender**: Only handles transaction generation +- **BlockMonitor**: Only handles block monitoring and TPS calculation +- **TPSMonitor**: Only handles coordination and lifecycle +- **index.js**: Only handles CLI interface + +#### **Testability** +```javascript +// Test components independently +const sender = new TransactionSender(api, keyring) +await sender.startSending(5) + +const monitor = new BlockMonitor() +monitor.startMonitoring(api) +``` + +#### **Reusability** +- TransactionSender can be used in other load testing tools +- BlockMonitor can be extended for other blockchain metrics +- TPSMonitor can be adapted for different blockchain networks + +#### **Maintainability** +- Easy to find and fix bugs in specific components +- Simple to add new features (e.g., different transaction types) +- Clear interfaces between components + +## πŸš€ Usage + +### Basic Command + +```bash +# Run with load generation (10 TPS by default) +node src/index.js + +# Connect to custom node +node src/index.js --node ws://your-node:9944 + +# Set target TPS +node src/index.js --tps 50 + +# Monitor only without load generation +node src/index.js --tps 0 + +# Full example +node src/index.js \ + --node ws://localhost:9944 \ + --tps 25 +``` + +### CLI Parameters + +- `-n, --node ` - Node WebSocket URL (default: ws://localhost:9944) +- `-t, --tps ` - Target TPS to generate (0 = monitor only, default: 10) + +### NPM Scripts + +```bash +# Run with default parameters +npm start +``` + +## πŸ“Š How it works + +### 1. Blockchain Connection +- Initializes cryptography +- Connects to node via WebSocket +- Creates Alice account for testing + +### 2. Load Generation (if --tps > 0) +- Sends Alice β†’ Alice transactions at specified frequency +- Uses `transferKeepAlive` for safe transfers +- Automatically manages nonce for each transaction +- Shows progress every 10 transactions + +### 3. TPS Monitoring +- Subscribes to new blocks +- Counts transactions in each block +- Calculates average TPS over last 10 blocks +- Shows statistics in real-time + +### 4. Statistics Output +``` +Block #1234: 8 txs, 13.3 TPS (45s runtime) +``` +Where: +- `#1234` - block number +- `8 txs` - number of transactions in block +- `13.3 TPS` - average TPS over last 10 blocks +- `45s runtime` - program runtime + +## πŸƒβ€β™‚οΈ Usage Examples + +### Node Performance Testing + +```bash +# Start with low load +node src/index.js --tps 5 + +# Gradually increase load +node src/index.js --tps 10 +node src/index.js --tps 20 +node src/index.js --tps 50 +``` + +### Monitoring Existing Network + +```bash +# Monitor TPS without generating load +node src/index.js --tps 0 --node ws://mainnet-node:9944 +``` + +### Local Testing + +```bash +# Test local node +node src/index.js --node ws://localhost:9944 --tps 15 +``` + +## πŸ” Differences from Legacy Script + +### ❌ Problems in original script: +1. **Wrong transaction type**: `system.remark` instead of `balances.transfer` +2. **Automatic nonce**: led to transaction rejections +3. **Sending speed measurement**: instead of real TPS +4. **One-time execution**: send batch and exit + +### βœ… Fixes in Simple TPS Monitor: +1. **Balance transfers** - real money transfers +2. **Automatic nonce** - proper handling of multiple transactions +3. **Block reading** - real TPS measurement +4. **Continuous operation** - configurable sending frequency +5. **Block monitoring** - real-time analysis +6. **Modular architecture** - clean separation of concerns + +## πŸ› οΈ Troubleshooting + +### Transactions not going through: +- Check Alice account balance +- Ensure node is accessible and running +- Verify node URL is correct + +### Low TPS: +- Try reducing sending frequency (--tps) +- Check node load +- Ensure node doesn't limit TPS + +### Connection errors: +- Check node accessibility +- Ensure correct WebSocket URL is used +- Check network settings + +### Common Error Messages: +- `Priority is too low`: Transaction pool is full, reduce TPS +- `Transaction is outdated`: Nonce issue, restart the tool +- `Connection failed`: Check node URL and network + +## πŸ“ˆ Load Testing Recommendations + +```bash +# 1. Start with monitoring without load +node src/index.js --tps 0 + +# 2. Add minimal load +node src/index.js --tps 1 + +# 3. Gradually increase load +node src/index.js --tps 5 +node src/index.js --tps 10 +node src/index.js --tps 20 + +# 4. Find maximum load +node src/index.js --tps 50 +node src/index.js --tps 100 +``` + +## 🎯 Result + +Simple TPS Monitor provides: +- βœ… **Accurate TPS measurement** - based on blockchain data +- βœ… **Proper transaction sending** - with nonce increment +- βœ… **Real load** - balance transfers, not just messages +- βœ… **Configuration flexibility** - --tps parameter for load control +- βœ… **Ease of use** - single file, one command launch +- βœ… **Modular design** - clean architecture, easy to extend + +**Result:** Reliable and accurate tool for measuring Polkadot/Substrate network performance. + +## πŸ“‹ Requirements for Testing + +### Node Setup +- Running Substrate/Polkadot node +- WebSocket endpoint available (default: ws://localhost:9944) +- Node configured for development/testing + +### Account Setup +- Alice account with sufficient balance +- Account should be able to send transactions +- Recommended: Use development node with pre-funded accounts + +## πŸ”§ Development + +### Dependencies +- `@polkadot/api` - Polkadot/Substrate API +- `@polkadot/util-crypto` - Cryptographic utilities +- `commander` - CLI argument parsing + +### Architecture Benefits + +#### **Single Responsibility Principle (SRP)** +- Each class has one responsibility +- Easier to understand and maintain + +#### **Testability** +```javascript +// Can test components separately +const sender = new TransactionSender(api, keyring) +await sender.startSending(5) + +const monitor = new BlockMonitor() +monitor.startMonitoring(api) +``` + +#### **Reusability** +- TransactionSender can be used in other projects +- BlockMonitor can be extended for other metrics + +#### **Extensibility** +- Easy to add new transaction types +- Can add new metrics to BlockMonitor + +## πŸ“„ License + +This project is unlicensed. All rights reserved. + +## 🀝 Contributing + +1. Fork the repository +2. Create your feature branch +3. Commit your changes +4. Push to the branch +5. Create a Pull Request + +--- + +**Note:** This tool is designed for testing and development purposes. Use responsibly and ensure you have proper permissions for the target network. diff --git a/tps-monitoring/docs/problem-analysis.md b/tps-monitoring/docs/problem-analysis.md new file mode 100644 index 0000000..3f0ec2e --- /dev/null +++ b/tps-monitoring/docs/problem-analysis.md @@ -0,0 +1,215 @@ +# Legacy TPS Script Problem Analysis + +## Legacy Script (chain-load-test/index.js) + +### Legacy Script Code: +```javascript +import {ApiPromise, WsProvider, Keyring} from '@polkadot/api'; +import {cryptoWaitReady} from '@polkadot/util-crypto'; + +async function runTpsTest(txCount = 100) { + // ... node connection ... + + for (let i = 0; i < txCount; i++) { + const tx = api.tx.system.remark(`${remarkText}-${i}`); + txs.push( + tx.signAndSend(sender, {nonce: -1}) // ❌ PROBLEM: auto-nonces + ); + } + + await Promise.all(txs); // ❌ PROBLEM: simultaneous sending + + // ❌ PROBLEM: measuring sending speed, not actual TPS + const elapsedSeconds = (end - start) / 1000; + console.log(`⚑ TPS: ${(txCount / elapsedSeconds).toFixed(2)}`); +} +``` + +## Legacy Script Problems + +### 1. ❌ Nonce Problem (transaction counter) +- **Problem:** Uses `{nonce: -1}` for all transactions +- **Result:** Only the first transaction passes, others are rejected +- **Reason:** All transactions have the same nonce in one block + +### 2. ❌ Wrong Transaction Type +- **Problem:** Uses `system.remark` (simple messages) +- **Result:** Doesn't measure real network load +- **Needed:** `balances.transfer` for real transfers + +### 3. ❌ Incorrect Performance Measurement +- **Problem:** Measures client transaction sending speed +- **Result:** Shows code performance, not network performance +- **Needed:** Read blocks and count extrinsics in them + +### 4. ❌ One-time Execution +- **Problem:** Sends batch of transactions once and exits +- **Result:** No continuous TPS monitoring +- **Needed:** Continuous sending with adjustable frequency + +## Our Solution (simple-monitor.js) + +### βœ… 1. Proper Nonce Management +```javascript +async startSendingTransactions(tpsTarget) { + this.sendInterval = setInterval(async () => { + const tx = this.api.tx.balances.transferKeepAlive( + this.alice.address, + 1000000000000 + ) + await tx.signAndSend(this.alice, { nonce: this.nonce++ }) // βœ… Nonce increment + }, 1000 / tpsTarget) +} +``` + +### βœ… 2. Real Balance Transfers +```javascript +// βœ… Using balances.transferKeepAlive (Alice β†’ Alice) +const tx = this.api.tx.balances.transferKeepAlive(this.alice.address, 1000000000000) +``` + +### βœ… 3. Real TPS Measurement from Blocks +```javascript +async startMonitoring() { + await this.api.rpc.chain.subscribeNewHeads(async (header) => { + const block = await this.api.rpc.chain.getBlock(header.hash) + const txCount = block.block.extrinsics.length - 1 // βœ… Count extrinsics + + // βœ… Calculate TPS based on last 10 blocks + const totalTxs = this.recentBlocks.reduce((sum, b) => sum + b.txCount, 0) + const tps = totalTxs / this.recentBlocks.length * (1000 / 6000) // 6s per block + }) +} +``` + +### βœ… 4. Continuous Operation with Adjustable Load +```javascript +// βœ… Continuous transaction sending +this.sendInterval = setInterval(async () => { + // Sending with specified TPS +}, 1000 / tpsTarget) + +// βœ… Continuous block monitoring +await this.api.rpc.chain.subscribeNewHeads(/* callback */) +``` + +## Results Comparison + +| Parameter | Legacy Script | Our simple-monitor.js | +|----------------------|----------------------|------------------------------| +| **Nonce** | ❌ All identical | βœ… Proper increment | +| **Transactions** | ❌ system.remark | βœ… balances.transferKeepAlive| +| **TPS Measurement** | ❌ Sending speed | βœ… Real TPS from blocks | +| **Operation Mode** | ❌ One-time | βœ… Continuous monitoring | +| **Load Configuration**| ❌ Fixed | βœ… --tps parameter | +| **Result** | ❌ Inaccurate | βœ… Real network TPS | + +## Conclusion + +Our `simple-monitor.js` project completely solves all 4 critical problems of the legacy script and provides: + +- βœ… **Accurate TPS measurement** - reads data from blockchain +- βœ… **Proper transaction sending** - with nonce increment +- βœ… **Real load** - balance transfers, not just messages +- βœ… **Configuration flexibility** - --tps parameter for load control +- βœ… **Ease of use** - single file, one command launch + +**Result:** Accurate and reliable tool for measuring Polkadot/Substrate network performance. + +--- + +# Sub-Flood Script Problem Analysis + +## Sub-Flood Script (sub-flood-script/index.ts) + +### Sub-Flood Script Code (Before Fix): +```typescript +// ❌ PROBLEM: Incorrect nonce management for multiple users +for (let threadNo = 0; threadNo < TOTAL_THREADS; threadNo++) { + for (let transactionNo = 0; transactionNo < TRANSACTION_PER_BATCH; transactionNo++) { + let userNo = threadNo * USERS_PER_THREAD + transactionNo; + let senderKeyPair = keyPairs.get(userNo); + + // ❌ PROBLEM: Getting fresh nonce for each transaction + let nonce = (await api.rpc.system.accountNextIndex(senderKeyPair.address)).toNumber(); + await transfer.signAndSend(senderKeyPair, { + nonce, // ❌ Same nonce for all transactions in batch + tip: 1 + }); + } +} +``` + +## Sub-Flood Script Problems + +### 1. ❌ Nonce Management Problem (Multiple Users) +- **Problem:** Gets fresh nonce for each transaction using `accountNextIndex` +- **Result:** All transactions from same user in batch have identical nonce +- **Reason:** Network nonce doesn't update fast enough for rapid transactions + +### 2. ❌ Priority Conflicts +- **Problem:** All transactions have same priority (`tip: 1`) +- **Result:** "Priority is too low" errors when multiple transactions compete +- **Reason:** Node rejects transactions with same nonce and priority + +### 3. ❌ Batch Processing Issues +- **Problem:** Sends all transactions in batch simultaneously +- **Result:** Only first transaction per user succeeds, others fail +- **Reason:** Nonce conflicts in transaction pool + +## Our Solution (Fixed Sub-Flood Script) + +### βœ… 1. Proper Nonce Management for Multiple Users +```typescript +// βœ… Pre-fetch nonces for all users +let nonces: number[] = []; +for (let i = 0; i <= TOTAL_USERS; i++) { + let keys = keyring.addFromUri(seedFromNum(i)); + let accountInfo = await api.query.system.account(keys.address); + nonces.push(accountInfo.nonce.toNumber()); +} + +// βœ… Use and increment nonces manually +let currentNonce = nonces[userNo]; +await transfer.signAndSend(senderKeyPair, { + nonce: currentNonce, + tip: 1 +}); +nonces[userNo]++; // βœ… Increment for next transaction +``` + +### βœ… 2. Sequential Nonce Increment +```typescript +// βœ… Each user maintains their own nonce counter +// βœ… No conflicts between different users +// βœ… Proper transaction ordering +``` + +### βœ… 3. Batch Processing with Proper Nonce +```typescript +// βœ… All transactions in batch can succeed +// βœ… Each user's transactions have unique nonces +// βœ… No "Priority is too low" errors +``` + +## Sub-Flood vs Simple TPS Monitor Comparison + +| Parameter | Sub-Flood (Before Fix) | Sub-Flood (After Fix) | Simple TPS Monitor | +|----------------------|--------------------------|---------------------------|---------------------------| +| **Nonce Management** | ❌ Fresh nonce per tx | βœ… Manual increment | βœ… Manual increment | +| **Multiple Users** | βœ… Yes | βœ… Yes | ❌ Single user (Alice) | +| **Priority Errors** | ❌ "Priority is too low" | βœ… No errors | βœ… No errors | +| **TPS Measurement** | βœ… Real TPS from blocks | βœ… Real TPS from blocks | βœ… Real TPS from blocks | +| **Load Distribution**| βœ… Across multiple users | βœ… Across multiple users | ❌ Single user load | +| **Result** | ❌ 10-20% success rate | βœ… 100% success rate | βœ… 100% success rate | + +## Conclusion + +The Sub-Flood Script provides advanced multi-user load testing capabilities but had critical nonce management issues. After fixing: + +- βœ… **Proper nonce management** - manual increment per user +- βœ… **Multi-user load distribution** - realistic network simulation +- βœ… **No priority conflicts** - all transactions succeed +- βœ… **Advanced features** - multiple threads, user management, finalization tracking + +**Result:** Advanced load testing tool with proper nonce handling for realistic multi-user scenarios. \ No newline at end of file diff --git a/tps-monitoring/package-lock.json b/tps-monitoring/package-lock.json new file mode 100644 index 0000000..4db3ba0 --- /dev/null +++ b/tps-monitoring/package-lock.json @@ -0,0 +1,1417 @@ +{ + "name": "tps-simple", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tps-simple", + "version": "1.0.0", + "license": "UNLICENSED", + "dependencies": { + "@polkadot/api": "^14.2.2", + "@polkadot/util-crypto": "^13.1.1", + "commander": "^11.1.0", + "minimist": "^1.2.8" + }, + "devDependencies": { + "@types/minimist": "^1.2.5", + "@types/node": "^20.0.0", + "tsx": "^4.7.0", + "typescript": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.6", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.6.tgz", + "integrity": "sha512-GIKz/j99FRthB8icyJQA51E8Uk5hXmdyThjgQXRKiv9h0zeRlzSCLIzFw6K1LotZ3XuB7yzlf76qk7uBmTdFqA==", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@polkadot-api/json-rpc-provider": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.0.1.tgz", + "integrity": "sha512-/SMC/l7foRjpykLTUTacIH05H3mr9ip8b5xxfwXlVezXrNVLp3Cv0GX6uItkKd+ZjzVPf3PFrDF2B2/HLSNESA==", + "optional": true + }, + "node_modules/@polkadot-api/json-rpc-provider-proxy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.1.0.tgz", + "integrity": "sha512-8GSFE5+EF73MCuLQm8tjrbCqlgclcHBSRaswvXziJ0ZW7iw3UEMsKkkKvELayWyBuOPa2T5i1nj6gFOeIsqvrg==", + "optional": true + }, + "node_modules/@polkadot-api/metadata-builders": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-builders/-/metadata-builders-0.3.2.tgz", + "integrity": "sha512-TKpfoT6vTb+513KDzMBTfCb/ORdgRnsS3TDFpOhAhZ08ikvK+hjHMt5plPiAX/OWkm1Wc9I3+K6W0hX5Ab7MVg==", + "optional": true, + "dependencies": { + "@polkadot-api/substrate-bindings": "0.6.0", + "@polkadot-api/utils": "0.1.0" + } + }, + "node_modules/@polkadot-api/observable-client": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@polkadot-api/observable-client/-/observable-client-0.3.2.tgz", + "integrity": "sha512-HGgqWgEutVyOBXoGOPp4+IAq6CNdK/3MfQJmhCJb8YaJiaK4W6aRGrdQuQSTPHfERHCARt9BrOmEvTXAT257Ug==", + "optional": true, + "dependencies": { + "@polkadot-api/metadata-builders": "0.3.2", + "@polkadot-api/substrate-bindings": "0.6.0", + "@polkadot-api/utils": "0.1.0" + }, + "peerDependencies": { + "@polkadot-api/substrate-client": "0.1.4", + "rxjs": ">=7.8.0" + } + }, + "node_modules/@polkadot-api/substrate-bindings": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.6.0.tgz", + "integrity": "sha512-lGuhE74NA1/PqdN7fKFdE5C1gNYX357j1tWzdlPXI0kQ7h3kN0zfxNOpPUN7dIrPcOFZ6C0tRRVrBylXkI6xPw==", + "optional": true, + "dependencies": { + "@noble/hashes": "^1.3.1", + "@polkadot-api/utils": "0.1.0", + "@scure/base": "^1.1.1", + "scale-ts": "^1.6.0" + } + }, + "node_modules/@polkadot-api/substrate-client": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.1.4.tgz", + "integrity": "sha512-MljrPobN0ZWTpn++da9vOvt+Ex+NlqTlr/XT7zi9sqPtDJiQcYl+d29hFAgpaeTqbeQKZwz3WDE9xcEfLE8c5A==", + "optional": true, + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.0.1", + "@polkadot-api/utils": "0.1.0" + } + }, + "node_modules/@polkadot-api/utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.1.0.tgz", + "integrity": "sha512-MXzWZeuGxKizPx2Xf/47wx9sr/uxKw39bVJUptTJdsaQn/TGq+z310mHzf1RCGvC1diHM8f593KrnDgc9oNbJA==", + "optional": true + }, + "node_modules/@polkadot/api": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/api/-/api-14.3.1.tgz", + "integrity": "sha512-ZBKSXEVJa1S1bnmpnA7KT/fX3sJDIJOdVD9Hp3X+G73yvXzuK5k1Mn5z9bD/AcMs/HAGcbuYU+b9+b9IByH9YQ==", + "dependencies": { + "@polkadot/api-augment": "14.3.1", + "@polkadot/api-base": "14.3.1", + "@polkadot/api-derive": "14.3.1", + "@polkadot/keyring": "^13.2.3", + "@polkadot/rpc-augment": "14.3.1", + "@polkadot/rpc-core": "14.3.1", + "@polkadot/rpc-provider": "14.3.1", + "@polkadot/types": "14.3.1", + "@polkadot/types-augment": "14.3.1", + "@polkadot/types-codec": "14.3.1", + "@polkadot/types-create": "14.3.1", + "@polkadot/types-known": "14.3.1", + "@polkadot/util": "^13.2.3", + "@polkadot/util-crypto": "^13.2.3", + "eventemitter3": "^5.0.1", + "rxjs": "^7.8.1", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/api-augment": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/api-augment/-/api-augment-14.3.1.tgz", + "integrity": "sha512-PE6DW+8kRhbnGKn7qCF7yM6eEt/kqrY8bh1i0RZcPY9QgwXW4bZZrtMK4WssX6Z70NTEoOW6xHYIjc7gFZuz8g==", + "dependencies": { + "@polkadot/api-base": "14.3.1", + "@polkadot/rpc-augment": "14.3.1", + "@polkadot/types": "14.3.1", + "@polkadot/types-augment": "14.3.1", + "@polkadot/types-codec": "14.3.1", + "@polkadot/util": "^13.2.3", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/api-base": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/api-base/-/api-base-14.3.1.tgz", + "integrity": "sha512-GZT6rTpT3HYZ/C3rLPjoX3rX3DOxNG/zgts+jKjNrCumAeZkVq5JErKIX8/3f2TVaE2Kbqniy3d1TH/AL4HBPA==", + "dependencies": { + "@polkadot/rpc-core": "14.3.1", + "@polkadot/types": "14.3.1", + "@polkadot/util": "^13.2.3", + "rxjs": "^7.8.1", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/api-derive": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/api-derive/-/api-derive-14.3.1.tgz", + "integrity": "sha512-PhqUEJCY54vXtIaoYqGUtJY06wHd/K0cBmBz9yCLxp8UZkLoGWhfJRTruI25Jnucf9awS5cZKYqbsoDrL09Oqg==", + "dependencies": { + "@polkadot/api": "14.3.1", + "@polkadot/api-augment": "14.3.1", + "@polkadot/api-base": "14.3.1", + "@polkadot/rpc-core": "14.3.1", + "@polkadot/types": "14.3.1", + "@polkadot/types-codec": "14.3.1", + "@polkadot/util": "^13.2.3", + "@polkadot/util-crypto": "^13.2.3", + "rxjs": "^7.8.1", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/keyring": { + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-13.5.4.tgz", + "integrity": "sha512-dQ/yq2OAl6jvjH+drxyqcfprsU2J9h74GSy5X4499W6YNwCt/2pxAJbmsM3lDWUlGOV1Wnp/aNHHs9kjb8GaJw==", + "dependencies": { + "@polkadot/util": "13.5.4", + "@polkadot/util-crypto": "13.5.4", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "13.5.4", + "@polkadot/util-crypto": "13.5.4" + } + }, + "node_modules/@polkadot/networks": { + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@polkadot/networks/-/networks-13.5.4.tgz", + "integrity": "sha512-JD7brNZsWTWbT3bDnEsAYkJfESvmn1XcoFMLoivVrg8dPXqYxoWcYveKPORjPyMPP6wgJ498vJGq7Ce0ihZ8ig==", + "dependencies": { + "@polkadot/util": "13.5.4", + "@substrate/ss58-registry": "^1.51.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/rpc-augment": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-augment/-/rpc-augment-14.3.1.tgz", + "integrity": "sha512-Z8Hp8fFHwFCiTX0bBCDqCZ4U26wLIJl1NRSjJTsAr+SS68pYZBDGCwhKztpKGqndk1W1akRUaxrkGqYdIFmspQ==", + "dependencies": { + "@polkadot/rpc-core": "14.3.1", + "@polkadot/types": "14.3.1", + "@polkadot/types-codec": "14.3.1", + "@polkadot/util": "^13.2.3", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/rpc-core": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-core/-/rpc-core-14.3.1.tgz", + "integrity": "sha512-FV2NPhFwFxmX8LqibDcGc6IKTBqmvwr7xwF2OA60Br4cX+AQzMSVpFlfQcETll+0M+LnRhqGKGkP0EQWXaSowA==", + "dependencies": { + "@polkadot/rpc-augment": "14.3.1", + "@polkadot/rpc-provider": "14.3.1", + "@polkadot/types": "14.3.1", + "@polkadot/util": "^13.2.3", + "rxjs": "^7.8.1", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/rpc-provider": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-14.3.1.tgz", + "integrity": "sha512-NF/Z/7lzT+jp5LZzC49g+YIjRzXVI0hFag3+B+4zh6E/kKADdF59EHj2Im4LDhRGOnEO9AE4H6/UjNEbZ94JtA==", + "dependencies": { + "@polkadot/keyring": "^13.2.3", + "@polkadot/types": "14.3.1", + "@polkadot/types-support": "14.3.1", + "@polkadot/util": "^13.2.3", + "@polkadot/util-crypto": "^13.2.3", + "@polkadot/x-fetch": "^13.2.3", + "@polkadot/x-global": "^13.2.3", + "@polkadot/x-ws": "^13.2.3", + "eventemitter3": "^5.0.1", + "mock-socket": "^9.3.1", + "nock": "^13.5.5", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@substrate/connect": "0.8.11" + } + }, + "node_modules/@polkadot/types": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-14.3.1.tgz", + "integrity": "sha512-O748XgCLDQYxS5nQ6TJSqW88oC4QNIoNVlWZC2Qq4SmEXuSzaNHQwSVtdyPRJCCc4Oi1DCQvGui4O+EukUl7HA==", + "dependencies": { + "@polkadot/keyring": "^13.2.3", + "@polkadot/types-augment": "14.3.1", + "@polkadot/types-codec": "14.3.1", + "@polkadot/types-create": "14.3.1", + "@polkadot/util": "^13.2.3", + "@polkadot/util-crypto": "^13.2.3", + "rxjs": "^7.8.1", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/types-augment": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-augment/-/types-augment-14.3.1.tgz", + "integrity": "sha512-SC4M6TBlgCglNz+gRbvfoVRDz0Vyeev6v0HeAdw0H6ayEW4BXUdo5bFr0092bdS5uTrEPgiSyUry5TJs2KoXig==", + "dependencies": { + "@polkadot/types": "14.3.1", + "@polkadot/types-codec": "14.3.1", + "@polkadot/util": "^13.2.3", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/types-codec": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-codec/-/types-codec-14.3.1.tgz", + "integrity": "sha512-3y3RBGd+8ebscGbNUOjqUjnRE7hgicgid5LtofHK3O1EDcJQJnYBDkJ7fOAi96CDgHsg+f2FWWkBWEPgpOQoMQ==", + "dependencies": { + "@polkadot/util": "^13.2.3", + "@polkadot/x-bigint": "^13.2.3", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/types-create": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-create/-/types-create-14.3.1.tgz", + "integrity": "sha512-F4EBvF3Zvym0xrkAA5Yz01IAVMepMV3w2Dwd0C9IygEAQ5sYLLPHmf72/aXn+Ag+bSyT2wlJHpDc+nEBXNQ3Gw==", + "dependencies": { + "@polkadot/types-codec": "14.3.1", + "@polkadot/util": "^13.2.3", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/types-known": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-known/-/types-known-14.3.1.tgz", + "integrity": "sha512-58b3Yc7+sxwNjs8axmrA9OCgnxmEKIq7XCH2VxSgLqTeqbohVtxwUSCW/l8NPrq1nxzj4J2sopu0PPg8/++q4g==", + "dependencies": { + "@polkadot/networks": "^13.2.3", + "@polkadot/types": "14.3.1", + "@polkadot/types-codec": "14.3.1", + "@polkadot/types-create": "14.3.1", + "@polkadot/util": "^13.2.3", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/types-support": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-support/-/types-support-14.3.1.tgz", + "integrity": "sha512-MfVe4iIOJIfBr+gj8Lu8gwIvhnO6gDbG5LeaKAjY6vS6Oh0y5Ztr8NdMIl8ccSpoyt3LqIXjfApeGzHiLzr6bw==", + "dependencies": { + "@polkadot/util": "^13.2.3", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/util": { + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-13.5.4.tgz", + "integrity": "sha512-w/D7tqfx5a+yHcVBTb+CWGwpJTwcFRNJaVIBxl/MjF3x8JUZCtcKNwklpWJH5HtwaXT1Mt2aBKjoxlNdnd6FYg==", + "dependencies": { + "@polkadot/x-bigint": "13.5.4", + "@polkadot/x-global": "13.5.4", + "@polkadot/x-textdecoder": "13.5.4", + "@polkadot/x-textencoder": "13.5.4", + "@types/bn.js": "^5.1.6", + "bn.js": "^5.2.1", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/util-crypto": { + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-13.5.4.tgz", + "integrity": "sha512-XkKtiUi6I60DxT0dblGajZsqX4jWTnMpj4Pqxddz61KbpmvyybtAUqgmXOmO/Mob6TgGTutPuFeE7uMNEdFdJw==", + "dependencies": { + "@noble/curves": "^1.3.0", + "@noble/hashes": "^1.3.3", + "@polkadot/networks": "13.5.4", + "@polkadot/util": "13.5.4", + "@polkadot/wasm-crypto": "^7.4.1", + "@polkadot/wasm-util": "^7.4.1", + "@polkadot/x-bigint": "13.5.4", + "@polkadot/x-randomvalues": "13.5.4", + "@scure/base": "^1.1.7", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "13.5.4" + } + }, + "node_modules/@polkadot/wasm-bridge": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-bridge/-/wasm-bridge-7.4.1.tgz", + "integrity": "sha512-tdkJaV453tezBxhF39r4oeG0A39sPKGDJmN81LYLf+Fihb7astzwju+u75BRmDrHZjZIv00un3razJEWCxze6g==", + "dependencies": { + "@polkadot/wasm-util": "7.4.1", + "tslib": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" + } + }, + "node_modules/@polkadot/wasm-crypto": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-7.4.1.tgz", + "integrity": "sha512-kHN/kF7hYxm1y0WeFLWeWir6oTzvcFmR4N8fJJokR+ajYbdmrafPN+6iLgQVbhZnDdxyv9jWDuRRsDnBx8tPMQ==", + "dependencies": { + "@polkadot/wasm-bridge": "7.4.1", + "@polkadot/wasm-crypto-asmjs": "7.4.1", + "@polkadot/wasm-crypto-init": "7.4.1", + "@polkadot/wasm-crypto-wasm": "7.4.1", + "@polkadot/wasm-util": "7.4.1", + "tslib": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" + } + }, + "node_modules/@polkadot/wasm-crypto-asmjs": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-7.4.1.tgz", + "integrity": "sha512-pwU8QXhUW7IberyHJIQr37IhbB6DPkCG5FhozCiNTq4vFBsFPjm9q8aZh7oX1QHQaiAZa2m2/VjIVE+FHGbvHQ==", + "dependencies": { + "tslib": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*" + } + }, + "node_modules/@polkadot/wasm-crypto-init": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-init/-/wasm-crypto-init-7.4.1.tgz", + "integrity": "sha512-AVka33+f7MvXEEIGq5U0dhaA2SaXMXnxVCQyhJTaCnJ5bRDj0Xlm3ijwDEQUiaDql7EikbkkRtmlvs95eSUWYQ==", + "dependencies": { + "@polkadot/wasm-bridge": "7.4.1", + "@polkadot/wasm-crypto-asmjs": "7.4.1", + "@polkadot/wasm-crypto-wasm": "7.4.1", + "@polkadot/wasm-util": "7.4.1", + "tslib": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" + } + }, + "node_modules/@polkadot/wasm-crypto-wasm": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-7.4.1.tgz", + "integrity": "sha512-PE1OAoupFR0ZOV2O8tr7D1FEUAwaggzxtfs3Aa5gr+yxlSOaWUKeqsOYe1KdrcjmZVV3iINEAXxgrbzCmiuONg==", + "dependencies": { + "@polkadot/wasm-util": "7.4.1", + "tslib": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*" + } + }, + "node_modules/@polkadot/wasm-util": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-util/-/wasm-util-7.4.1.tgz", + "integrity": "sha512-RAcxNFf3zzpkr+LX/ItAsvj+QyM56TomJ0xjUMo4wKkHjwsxkz4dWJtx5knIgQz/OthqSDMR59VNEycQeNuXzA==", + "dependencies": { + "tslib": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*" + } + }, + "node_modules/@polkadot/x-bigint": { + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@polkadot/x-bigint/-/x-bigint-13.5.4.tgz", + "integrity": "sha512-vA4vjHWDUAnoAxzp1kSQMCzaArdagGXCNlooI2EOZ0pcFnEf4NkKCVjYg8i5L1QOYRAeJjgoKjKwCFBx63vtRw==", + "dependencies": { + "@polkadot/x-global": "13.5.4", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/x-fetch": { + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@polkadot/x-fetch/-/x-fetch-13.5.4.tgz", + "integrity": "sha512-VVhmfPaQwFVopgtMUCNhodyZXBy9P4wkQwwYWpkQv2KqYOEQVck/Hhq8IVhGdbtPJxCAWsj/EyYTzUIHZ9aBlw==", + "dependencies": { + "@polkadot/x-global": "13.5.4", + "node-fetch": "^3.3.2", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/x-global": { + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@polkadot/x-global/-/x-global-13.5.4.tgz", + "integrity": "sha512-oRUdO8/uKOEmLoPUFYgGascE/nyjT2ObRdf7jgwXOd9f+uUHPiE3K/MNAEi9t9sRKs8dbqgyaGWLTRYCDyzMag==", + "dependencies": { + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/x-randomvalues": { + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-13.5.4.tgz", + "integrity": "sha512-jKVEj+wVO83drbFFGGxhHJqwsOZCzyy6HVwQ/M9G6zhNXHrT46OWK+myd3dB4KbHoxWuH03Nvh540vMC3ah8Fw==", + "dependencies": { + "@polkadot/x-global": "13.5.4", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "13.5.4", + "@polkadot/wasm-util": "*" + } + }, + "node_modules/@polkadot/x-textdecoder": { + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-13.5.4.tgz", + "integrity": "sha512-+5rWIs+mhvBR2D7+/gWQyKKDoQzyHRIUrygphxdpBsFSvsJkTTGeGXLiD/ls0gTTE31Kb6StQJi1b9h6ywOvfg==", + "dependencies": { + "@polkadot/x-global": "13.5.4", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/x-textencoder": { + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-13.5.4.tgz", + "integrity": "sha512-GQ4kVJLtiirjI3NAKCnXCSIRudpTKog5SFPqouImV4X5rSsxnLf2xOqLwgYobdv3SIpTHBA1vy2RpQqUQUF6vw==", + "dependencies": { + "@polkadot/x-global": "13.5.4", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/x-ws": { + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/@polkadot/x-ws/-/x-ws-13.5.4.tgz", + "integrity": "sha512-tznbRjPnb3QW8v6+7zUoJINL84DW2dHJjwd0rkU0dtwzc9Y92faxz3bgOrCpgC2oVDpyUUg2PsyjokVBQHqLSA==", + "dependencies": { + "@polkadot/x-global": "13.5.4", + "tslib": "^2.8.0", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@substrate/connect": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/@substrate/connect/-/connect-0.8.11.tgz", + "integrity": "sha512-ofLs1PAO9AtDdPbdyTYj217Pe+lBfTLltdHDs3ds8no0BseoLeAGxpz1mHfi7zB4IxI3YyAiLjH6U8cw4pj4Nw==", + "deprecated": "versions below 1.x are no longer maintained", + "optional": true, + "dependencies": { + "@substrate/connect-extension-protocol": "^2.0.0", + "@substrate/connect-known-chains": "^1.1.5", + "@substrate/light-client-extension-helpers": "^1.0.0", + "smoldot": "2.0.26" + } + }, + "node_modules/@substrate/connect-extension-protocol": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@substrate/connect-extension-protocol/-/connect-extension-protocol-2.2.2.tgz", + "integrity": "sha512-t66jwrXA0s5Goq82ZtjagLNd7DPGCNjHeehRlE/gcJmJ+G56C0W+2plqOMRicJ8XGR1/YFnUSEqUFiSNbjGrAA==", + "optional": true + }, + "node_modules/@substrate/connect-known-chains": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/@substrate/connect-known-chains/-/connect-known-chains-1.10.3.tgz", + "integrity": "sha512-OJEZO1Pagtb6bNE3wCikc2wrmvEU5x7GxFFLqqbz1AJYYxSlrPCGu4N2og5YTExo4IcloNMQYFRkBGue0BKZ4w==", + "optional": true + }, + "node_modules/@substrate/light-client-extension-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@substrate/light-client-extension-helpers/-/light-client-extension-helpers-1.0.0.tgz", + "integrity": "sha512-TdKlni1mBBZptOaeVrKnusMg/UBpWUORNDv5fdCaJklP4RJiFOzBCrzC+CyVI5kQzsXBisZ+2pXm+rIjS38kHg==", + "optional": true, + "dependencies": { + "@polkadot-api/json-rpc-provider": "^0.0.1", + "@polkadot-api/json-rpc-provider-proxy": "^0.1.0", + "@polkadot-api/observable-client": "^0.3.0", + "@polkadot-api/substrate-client": "^0.1.2", + "@substrate/connect-extension-protocol": "^2.0.0", + "@substrate/connect-known-chains": "^1.1.5", + "rxjs": "^7.8.1" + }, + "peerDependencies": { + "smoldot": "2.x" + } + }, + "node_modules/@substrate/ss58-registry": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/@substrate/ss58-registry/-/ss58-registry-1.51.0.tgz", + "integrity": "sha512-TWDurLiPxndFgKjVavCniytBIw+t4ViOi7TYp9h/D0NMmkEc9klFTo+827eyEJ0lELpqO207Ey7uGxUa+BS1jQ==" + }, + "node_modules/@types/bn.js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.2.0.tgz", + "integrity": "sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.19.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz", + "integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/bn.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==" + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/esbuild": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mock-socket": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz", + "integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/nock": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.6.tgz", + "integrity": "sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==", + "dependencies": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">= 10.13" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/scale-ts": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/scale-ts/-/scale-ts-1.6.1.tgz", + "integrity": "sha512-PBMc2AWc6wSEqJYBDPcyCLUj9/tMKnLX70jLOSndMtcUoLQucP/DM0vnQo1wJAYjTrQiq8iG9rD0q6wFzgjH7g==", + "optional": true + }, + "node_modules/smoldot": { + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-2.0.26.tgz", + "integrity": "sha512-F+qYmH4z2s2FK+CxGj8moYcd1ekSIKH8ywkdqlOz88Dat35iB1DIYL11aILN46YSGMzQW/lbJNS307zBSDN5Ig==", + "optional": true, + "dependencies": { + "ws": "^8.8.1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/tsx": { + "version": "4.20.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.3.tgz", + "integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==", + "dev": true, + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/tps-monitoring/package.json b/tps-monitoring/package.json new file mode 100644 index 0000000..9166430 --- /dev/null +++ b/tps-monitoring/package.json @@ -0,0 +1,38 @@ +{ + "name": "tps-simple", + "version": "1.0.0", + "description": "Simple TPS measurement tool for Polkadot/Substrate blockchains", + "type": "module", + "main": "src/index.js", + "scripts": { + "start": "node src/index.js", + "sub-flood": "tsx load-testing/index.ts", + "build": "tsc", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "@polkadot/api": "^14.2.2", + "@polkadot/util-crypto": "^13.1.1", + "commander": "^11.1.0", + "minimist": "^1.2.8" + }, + "devDependencies": { + "@types/minimist": "^1.2.5", + "@types/node": "^20.0.0", + "tsx": "^4.7.0", + "typescript": "^5.0.0" + }, + "keywords": [ + "polkadot", + "substrate", + "tps", + "blockchain", + "simple", + "load-testing" + ], + "author": "Simple TPS Monitor Team", + "license": "UNLICENSED", + "engines": { + "node": ">=16.0.0" + } +} diff --git a/tps-monitoring/src/index.js b/tps-monitoring/src/index.js new file mode 100644 index 0000000..4f29e7f --- /dev/null +++ b/tps-monitoring/src/index.js @@ -0,0 +1,21 @@ +#!/usr/bin/env node + +import { Command } from 'commander' +import { TPSMonitor } from './modules/TPSMonitor.js' + +// CLI interface +const program = new Command() +program + .name('simple-tps-monitor') + .description('Simple TPS measurement and load testing tool') + .version('1.0.0') + .option('-n, --node ', 'Node websocket URL', 'ws://localhost:9944') + .option('-t, --tps ', 'Target TPS to generate (0 = monitor only)', '10') + .action(async (options) => { + options.tps = parseInt(options.tps) + + const monitor = new TPSMonitor() + await monitor.start(options) + }) + +program.parse() \ No newline at end of file diff --git a/tps-monitoring/src/modules/BlockMonitor.js b/tps-monitoring/src/modules/BlockMonitor.js new file mode 100644 index 0000000..140c9d8 --- /dev/null +++ b/tps-monitoring/src/modules/BlockMonitor.js @@ -0,0 +1,78 @@ +// === BLOCK MONITOR CLASS === +// Monitors new blocks and calculates real TPS from blockchain data +export class BlockMonitor { + constructor() { + this.blockTimes = [] // Array of block timestamps + this.txCounts = [] // Array of transaction counts per block + this.startTime = Date.now() // When monitoring started + } + + // === START MONITORING BLOCKS === + // Subscribe to new blocks and calculate TPS + startMonitoring(api) { + console.log('πŸ“Š Monitoring TPS...') + + // === SUBSCRIBE TO NEW BLOCKS === + // This function runs every time a new block is created + api.derive.chain.subscribeNewHeads(async (header) => { + // === GET BLOCK INFORMATION === + const blockNumber = header.number.toNumber() + const now = Date.now() + + // === FETCH BLOCK DETAILS === + // Get full block data including transactions + const blockHash = header.hash + const block = await api.rpc.chain.getBlock(blockHash) + const txCount = block.block.extrinsics.length // Count transactions in block + + // === STORE BLOCK DATA === + // Save timestamp and transaction count for this block + this.blockTimes.push(now) + this.txCounts.push(txCount) + + // === MAINTAIN SLIDING WINDOW === + // Keep only last 10 blocks for average calculation + if (this.blockTimes.length > 10) { + this.blockTimes.shift() // Remove oldest time + this.txCounts.shift() // Remove oldest count + } + + // === CALCULATE AND DISPLAY TPS === + const avgTPS = this.calculateTPS() + const runtime = Math.floor((now - this.startTime) / 1000) + + // Log block information with current TPS + console.log(`Block #${blockNumber}: ${txCount} txs, ${avgTPS.toFixed(1)} TPS (${runtime}s runtime)`) + }) + } + + // === CALCULATE TPS FROM BLOCKS === + // Calculate transactions per second based on recent blocks + calculateTPS() { + // Need at least 2 blocks to calculate TPS + if (this.blockTimes.length <= 1) return 0 + + // === CALCULATE TIME SPAN === + // Time between first and last block in sliding window + const timeSpan = (this.blockTimes[this.blockTimes.length - 1] - this.blockTimes[0]) / 1000 + + // === COUNT TOTAL TRANSACTIONS === + // Sum all transactions in the sliding window + const totalTx = this.txCounts.reduce((sum, count) => sum + count, 0) + + // === CALCULATE TPS === + // Total transactions divided by time span + return totalTx / timeSpan + } + + // === GET MONITORING STATISTICS === + // Return current statistics for display + getStats() { + return { + blockCount: this.blockTimes.length, // Number of blocks monitored + totalTx: this.txCounts.reduce((sum, count) => sum + count, 0), // Total transactions seen + avgTPS: this.calculateTPS(), // Current average TPS + runtime: Math.floor((Date.now() - this.startTime) / 1000) // Total runtime in seconds + } + } +} \ No newline at end of file diff --git a/tps-monitoring/src/modules/TPSMonitor.js b/tps-monitoring/src/modules/TPSMonitor.js new file mode 100644 index 0000000..39991ca --- /dev/null +++ b/tps-monitoring/src/modules/TPSMonitor.js @@ -0,0 +1,116 @@ +// === MAIN TPS MONITOR CLASS === +// Coordinates transaction sending and block monitoring +import { ApiPromise, WsProvider } from '@polkadot/api' +import { Keyring } from '@polkadot/keyring' +import { cryptoWaitReady } from '@polkadot/util-crypto' +import { TransactionSender } from './TransactionSender.js' +import { BlockMonitor } from './BlockMonitor.js' + +export class TPSMonitor { + constructor() { + this.api = null // Blockchain API connection + this.keyring = null // Account keyring + this.transactionSender = null // Component for sending transactions + this.blockMonitor = null // Component for monitoring blocks + this.isRunning = false // Overall running status + } + + // === SETUP BLOCKCHAIN CONNECTION === + // Connect to node and initialize all components + async initialize(nodeUrl) { + console.log('πŸš€ Starting Simple TPS Monitor...') + console.log('πŸ“‘ Connecting to:', nodeUrl) + + // === INITIALIZE CRYPTO === + // Wait for cryptographic functions to be ready + await cryptoWaitReady() + + // === CONNECT TO BLOCKCHAIN NODE === + // Create WebSocket connection to blockchain + const provider = new WsProvider(nodeUrl) + this.api = await ApiPromise.create({ provider }) + + // === SETUP ACCOUNT MANAGEMENT === + // Create keyring for managing accounts + this.keyring = new Keyring({ type: 'sr25519' }) + + // === INITIALIZE COMPONENTS === + // Create transaction sender and block monitor + this.transactionSender = new TransactionSender(this.api, this.keyring) + this.blockMonitor = new BlockMonitor() + + // === SETUP TRANSACTION SENDER === + // Initialize Alice account and get starting nonce + await this.transactionSender.initialize() + + console.log('βœ… Connected to blockchain') + } + + // === START MONITORING === + // Start both block monitoring and transaction sending + async start(options) { + // Connect to blockchain first + await this.initialize(options.node) + + this.isRunning = true + + // === START BLOCK MONITORING === + // Begin monitoring new blocks for TPS calculation + this.blockMonitor.startMonitoring(this.api) + + // === START TRANSACTION SENDING (IF REQUESTED) === + // Only send transactions if TPS > 0 + if (options.tps > 0) { + await this.transactionSender.startSending(options.tps) + } + + console.log('\n⌨️ Press Ctrl+C to stop\n') + + // === SETUP GRACEFUL SHUTDOWN === + // Handle Ctrl+C to stop cleanly + process.on('SIGINT', () => { + this.stop() + }) + } + + // === STOP MONITORING === + // Stop all components and show final statistics + stop() { + console.log('\nπŸ‘‹ Stopping TPS Monitor...') + this.isRunning = false + + // === STOP TRANSACTION SENDING === + if (this.transactionSender) { + this.transactionSender.stop() + } + + // === DISPLAY FINAL STATISTICS === + if (this.blockMonitor) { + const stats = this.blockMonitor.getStats() + console.log(`\nπŸ“Š Final Stats:`) + console.log(` Blocks monitored: ${stats.blockCount}`) + console.log(` Total transactions: ${stats.totalTx}`) + console.log(` Average TPS: ${stats.avgTPS.toFixed(1)}`) + console.log(` Runtime: ${stats.runtime}s`) + } + + // Exit the program + process.exit(0) + } + + // === GET CURRENT STATISTICS === + // Return current monitoring stats + getStats() { + if (!this.blockMonitor) return null + + // Get block monitoring stats + const stats = this.blockMonitor.getStats() + + // Add transaction sending stats if available + if (this.transactionSender) { + stats.sentTx = this.transactionSender.getTxCount() + } + + return stats + } +} \ No newline at end of file diff --git a/tps-monitoring/src/modules/TransactionSender.js b/tps-monitoring/src/modules/TransactionSender.js new file mode 100644 index 0000000..5288918 --- /dev/null +++ b/tps-monitoring/src/modules/TransactionSender.js @@ -0,0 +1,106 @@ +// === TRANSACTION SENDER CLASS === +// Handles sending transactions to blockchain with proper nonce management +export class TransactionSender { + constructor(api, keyring) { + this.api = api // Blockchain API connection + this.keyring = keyring // Keyring for account management + this.alice = null // Alice account (sender) + this.isRunning = false // Flag to control transaction sending + this.txCount = 0 // Counter of sent transactions + this.nonce = null // Current nonce for transactions + } + + // === SETUP ALICE ACCOUNT === + // Get Alice account and initial nonce from blockchain + async initialize() { + // Create Alice account from seed + this.alice = this.keyring.addFromUri('//Alice') + + // Get current nonce from blockchain (starting point) + this.nonce = await this.api.rpc.system.accountNextIndex(this.alice.address) + + console.log('πŸ‘€ Using Alice account:', this.alice.address.substring(0, 20) + '...') + console.log(`πŸ”’ Starting nonce: ${this.nonce.toNumber()}`) + } + + // === START SENDING TRANSACTIONS === + // Send transactions at specified TPS rate + async startSending(tpsTarget = 10) { + console.log(`πŸ”„ Starting to send ${tpsTarget} TPS (Alice β†’ Alice transfers)`) + + // Set running flag and reset counter + this.isRunning = true + this.txCount = 0 + + // Calculate delay between transactions (milliseconds) + const intervalMs = 1000 / tpsTarget + + // === RECURSIVE TRANSACTION SENDING FUNCTION === + const sendTx = async () => { + // Stop if not running + if (!this.isRunning) return + + try { + // === CREATE AND SEND TRANSACTION === + // Create balance transfer: Alice -> Alice (1 unit) + const transfer = this.api.tx.balances.transferKeepAlive(this.alice.address, 1) + + // Send transaction with current nonce + await transfer.signAndSend(this.alice, { nonce: this.nonce }) + + // === UPDATE COUNTERS === + this.nonce++ // Increment nonce for next transaction + this.txCount++ // Count sent transactions + + // Log progress every 10 transactions + if (this.txCount % 10 === 0) { + console.log(`πŸ“€ Sent ${this.txCount} transactions (nonce: ${this.getCurrentNonce()})`) + } + + } catch (error) { + console.error('❌ Transfer failed:', error.message) + + // === NONCE ERROR RECOVERY === + // If nonce error occurs, get fresh nonce from blockchain + if (error.message.includes('nonce') || error.message.includes('outdated')) { + try { + this.nonce = await this.api.rpc.system.accountNextIndex(this.alice.address) + console.log(`πŸ”„ Updated nonce to: ${this.getCurrentNonce()}`) + } catch (nonceError) { + console.error('❌ Failed to update nonce:', nonceError.message) + } + } + } + + // === SCHEDULE NEXT TRANSACTION === + // Wait specified interval then send next transaction + if (this.isRunning) { + setTimeout(sendTx, intervalMs) + } + } + + // Start the transaction sending loop + sendTx() + } + + // === STOP SENDING === + // Stop transaction sending and show final stats + stop() { + this.isRunning = false + console.log(`πŸ“€ Stopped sending. Total sent: ${this.txCount} transactions`) + console.log(`πŸ”’ Final nonce: ${this.getCurrentNonce() || 'N/A'}`) + } + + // === GET TRANSACTION COUNT === + getTxCount() { + return this.txCount + } + + // === GET CURRENT NONCE === + // Handle both BigNumber and regular number types + getCurrentNonce() { + if (!this.nonce) return null + // Convert BigNumber to regular number if needed + return typeof this.nonce.toNumber === 'function' ? this.nonce.toNumber() : this.nonce + } +} \ No newline at end of file diff --git a/tps-monitoring/sub-flood-script/index.ts b/tps-monitoring/sub-flood-script/index.ts new file mode 100644 index 0000000..f765079 --- /dev/null +++ b/tps-monitoring/sub-flood-script/index.ts @@ -0,0 +1,301 @@ +// === SUB-FLOOD LOAD TESTING SCRIPT === +// Advanced multi-user blockchain load testing tool +import { ApiPromise, WsProvider } from '@polkadot/api' +import { Keyring } from '@polkadot/keyring' +import { cryptoWaitReady } from '@polkadot/util-crypto' +import minimist from 'minimist' + +// === GENERATE USER SEED === +// Create unique seed for each user account (user0000, user0001, etc.) +function seedFromNum(seed: number): string { + return '//user//' + ("0000" + seed).slice(-4); +} + +// === GET BLOCK STATISTICS === +// Extract transaction count and timestamp from a blockchain block +async function getBlockStats(api: ApiPromise, hash?: any): Promise { + // Get block data (current block if no hash provided) + const signedBlock = hash ? await api.rpc.chain.getBlock(hash) : await api.rpc.chain.getBlock(); + + // === EXTRACT TIMESTAMP FROM BLOCK === + // Find timestamp extrinsic in block + let timestamp = signedBlock.block.extrinsics.find( + ({ method: { method, section } }: any) => section === 'timestamp' && method === 'set' + )!.method.args[0].toString(); + + // Convert timestamp to Date object + let date = new Date(+timestamp); + + // Return block statistics + return { + date, // Block creation time + transactions: signedBlock.block.extrinsics.length, // Number of transactions + parent: signedBlock.block.header.parentHash, // Previous block hash + blockNumber: signedBlock.block.header.number, // Block number + } +} + +// === MAIN LOAD TESTING FUNCTION === +async function run() { + // === PARSE COMMAND LINE ARGUMENTS === + var argv = minimist(process.argv.slice(2)); + + // Load testing configuration with defaults + let TOTAL_TRANSACTIONS = argv.total_transactions ? argv.total_transactions : 25000; // Total transactions to send + let TPS = argv.scale ? argv.scale : 100; // Target transactions per second + let TOTAL_THREADS = argv.total_threads ? argv.total_threads : 10; // Number of parallel threads + if (TPS < TOTAL_THREADS) TOTAL_THREADS = TPS; // Ensure threads don't exceed TPS + let TOTAL_USERS = TPS; // One user per TPS target + let USERS_PER_THREAD = Math.ceil(TOTAL_USERS / TOTAL_THREADS); // Users distributed across threads + let TOTAL_BATCHES = Math.ceil(TOTAL_TRANSACTIONS / TPS); // Number of batches needed + let TRANSACTION_PER_BATCH = Math.ceil(TPS / TOTAL_THREADS); // Transactions per batch per thread + let WS_URL = argv.url ? argv.url : "ws://localhost:9944"; // Blockchain node URL + let TOKENS_TO_SEND = 1; // Amount to transfer (1 unit) + let MEASURE_FINALISATION = argv.finalization ? argv.finalization : false; // Track finalization + let FINALISATION_TIMEOUT = argv.finalization_timeout ? argv.finalization_timeout : 20000; // 20 seconds + let FINALISATION_ATTEMPTS = argv.finalization_attempts ? argv.finalization_attempts : 5; + + console.log('πŸš€ Starting Sub-Flood with full functionality...') + console.log('πŸ“‘ Connecting to:', WS_URL) + + // === INITIALIZE BLOCKCHAIN CONNECTION === + // Wait for cryptographic functions to be ready + await cryptoWaitReady() + + // Connect to blockchain node via WebSocket + const provider = new WsProvider(WS_URL) + const api = await ApiPromise.create({ provider }) + + // Setup account keyring for sr25519 signature scheme + const keyring = new Keyring({ type: 'sr25519' }) + + // === PRE-FETCH NONCES FOR ALL USER ACCOUNTS === + // This prevents nonce conflicts during batch sending + let nonces: number[] = []; + console.log("Fetching nonces for accounts..."); + for (let i = 0; i <= TOTAL_USERS; i++) { + // Generate user seed and create account + let stringSeed = seedFromNum(i); + let keys = keyring.addFromUri(stringSeed); + + // Get current nonce from blockchain for this account + let accountInfo = await api.query.system.account(keys.address) as any; + let nonce = accountInfo.nonce.toNumber(); + nonces.push(nonce); + } + console.log("All nonces fetched!"); + + // === FUND ALL USER ACCOUNTS FROM ALICE === + // Users need funds to send transactions + console.log("Endowing all users from Alice account..."); + let aliceKeyPair = keyring.addFromUri("//Alice"); // Alice account (has funds) + let aliceNonce = await api.rpc.system.accountNextIndex(aliceKeyPair.address); // Alice's nonce + let keyPairs = new Map(); // Store all user keypairs + console.log("Alice nonce is " + aliceNonce.toNumber()); + + let finalized_transactions = 0; // Counter for finalized funding transactions + + // === SEND FUNDS TO EACH USER === + for (let seed = 0; seed <= TOTAL_USERS; seed++) { + // Create user account + let keypair = keyring.addFromUri(seedFromNum(seed)); + keyPairs.set(seed, keypair); + + // === CALCULATE FUNDING AMOUNT === + // Must be greater than existential deposit to keep account alive + let existentialDeposit = (api.consts.balances.existentialDeposit as any).toNumber(); + let transfer = api.tx.balances.transferKeepAlive(keypair.address, BigInt(existentialDeposit) * 10n); + + let receiverSeed = seedFromNum(seed); + console.log(`Alice -> ${receiverSeed} (${keypair.address})`); + + // === SEND FUNDING TRANSACTION === + await transfer.signAndSend(aliceKeyPair, { + nonce: aliceNonce as any, // Use Alice's current nonce + tip: 1 // Small tip for priority + }, ({ status }) => { + // Track when transaction is finalized + if (status.isFinalized) { + finalized_transactions++; + } + }); + + // === UPDATE ALICE'S NONCE === + // Get fresh nonce for next funding transaction + aliceNonce = await api.rpc.system.accountNextIndex(aliceKeyPair.address); + } + console.log("All users endowed from Alice account!"); + + // === WAIT FOR FUNDING FINALIZATION === + console.log("Wait for transactions finalisation"); + await new Promise(r => setTimeout(r, FINALISATION_TIMEOUT)); + console.log(`Finalized transactions ${finalized_transactions}`); + + // === VERIFY ALL FUNDING COMPLETED === + if (finalized_transactions < TOTAL_USERS + 1) { + throw Error(`Not all transactions finalized`); + } + + // === SETUP LOAD TESTING === + console.log(`Starting to send ${TOTAL_TRANSACTIONS} transactions across ${TOTAL_THREADS} threads...`); + let nextTime = new Date().getTime(); // Next batch send time + let initialTime = new Date(); // Test start time + + // === SHARED MEMORY FOR THREAD COORDINATION === + // Track finalization across multiple threads + const finalisationTime = new Uint32Array(new SharedArrayBuffer(Uint32Array.BYTES_PER_ELEMENT)); + finalisationTime[0] = 0; + const finalisedTxs = new Uint16Array(new SharedArrayBuffer(Uint16Array.BYTES_PER_ELEMENT)); + finalisedTxs[0] = 0; + + let txCounter = 0; // Total transactions sent + + // === MAIN LOAD TESTING LOOP === + // Send transactions in batches, one batch per second + for (var batchNo = 0; batchNo < TOTAL_BATCHES; batchNo++) { + // === TIMING CONTROL === + // Wait until it's time to send next batch (1 second intervals) + while (new Date().getTime() < nextTime) { + await new Promise(r => setTimeout(r, 5)); + } + nextTime = nextTime + 1000; // Schedule next batch 1 second later + + var errors = []; + console.log(`Starting batch #${batchNo}`); + let batchPromises = new Array>(); + + // === CREATE TRANSACTIONS FOR THIS BATCH === + // Distribute transactions across threads + for (let threadNo = 0; threadNo < TOTAL_THREADS; threadNo++) { + for (let transactionNo = 0; transactionNo < TRANSACTION_PER_BATCH; transactionNo++) { + // === CALCULATE USER FOR THIS TRANSACTION === + let userNo = threadNo * USERS_PER_THREAD + transactionNo; + if (userNo >= TOTAL_USERS) break; // Don't exceed user count + if (!keyPairs.has(userNo)) continue; // Skip if user doesn't exist + + let senderKeyPair = keyPairs.get(userNo); // Get user's keypair + + // === CREATE TRANSACTION PROMISE === + // Each transaction runs asynchronously + batchPromises.push( + new Promise(async resolve => { + try { + // === GET CURRENT NONCE FOR THIS USER === + let currentNonce = nonces[userNo]; + + // === CREATE TRANSFER TRANSACTION === + // Send 1 token back to Alice + let transfer = api.tx.balances.transferKeepAlive(aliceKeyPair.address, BigInt(TOKENS_TO_SEND)); + + // === SEND TRANSACTION === + await transfer.signAndSend(senderKeyPair, { + nonce: currentNonce, // Use pre-fetched nonce + tip: 1 // Small tip for priority + }, ({ status }) => { + // === UPDATE NONCE AFTER SENDING === + // Increment nonce for this user + nonces[userNo]++; + + // === TRACK FINALIZATION === + if (status.isFinalized) { + Atomics.add(finalisedTxs, 0, 1); // Atomic increment + let finalisationTimeCurrent = new Date().getTime() - initialTime.getTime(); + if (finalisationTimeCurrent > Atomics.load(finalisationTime, 0)) { + Atomics.store(finalisationTime, 0, finalisationTimeCurrent); + } + } + }); + txCounter++; + resolve(1); // Success + } catch (err: any) { + errors.push(err); + resolve(-1); // Error + } + }) + ); + } + } + + // === WAIT FOR ALL TRANSACTIONS IN BATCH TO COMPLETE === + await Promise.all(batchPromises); + + // === LOG ERRORS IF ANY === + if (errors.length > 0) { + console.log(`${errors.length}/${TRANSACTION_PER_BATCH} errors sending transactions`); + } + } + + // === CALCULATE TEST RESULTS === + let finalTime = new Date(); + let diff = finalTime.getTime() - initialTime.getTime(); // Total test duration + + // === COUNT TRANSACTIONS IN BLOCKS === + // Walk backwards through blockchain to count our transactions + var total_transactions = 0; + var total_blocks = 0; + var latest_block = await getBlockStats(api); + console.log(`latest block: ${latest_block.date}`); + console.log(`initial time: ${initialTime}`); + + let prunedFlag = false; + + // === TRAVERSE BLOCKS DURING TEST PERIOD === + for (; latest_block.date > initialTime; ) { + try { + // Get previous block + latest_block = await getBlockStats(api, latest_block.parent); + } catch(err: any) { + console.log("Cannot retrieve block info with error: " + err.toString()); + console.log("Most probably the state is pruned already, stopping"); + prunedFlag = true; + break; + } + + // === COUNT TRANSACTIONS IN BLOCKS DURING TEST === + if (latest_block.date < finalTime) { + console.log(`block number ${latest_block.blockNumber}: ${latest_block.transactions} transactions`); + total_transactions += latest_block.transactions; + total_blocks++; + } + } + + // === CALCULATE FINAL TPS === + let tps = (total_transactions * 1000) / diff; + console.log(`TPS from ${total_blocks} blocks: ${tps}`); + + // === WAIT FOR TRANSACTION FINALIZATION (OPTIONAL) === + if (MEASURE_FINALISATION && !prunedFlag) { + let break_condition = false; + let attempt = 0; + + // === WAIT FOR ALL TRANSACTIONS TO FINALIZE === + while (!break_condition) { + console.log(`Wait ${FINALISATION_TIMEOUT} ms for transactions finalisation, attempt ${attempt} out of ${FINALISATION_ATTEMPTS}`); + await new Promise(r => setTimeout(r, FINALISATION_TIMEOUT)); + + // === CHECK FINALIZATION PROGRESS === + if (Atomics.load(finalisedTxs, 0) < TOTAL_TRANSACTIONS) { + if (attempt == FINALISATION_ATTEMPTS) { + // Time limit reached + break_condition = true; + } else { + attempt++; + } + } else { + // All transactions finalized + break_condition = true; + } + } + console.log(`Finalized ${Atomics.load(finalisedTxs, 0)} out of ${TOTAL_TRANSACTIONS} transactions, finalization time was ${Atomics.load(finalisationTime, 0)}`); + } +} + +// === RUN THE LOAD TEST === +// Execute main function with error handling +run().then(function() { + console.log("Done"); + process.exit(0); +}).catch(function(err) { + console.log("Error: " + err.toString()); + process.exit(1); +}); diff --git a/tps-monitoring/tsconfig.json b/tps-monitoring/tsconfig.json new file mode 100644 index 0000000..18c8939 --- /dev/null +++ b/tps-monitoring/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ES2020", + "moduleResolution": "node", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist", + "rootDir": "./", + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": [ + "sub-flood-script/**/*", + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file