# Project export: CryptoWise: Intelligent Trading and Insights Platform

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 11.0
- Tagline: Revolutionize your cryptocurrency trading experience with our platform that combines real-time data visualization, intelligent query handling, and automated trading strategies.
- Devpost: https://devpost.com/software/cryptowise-intelligent-trading-and-insights-platform
- GitHub: https://github.com/sohail-coder/kraken-analytics-backend
- Demo: https://docs.google.com/presentation/d/1TDGFxihhECPf01kBEuqnaqiSKVZF6DWpon8X1Au-7BY/edit#slide=id.gc6f90357f_0_47
- Video: https://www.youtube.com/embed/OMBlUbdAGMg?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Singlestore: Best Real-Time App)
- Team: 3 GitHub contributor(s) — sohail shaik (10 commits), Varshith Pabbisetty (5 commits), mahendra_chittupolu (1 commits)

## Devpost submission (written by the team)

### Inspiration

The inspiration for CryptoWise stemmed from the dynamic and often unpredictable nature of the cryptocurrency market. We recognized the need for a comprehensive platform that not only provides real-time data but also offers intelligent insights and automated trading capabilities. Our goal was to empower traders, researchers, and enthusiasts with tools that enhance decision-making and streamline trading processes.

### What it does

CryptoWise is an all-in-one platform designed to revolutionize the cryptocurrency trading experience. It offers: Real-Time Data Visualization: Users can view up-to-the-minute cryptocurrency values and trends through interactive charts. Intelligent Query Handling: A chatbot integrated with advanced LLM technology answers queries related to real-time, historical, and generic cryptocurrency data. Automated Trading: The platform includes a predictive model that suggests Buy, Sell, or Hold actions based on real-time data analysis, with the capability to execute trades automatically when conditions are met.

### How we built it

We built CryptoWise using a combination of cutting-edge technologies: Data Acquisition: Integrated WebSocket APIs to fetch real-time cryptocurrency data, which is stored in SingleStore Database for fast retrieval and analytics. Visualization: Used SingleStore's analytics API to create dynamic charts that display changing market trends. Chatbot Integration: Leveraged the GROQ API and OpenAI 3.5 Turbo model to develop a chatbot capable of handling complex queries about cryptocurrency. Predictive Modeling: Developed a model trained on real-time and historical data from Kraken's WebSocket and OHLC APIs to make trading decisions. Automated Trading with Smart Contracts: Utilized smart contracts for executing trades securely. MetaMask authentication ensures secure access to users' wallets using private keys. Initially planned integration with Fetch.ai's agent-based tools faced challenges due to technical integration complexity and resource allocation constraints.

### Challenges we ran into

Throughout the development of CryptoWise, we faced several challenges: Data Integration: Ensuring seamless integration of real-time data from multiple sources was complex and required robust solutions. Model Training: Continuously training the predictive model on diverse datasets to maintain accuracy was demanding. User Interface Design: Creating an intuitive and user-friendly interface that effectively displays complex data was challenging but crucial for user engagement. Agent-Based Trading Integration: The complexity of integrating Fetch.ai's agents with our existing infrastructure proved challenging, along with resource limitations.

### Accomplishments we're proud of

We are proud of several key accomplishments: Seamless Data Flow: Successfully integrated various data sources into a cohesive platform that delivers real-time insights. Advanced AI Integration: Implemented a sophisticated chatbot that enhances user interaction with accurate and relevant responses. Automated Trading Feature: Developed an automated trading system using smart contracts that executes trades based on predictive analytics, offering users a competitive edge.

### What we learned

The development of CryptoWise taught us invaluable lessons: Importance of Data Accuracy: Real-time accuracy is critical in financial applications, necessitating rigorous testing and validation. AI Capabilities: Leveraging AI for query handling and decision-making can significantly enhance user experience and platform functionality. User-Centric Design: Designing with the user in mind is essential for creating an engaging and effective interface.

### What's next

Looking ahead, we plan to expand CryptoWise's capabilities by: Enhanced Predictive Analytics: Further refining our models to improve prediction accuracy and broaden the range of supported cryptocurrencies. User Personalization: Introducing features that allow users to customize their experience based on individual preferences and trading strategies. Community Engagement: Building a community around CryptoWise where users can share insights, strategies, and feedback to continuously improve the platform. CryptoWise aims to set a new standard in cryptocurrency trading by combining powerful analytics with intuitive design, making it an indispensable tool for anyone involved in the crypto market. Overview This section provides details about the various API endpoints used in the CryptoWise platform, categorized according to their role in the architecture. Automated Trading Automated Purchase Endpoint URL: http://146.190.123.50:4000/api/simulate-purchase Purpose: Simulates the purchase of cryptocurrency based on model predictions. URL: http://146.190.123.50:4000/api/simulate-purchase Purpose: Simulates the purchase of cryptocurrency based on model predictions. Query Handling Query to LLM URL: http://146.190.123.50:4000/query Purpose: Sends user queries to the LLM for categorization and response generation. URL: http://146.190.123.50:4000/query Purpose: Sends user queries to the LLM for categorization and response generation. Data Acquisition Kraken WebSocket API URL: wss://ws.kraken.com/ Purpose: Provides real-time cryptocurrency data for live updates. Kraken WebSocket API URL: wss://ws.kraken.com/ Purpose: Provides real-time cryptocurrency data for live updates. Historical Data URL: https://api.kraken.com/0/public/OHLC Purpose: Fetches historical price data for cryptocurrencies. Historical Data URL: https://api.kraken.com/0/public/OHLC Purpose: Fetches historical price data for cryptocurrencies. Machine Learning ML Prediction API URL: http://146.190.123.50:5000/prediction Purpose: Provides predictions on Buy, Sell, or Hold actions based on real-time data analysis. URL: http://146.190.123.50:5000/prediction Purpose: Provides predictions on Buy, Sell, or Hold actions based on real-time data analysis. Analytics Bollinger Bands URL: http://146.190.123.50:4000/api/analytics/bollinger-bands?coin=XBT&period=20&stdDev=2 Purpose: Calculates Bollinger Bands for specified cryptocurrency and period. Bollinger Bands URL: http://146.190.123.50:4000/api/analytics/bollinger-bands?coin=XBT&period=20&stdDev=2 Purpose: Calculates Bollinger Bands for specified cryptocurrency and period. Historical Volatility URL: http://146.190.123.50:4000/api/analytics/historical_volatility?coin=XBT Purpose: Computes historical volatility for the specified cryptocurrency. Historical Volatility URL: http://146.190.123.50:4000/api/analytics/historical_volatility?coin=XBT Purpose: Computes historical volatility for the specified cryptocurrency. Stats URL: http://146.190.123.50:4000/api/analytics/stats Purpose: Provides statistical analysis of cryptocurrency data. Stats URL: http://146.190.123.50:4000/api/analytics/stats Purpose: Provides statistical analysis of cryptocurrency data. Candle Data URL: http://146.190.123.50:4000/api/analytics/get_candle_data?coin=XBT Purpose: Retrieves candlestick data for detailed market analysis. Candle Data URL: http://146.190.123.50:4000/api/analytics/get_candle_data?coin=XBT Purpose: Retrieves candlestick data for detailed market analysis.

## README (from the GitHub repository)

# Sample Hardhat Project

This project demonstrates a basic Hardhat use case. It comes with a sample contract, a test for that contract, and a Hardhat Ignition module that deploys that contract.

Try running some of the following tasks:

```shell
npx hardhat help
npx hardhat test
REPORT_GAS=true npx hardhat test
npx hardhat node
npx hardhat ignition deploy ./ignition/modules/Lock.js
```


## Detected evidence (automated analysis)

Indexed codebase: 19 recognized source files, 58 KB.
- Express (technology) — detected in the code
- JavaScript (language) — detected in the code
- OpenAI (technology) — detected in the code
- Solidity (language) — detected in the code
- SQL (language) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- Python (language) — claimed on Devpost, not found in the code
- React (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (24 of 24)

```
.DS_Store
.gitignore
certs/singlestore_bundle.pem
contracts/Lock.sol
contracts/PurchaseSimulator.sol
hardhat.config.js
ignition/modules/Lock.js
nodemon.json
package.json
README.md
scripts/deploy-mocketh.js
scripts/deploy-purchase-simulator.js
src/config/dbConfig.js
src/controllers/analyticsController.js
src/middleware/errorHandler.js
src/models/tickerModel.js
src/routes/analyticsRoutes.js
src/schema.sql
src/server.js
src/services/groqService.js
src/services/singlestoreService.js
src/services/websocketService.js
src/utils/logger.js
test/Lock.js
```

### Dependencies

- package.json: @crypto-act/kraken-websocket-library@^1.3.0, @nomicfoundation/hardhat-toolbox@^5.0.0, @nomiclabs/hardhat-etherscan@^3.1.8, @sanity/client@^6.22.2, axios@^1.7.7, cors@^2.8.5, dotenv@^16.4.5, express@^4.21.1, groq-sdk@^0.7.0, mysql2@^3.11.3, node-fetch@^2.7.0, nodemon@^3.1.7, openai@^4.68.1, socket.io@^4.8.0, winston@^3.15.0, ws@^8.18.0

### Recent commits (newest first)

- Working
- purchase-simulate
- Merge pull request #4 from sohail-coder/varshith/GroqLLM2
- Kraken Accuracy buy
- Merge pull request #3 from sohail-coder/varshith/GroqLLM2
- Working code
- Merge branch 'main' into varshith/GroqLLM2
- Pull
- Merge pull request #2 from sohail-coder/setup-hardhat
- Added contract
- SmartContract
- LLM Integration
- Merge pull request #1 from sohail-coder/kraken-backend
- backend
- Update code
- first commit

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "kraken-analytics-backend",
  "version": "1.0.0",
  "description": "",
  "main": "./src/server.js",
  "scripts": {
    "start": "node src/server.js",
    "dev": "nodemon src/server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@crypto-act/kraken-websocket-library": "^1.3.0",
    "@sanity/client": "^6.22.2",
    "axios": "^1.7.7",
    "cors": "^2.8.5",
    "express": "^4.21.1",
    "groq-sdk": "^0.7.0",
    "mysql2": "^3.11.3",
    "node-fetch": "^2.7.0",
    "openai": "^4.68.1",
    "socket.io": "^4.8.0",
    "winston": "^3.15.0",
    "ws": "^8.18.0"
  },
  "devDependencies": {
    "@nomicfoundation/hardhat-toolbox": "^5.0.0",
    "@nomiclabs/hardhat-etherscan": "^3.1.8",
    "dotenv": "^16.4.5",
    "nodemon": "^3.1.7"
  }
}

```

### src/server.js

```javascript
const express = require("express");
const http = require("http");
const socketIo = require("socket.io");
const cors = require("cors");
const connectToKrakenWebSocket = require("./services/websocketService");
const analyticsRoutes = require("./routes/analyticsRoutes");
const singlestoreService = require("./services/singlestoreService");
const logger = require("./utils/logger");
const errorHandler = require("./middleware/errorHandler");
const { ethers } = require("ethers");
const axios = require("axios");

require("dotenv").config();
const { handleQuery } = require("./services/groqService");

const app = express();
const port = process.env.PORT || 5000;
const server = http.createServer(app);
const io = socketIo(server, {
  cors: {
    origin: "*", // Allow all origins
    methods: ["GET", "POST"],
  },
});
app.use(express.json());

const PORT = process.env.SERVER_PORT || 4000;
const ANALYTICS_INTERVAL = process.env.ANALYTICS_INTERVAL || 1000; // Allow customization via ENV

// Middleware
app.use(cors());
app.use(express.json());

// API Routes
app.use("/api/analytics", analyticsRoutes);

app.post("/query", async (req, res) => {
  try {
    const userQuery = req.body.query;
    const response = await handleQuery(userQuery);
    res.json({ response });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000, () => {
  console.log("Server is running on port 3000");
});

// Error Handling Middleware
app.use(errorHandler);

// Socket.IO Connections
io.on("connection", (socket) => {
  logger.info(`Client connected: ${socket.id}`);

  socket.on("disconnect", () => {
    logger.info(`Client disconnected: ${socket.id}`);
  });
});

/**
 * Function to emit analytics periodically
 */
const emitAnalytics = async () => {
  try {
    // Fetch distinct coins from the ticker table
    const coinsRows = await singlestoreService.execute(`
      SELECT DISTINCT coin FROM ticker
    `);

    if (!Array.isArray(coinsRows)) {
      throw new TypeError("coinsRows is not an array");
    }

    const coins = coinsRows.map((row) => row.coin);

    if (coins.length === 0) {
      logger.info("No coins found in the ticker table.");
      return;
    }

    // Prepare analytics data for each coin
    const analyticsPromises = coins.map(async (coin) => {
      try {
        const averagePrice = await singlestoreService.getAverageClosePrice(
          coin
        );
        const totalVolume = await singlestoreService.getTotalVolume(coin);
        const bollingerBands = await singlestoreService.getBollingerBands(
          coin,
          20,
          2
        );
        const movingAverage = await singlestoreService.getMovingAverage(
          coin,
          20
        );
        const averageTrueRange = await singlestoreService.getAverageTrueRange(
          coin
        );

        return {
          coin,
          analytics: {
            average_close_price: averagePrice || null,
            total_volume: totalVolume || null,
            bollinger_bands: bollingerBands || null,
            moving_average: movingAverage || null,
            average_true_range: averageTrueRange || null,
          },
        };
      } catch (analyticsError) {
        logger.error(
          `Error calculating analytics for ${coin}:`,
          analyticsError
        );
        return null; // Return null if there’s an error for a particular coin
      }
    });

    const analyticsDataArray = (await Promise.all(analyticsPromises)).filter(
      (data) => data !== null
    ); // Filter out null results

    // Emit analytics data to all connected clients
    analyticsDataArray.forEach((data) => {
      io.emit("analyticsUpdate", data);
      // logger.info(`Emitted analytics for ${data.coin}`);
    });
  } catch (error) {
    logger.error("Error emitting analytics:", error);
  }
};
// Validate Environment Variables
if (!process.env.SEPOLIA_API_URL) {
  console.error("Error: SEPOLIA_API_URL is not defined in the .env file.");
  process.exit(1);
}

if (!process.env.PRIVATE_KEY) {
  console.error("Error: PRIVATE_KEY is not defined in the .env file.");
  process.exit(1);
}

// Smart Contract Details
const PURCHASE_SIMULATOR_ADDRESS = "0xFc60AB5e93b917395CBBb88AA5F14Ba60C50eB25"; // Replace with your deployed PurchaseSimulator contract address

// Minimal ABI to interact with PurchaseSimulator's buy function and PurchaseMade event
const PURCHASE_SIMULATOR_ABI = [
  "function buy(uint256 amount) external payable",
  "event PurchaseMade(address indexed buyer, uint256 amount)",
];

// Initialize Ethers Provider and Signer
const provider = new ethers.providers.JsonRpcProvider(
  process.env.SEPOLIA_API_URL
);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
const purchaseSimulator = new ethers.Contract(
  PURCHASE_SIMULATOR_ADDRESS,
  PURCHASE_SIMULATOR_ABI,
  wallet
);

// In-memory user balances (for simulation purposes)
// Note: In production, use a persistent database
const userBalances = {};
setInterval(async () => {
  try {
    console.log("Fetching new prediction...");

    const predictionResponse = await axios.post(
      "http://146.190.123.50:5000/prediction",
      {
        pair: "ETH/USD", // You can adjust this or make it dynamic based on users
      }
    );

    const { processed_vectors, decision } = predictionResponse.data;

    // Store the latest prediction data globally
    latestPrediction = {
      processed_vectors,
      decision,
      timestamp: new Date(), // Keep a timestamp to know when it was fetched
    };

    const accuracy =
      (processed_vectors.reduce((a, b) => a + b, 0) /
        processed_vectors.length) *
      100;

    console.log(`Accuracy: ${accuracy}, Decision: ${decision}`);

    if (accuracy > 60) {
      try {
        const address = "0xcB85555a244000761D25be3b498FeC29B59a1F9B";

        // Validate Ethereum Address
        if (!address || !ethers.utils.isAddress(address)) {
          console.error("Invalid Ethereum address.");
[truncated — 5100 more characters]
```

### hardhat.config.js

```javascript
// hardhat.config.js

require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();

module.exports = {
  solidity: "0.8.27", // Ensure this matches your contract's pragma
  networks: {
    sepolia: {
      url: process.env.SEPOLIA_API_URL || "",
      accounts: process.env.PRIVATE_KEY ? [`0x${process.env.PRIVATE_KEY}`] : [],
    },
  },
  etherscan: {
    apiKey: process.env.ETHERSCAN_API_KEY || "",
  },
};

```

### contracts/PurchaseSimulator.sol

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

contract PurchaseSimulator {
    event PurchaseMade(address indexed buyer, uint256 amount);

    // Function to simulate a purchase
    function buy(uint256 amount) external payable {
        require(msg.value >= 0.001 ether, "Insufficient fee to simulate purchase");

        // Emit an event to log the purchase
        emit PurchaseMade(msg.sender, amount);

        // Optionally, send back the fee to the buyer
        payable(msg.sender).transfer(msg.value);
    }
}

```

### scripts/deploy-mocketh.js

```javascript
// scripts/deploy-mocketh.js

const hre = require("hardhat");

async function main() {
  // Compile contracts if not already compiled
  await hre.run("compile");

  // Get the ContractFactory for MockETH
  const MockETH = await hre.ethers.getContractFactory("MockETH");

  // Deploy the contract
  const mockETH = await MockETH.deploy();

  // Wait for deployment to be mined (Ethers v6)
  await mockETH.waitForDeployment();

  // Get the deployed contract's address
  console.log("MockETH deployed to:", mockETH.target);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error("Error deploying MockETH:", error);
    process.exit(1);
  });

```

### scripts/deploy-purchase-simulator.js

```javascript
// scripts/deploy-purchase-simulator.js

const hre = require("hardhat");

async function main() {
  // Compile contracts if not already compiled
  await hre.run('compile');

  // Get the ContractFactory
  const PurchaseSimulator = await hre.ethers.getContractFactory("PurchaseSimulator");

  // Deploy the contract
  const purchaseSimulator = await PurchaseSimulator.deploy();

  // Wait for deployment to be mined (Ethers v6)
  await purchaseSimulator.waitForDeployment();

  // Get the deployed contract's address
  console.log("PurchaseSimulator deployed to:", purchaseSimulator.target);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error("Error deploying PurchaseSimulator:", error);
    process.exit(1);
  });

```

### contracts/Lock.sol

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;

// Uncomment this line to use console.log
// import "hardhat/console.sol";

contract Lock {
    uint public unlockTime;
    address payable public owner;

    event Withdrawal(uint amount, uint when);

    constructor(uint _unlockTime) payable {
        require(
            block.timestamp < _unlockTime,
            "Unlock time should be in the future"
        );

        unlockTime = _unlockTime;
        owner = payable(msg.sender);
    }

    function withdraw() public {
        // Uncomment this line, and the import of "hardhat/console.sol", to print a log in your terminal
        // console.log("Unlock time is %o and block timestamp is %o", unlockTime, block.timestamp);

        require(block.timestamp >= unlockTime, "You can't withdraw yet");
        require(msg.sender == owner, "You aren't the owner");

        emit Withdrawal(address(this).balance, block.timestamp);

        owner.transfer(address(this).balance);
    }
}

```

### src/schema.sql

```sql
-- setup.sql

-- Create Database
CREATE DATABASE IF NOT EXISTS kraken_data;

-- Use the Database
USE kraken_data;
DROP TABLE IF EXISTS ticker;


-- Create Ticker Table
CREATE TABLE IF NOT EXISTS ticker (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  coin VARCHAR(10) NOT NULL, -- e.g., 'BTC', 'SOL'
  pair VARCHAR(20) NOT NULL, -- e.g., 'BTC/USD'
  channel_name VARCHAR(50),
  best_ask_price DECIMAL(18,8),
  best_ask_whole_lot_volume BIGINT,
  best_ask_lot_volume DECIMAL(18,8),
  best_bid_price DECIMAL(18,8),
  best_bid_whole_lot_volume BIGINT,
  best_bid_lot_volume DECIMAL(18,8),
  close_price DECIMAL(18,8),
  close_lot_volume DECIMAL(18,8),
  volume_today DECIMAL(18,8),
  volume_last_24h DECIMAL(18,8),
  vwap_today DECIMAL(18,8),
  vwap_last_24h DECIMAL(18,8),
  number_of_trades_today INT,
  number_of_trades_last_24h INT,
  low_price_today DECIMAL(18,8),
  low_price_last_24h DECIMAL(18,8),
  high_price_today DECIMAL(18,8),
  high_price_last_24h DECIMAL(18,8),
  open_price_today DECIMAL(18,8),
  open_price_last_24h DECIMAL(18,8),
  received_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_coin_received_at (coin, received_at) -- Composite Index
);

```

### test/Lock.js

```javascript
const {
  time,
  loadFixture,
} = require("@nomicfoundation/hardhat-toolbox/network-helpers");
const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs");
const { expect } = require("chai");

describe("Lock", function () {
  // We define a fixture to reuse the same setup in every test.
  // We use loadFixture to run this setup once, snapshot that state,
  // and reset Hardhat Network to that snapshot in every test.
  async function deployOneYearLockFixture() {
    const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;
    const ONE_GWEI = 1_000_000_000;

    const lockedAmount = ONE_GWEI;
    const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;

    // Contracts are deployed using the first signer/account by default
    const [owner, otherAccount] = await ethers.getSigners();

    const Lock = await ethers.getContractFactory("Lock");
    const lock = await Lock.deploy(unlockTime, { value: lockedAmount });

    return { lock, unlockTime, lockedAmount, owner, otherAccount };
  }

  describe("Deployment", function () {
    it("Should set the right unlockTime", async function () {
      const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture);

      expect(await lock.unlockTime()).to.equal(unlockTime);
    });

    it("Should set the right owner", async function () {
      const { lock, owner } = await loadFixture(deployOneYearLockFixture);

      expect(await lock.owner()).to.equal(owner.address);
    });

    it("Should receive and store the funds to lock", async function () {
      const { lock, lockedAmount } = await loadFixture(
        deployOneYearLockFixture
      );

      expect(await ethers.provider.getBalance(lock.target)).to.equal(
        lockedAmount
      );
    });

    it("Should fail if the unlockTime is not in the future", async function () {
      // We don't use the fixture here because we want a different deployment
      const latestTime = await time.latest();
      const Lock = await ethers.getContractFactory("Lock");
      await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWith(
        "Unlock time should be in the future"
      );
    });
  });

  describe("Withdrawals", function () {
    describe("Validations", function () {
      it("Should revert with the right error if called too soon", async function () {
        const { lock } = await loadFixture(deployOneYearLockFixture);

        await expect(lock.withdraw()).to.be.revertedWith(
          "You can't withdraw yet"
        );
      });

      it("Should revert with the right error if called from another account", async function () {
        const { lock, unlockTime, otherAccount } = await loadFixture(
          deployOneYearLockFixture
        );

        // We can increase the time in Hardhat Network
        await time.increaseTo(unlockTime);

        // We use lock.connect() to send a transaction from another account
        await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith(
          "You aren't the owner"
        );
      });

      it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () {
        const { lock, unlockTime } = await loadFixture(
          deployOneYearLockFixture
        );

        // Transactions are sent using the first signer by default
        await time.increaseTo(unlockTime);

        await expect(lock.withdraw()).not.to.be.reverted;
      });
    });

    describe("Events", function () {
      it("Should emit an event on withdrawals", async function () {
        const { lock, unlockTime, lockedAmount } = await loadFixture(
          deployOneYearLockFixture
        );

        await time.increaseTo(unlockTime);

        await expect(lock.withdraw())
          .to.emit(lock, "Withdrawal")
          .withArgs(lockedAmount, anyValue); // We accept any value as `when` arg
      });
    });

    describe("Transfers", function () {
      it("Should transfer the funds to the owner", async function () {
        const { lock, unlockTime, lockedAmount, owner } = await loadFixture(
          deployOneYearLockFixture
        );

        await time.increaseTo(unlockTime);

        await expect(lock.withdraw()).to.changeEtherBalances(
          [owner, lock],
          [lockedAmount, -lockedAmount]
        );
      });
    });
  });
});

```

### src/middleware/errorHandler.js

```javascript
// src/middleware/errorHandler.js

const logger = require('../utils/logger');

/**
 * Error handling middleware.
 * Sends a JSON response with the error message.
 */
const errorHandler = (err, req, res, next) => {
  logger.error(err.stack);
  res.status(500).json({ error: 'Internal Server Error' });
};

module.exports = errorHandler;

```

[9 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]