anton-abyzov

Blockchain & Web3 Development

"Blockchain and Web3 development with Solidity, Hardhat, Foundry, ethers.js, and smart contract security. Covers Ethereum, Layer 2 solutions, DeFi, NFTs, and decentralized storage. Activates for: blockchain, web3, solidity, smart contract, Ethereum, Hardhat, Foundry, ERC-20, ERC-721, NFT, DeFi, dapp, WalletConnect, IPFS, layer 2, gas optimization, reentrancy, OpenZeppelin."

anton-abyzov 144 18 Updated 3mo ago
GitHub

Install

npx skillscat add anton-abyzov/specweave/plugins-specweave-blockchain-skills-blockchain

Install via the SkillsCat registry.

SKILL.md

Blockchain & Web3 Development

Expert guidance for building production-grade decentralized applications, smart contracts, and Web3 integrations. This skill covers Ethereum ecosystem, alternative chains, security, and frontend integration.

Ethereum Smart Contract Development

Solidity Fundamentals

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/// @title Secure Vault
/// @notice A vault for depositing and withdrawing ERC-20 tokens
/// @dev Implements reentrancy protection and ownership controls
contract SecureVault is Ownable, ReentrancyGuard {
    mapping(address => mapping(address => uint256)) private balances;
    mapping(address => bool) public allowedTokens;

    event Deposited(address indexed user, address indexed token, uint256 amount);
    event Withdrawn(address indexed user, address indexed token, uint256 amount);
    event TokenAllowed(address indexed token, bool allowed);

    error TokenNotAllowed(address token);
    error InsufficientBalance(uint256 requested, uint256 available);
    error ZeroAmount();

    constructor() Ownable(msg.sender) {}

    /// @notice Deposit tokens into the vault
    /// @param token The ERC-20 token address
    /// @param amount The amount to deposit
    function deposit(address token, uint256 amount) external nonReentrant {
        if (amount == 0) revert ZeroAmount();
        if (!allowedTokens[token]) revert TokenNotAllowed(token);

        // Effects before interactions (CEI pattern)
        balances[msg.sender][token] += amount;

        // Interaction
        bool success = IERC20(token).transferFrom(msg.sender, address(this), amount);
        require(success, "Transfer failed");

        emit Deposited(msg.sender, token, amount);
    }

    /// @notice Withdraw tokens from the vault
    /// @param token The ERC-20 token address
    /// @param amount The amount to withdraw
    function withdraw(address token, uint256 amount) external nonReentrant {
        if (amount == 0) revert ZeroAmount();
        uint256 balance = balances[msg.sender][token];
        if (amount > balance) revert InsufficientBalance(amount, balance);

        // Effects before interactions (CEI pattern)
        balances[msg.sender][token] = balance - amount;

        // Interaction
        bool success = IERC20(token).transfer(msg.sender, amount);
        require(success, "Transfer failed");

        emit Withdrawn(msg.sender, token, amount);
    }

    /// @notice Get balance for a user and token
    function getBalance(address user, address token) external view returns (uint256) {
        return balances[user][token];
    }

    /// @notice Allow or disallow a token for deposits
    function setAllowedToken(address token, bool allowed) external onlyOwner {
        allowedTokens[token] = allowed;
        emit TokenAllowed(token, allowed);
    }
}

ERC Standards Reference

Standard Purpose Key Functions
ERC-20 Fungible tokens transfer, approve, transferFrom, balanceOf
ERC-721 NFTs (unique tokens) ownerOf, safeTransferFrom, tokenURI, approve
ERC-1155 Multi-token (fungible + NFT) balanceOf, safeTransferFrom, safeBatchTransferFrom
ERC-2981 NFT royalties royaltyInfo
ERC-4626 Tokenized vaults deposit, withdraw, convertToShares, convertToAssets
ERC-6551 Token-bound accounts NFTs that own assets

ERC-721 NFT Implementation

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Royalty.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

contract MyNFT is ERC721, ERC721URIStorage, ERC721Royalty, Ownable {
    uint256 private _nextTokenId;
    uint256 public constant MAX_SUPPLY = 10000;
    uint256 public mintPrice = 0.08 ether;
    string private _baseTokenURI;
    bool public mintingActive = false;

    error MintingNotActive();
    error MaxSupplyReached();
    error InsufficientPayment();
    error MaxPerWalletReached();

    mapping(address => uint256) public mintCount;
    uint256 public constant MAX_PER_WALLET = 5;

    constructor(
        string memory baseURI,
        address royaltyReceiver
    ) ERC721("MyNFT", "MNFT") Ownable(msg.sender) {
        _baseTokenURI = baseURI;
        // 5% royalty
        _setDefaultRoyalty(royaltyReceiver, 500);
    }

    function mint(uint256 quantity) external payable {
        if (!mintingActive) revert MintingNotActive();
        if (_nextTokenId + quantity > MAX_SUPPLY) revert MaxSupplyReached();
        if (msg.value < mintPrice * quantity) revert InsufficientPayment();
        if (mintCount[msg.sender] + quantity > MAX_PER_WALLET) revert MaxPerWalletReached();

        mintCount[msg.sender] += quantity;

        for (uint256 i = 0; i < quantity; i++) {
            uint256 tokenId = _nextTokenId++;
            _safeMint(msg.sender, tokenId);
        }
    }

    function tokenURI(uint256 tokenId)
        public view override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        _requireOwned(tokenId);
        return string(abi.encodePacked(_baseTokenURI, Strings.toString(tokenId), ".json"));
    }

    // Required overrides
    function supportsInterface(bytes4 interfaceId)
        public view override(ERC721, ERC721URIStorage, ERC721Royalty)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }

    function _update(address to, uint256 tokenId, address auth)
        internal override(ERC721)
        returns (address)
    {
        return super._update(to, tokenId, auth);
    }
}

Development Tools

Hardhat Setup and Configuration

// hardhat.config.ts
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import "@nomicfoundation/hardhat-verify";
import "hardhat-gas-reporter";
import "solidity-coverage";
import * as dotenv from "dotenv";

dotenv.config();

const config: HardhatUserConfig = {
  solidity: {
    version: "0.8.24",
    settings: {
      optimizer: { enabled: true, runs: 200 },
      viaIR: true, // Enable IR-based compilation for better optimization
    },
  },
  networks: {
    hardhat: {
      forking: {
        url: process.env.MAINNET_RPC_URL ?? "",
        blockNumber: 19_000_000, // Pin block for deterministic tests
      },
    },
    sepolia: {
      url: process.env.SEPOLIA_RPC_URL ?? "",
      accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
    },
    base: {
      url: "https://mainnet.base.org",
      accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
    },
  },
  etherscan: {
    apiKey: {
      mainnet: process.env.ETHERSCAN_API_KEY ?? "",
      sepolia: process.env.ETHERSCAN_API_KEY ?? "",
      base: process.env.BASESCAN_API_KEY ?? "",
    },
  },
  gasReporter: {
    enabled: process.env.REPORT_GAS === "true",
    currency: "USD",
    coinmarketcap: process.env.COINMARKETCAP_API_KEY,
  },
};

export default config;

Hardhat Testing

// test/SecureVault.test.ts
import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers";
import { expect } from "chai";
import hre from "hardhat";

describe("SecureVault", function () {
  async function deployVaultFixture() {
    const [owner, user1, user2] = await hre.ethers.getSigners();

    // Deploy mock ERC-20 token
    const MockToken = await hre.ethers.getContractFactory("MockERC20");
    const token = await MockToken.deploy("Mock", "MCK", hre.ethers.parseEther("1000000"));

    // Deploy vault
    const Vault = await hre.ethers.getContractFactory("SecureVault");
    const vault = await Vault.deploy();

    // Allow the token
    await vault.setAllowedToken(await token.getAddress(), true);

    // Transfer tokens to user1
    await token.transfer(user1.address, hre.ethers.parseEther("1000"));

    return { vault, token, owner, user1, user2 };
  }

  describe("Deposits", function () {
    it("should accept deposits of allowed tokens", async function () {
      const { vault, token, user1 } = await loadFixture(deployVaultFixture);
      const amount = hre.ethers.parseEther("100");

      await token.connect(user1).approve(await vault.getAddress(), amount);
      await vault.connect(user1).deposit(await token.getAddress(), amount);

      expect(await vault.getBalance(user1.address, await token.getAddress()))
        .to.equal(amount);
    });

    it("should revert on zero amount", async function () {
      const { vault, token, user1 } = await loadFixture(deployVaultFixture);

      await expect(
        vault.connect(user1).deposit(await token.getAddress(), 0)
      ).to.be.revertedWithCustomError(vault, "ZeroAmount");
    });

    it("should revert on disallowed tokens", async function () {
      const { vault, user1 } = await loadFixture(deployVaultFixture);
      const fakeToken = "0x0000000000000000000000000000000000000001";

      await expect(
        vault.connect(user1).deposit(fakeToken, 100)
      ).to.be.revertedWithCustomError(vault, "TokenNotAllowed");
    });

    it("should emit Deposited event", async function () {
      const { vault, token, user1 } = await loadFixture(deployVaultFixture);
      const amount = hre.ethers.parseEther("50");

      await token.connect(user1).approve(await vault.getAddress(), amount);
      await expect(vault.connect(user1).deposit(await token.getAddress(), amount))
        .to.emit(vault, "Deposited")
        .withArgs(user1.address, await token.getAddress(), amount);
    });
  });

  describe("Withdrawals", function () {
    it("should allow withdrawal of deposited tokens", async function () {
      const { vault, token, user1 } = await loadFixture(deployVaultFixture);
      const amount = hre.ethers.parseEther("100");

      await token.connect(user1).approve(await vault.getAddress(), amount);
      await vault.connect(user1).deposit(await token.getAddress(), amount);
      await vault.connect(user1).withdraw(await token.getAddress(), amount);

      expect(await vault.getBalance(user1.address, await token.getAddress()))
        .to.equal(0);
    });

    it("should revert on insufficient balance", async function () {
      const { vault, token, user1 } = await loadFixture(deployVaultFixture);

      await expect(
        vault.connect(user1).withdraw(await token.getAddress(), 100)
      ).to.be.revertedWithCustomError(vault, "InsufficientBalance");
    });
  });
});

Foundry (Forge) Testing

// test/SecureVault.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "forge-std/Test.sol";
import "../src/SecureVault.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockERC20 is ERC20 {
    constructor() ERC20("Mock", "MCK") {
        _mint(msg.sender, 1_000_000 ether);
    }
}

contract SecureVaultTest is Test {
    SecureVault vault;
    MockERC20 token;
    address user1 = makeAddr("user1");
    address user2 = makeAddr("user2");

    function setUp() public {
        vault = new SecureVault();
        token = new MockERC20();
        vault.setAllowedToken(address(token), true);

        // Fund user1
        token.transfer(user1, 1000 ether);
    }

    function test_deposit() public {
        vm.startPrank(user1);
        token.approve(address(vault), 100 ether);
        vault.deposit(address(token), 100 ether);
        vm.stopPrank();

        assertEq(vault.getBalance(user1, address(token)), 100 ether);
    }

    function test_revert_depositZeroAmount() public {
        vm.prank(user1);
        vm.expectRevert(SecureVault.ZeroAmount.selector);
        vault.deposit(address(token), 0);
    }

    function testFuzz_deposit(uint256 amount) public {
        amount = bound(amount, 1, 1000 ether);

        vm.startPrank(user1);
        token.approve(address(vault), amount);
        vault.deposit(address(token), amount);
        vm.stopPrank();

        assertEq(vault.getBalance(user1, address(token)), amount);
    }

    // Invariant: total vault balance >= sum of all user balances
    function invariant_solvency() public view {
        uint256 vaultBalance = token.balanceOf(address(vault));
        uint256 user1Balance = vault.getBalance(user1, address(token));
        uint256 user2Balance = vault.getBalance(user2, address(token));
        assertGe(vaultBalance, user1Balance + user2Balance);
    }
}

Deployment Scripts

// scripts/deploy.ts (Hardhat)
import hre from "hardhat";

async function main() {
  const [deployer] = await hre.ethers.getSigners();
  console.log("Deploying with:", deployer.address);

  // Deploy
  const Vault = await hre.ethers.getContractFactory("SecureVault");
  const vault = await Vault.deploy();
  await vault.waitForDeployment();

  const vaultAddress = await vault.getAddress();
  console.log("SecureVault deployed to:", vaultAddress);

  // Verify on Etherscan (wait for block confirmations first)
  if (hre.network.name !== "hardhat" && hre.network.name !== "localhost") {
    console.log("Waiting for block confirmations...");
    await vault.deploymentTransaction()?.wait(5);

    await hre.run("verify:verify", {
      address: vaultAddress,
      constructorArguments: [],
    });
    console.log("Contract verified on Etherscan");
  }
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
# Foundry deployment
forge script script/Deploy.s.sol:DeployScript \
  --rpc-url $SEPOLIA_RPC_URL \
  --broadcast \
  --verify \
  --etherscan-api-key $ETHERSCAN_API_KEY \
  -vvvv

Smart Contract Security

Common Vulnerabilities and Mitigations

Vulnerability Description Mitigation
Reentrancy External call re-enters contract before state update CEI pattern + ReentrancyGuard
Integer overflow Arithmetic wraps around (pre-0.8) Solidity 0.8+ has built-in checks
Front-running Miners/validators reorder transactions Commit-reveal schemes, flashbots
Access control Missing authorization checks OpenZeppelin Ownable or AccessControl
Oracle manipulation Price oracle returns stale/manipulated data Chainlink oracles, TWAP
Uninitialized proxy Implementation not initialized after deploy Use initializer modifier
Delegatecall injection Malicious delegatecall target Restrict delegatecall targets
Signature replay Reusing signed messages across chains/contracts Include chainId, nonce, contract address

Checks-Effects-Interactions (CEI) Pattern

// ALWAYS follow this order:
function withdraw(uint256 amount) external {
    // 1. CHECKS - validate inputs and state
    require(balances[msg.sender] >= amount, "Insufficient balance");

    // 2. EFFECTS - update state BEFORE external calls
    balances[msg.sender] -= amount;

    // 3. INTERACTIONS - external calls LAST
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success, "Transfer failed");
}

Audit Tools

# Slither - static analysis
pip install slither-analyzer
slither . --config-file slither.config.json

# Mythril - symbolic execution
pip install mythril
myth analyze contracts/SecureVault.sol --solv 0.8.24

# Foundry fuzzing and invariant testing
forge test --fuzz-runs 10000
forge test --match-test invariant

Frontend Integration

ethers.js v6

import { ethers, BrowserProvider, Contract, formatEther, parseEther } from 'ethers';
import VaultABI from './abis/SecureVault.json';

const VAULT_ADDRESS = '0x...';

// Connect wallet
async function connectWallet(): Promise<BrowserProvider> {
  if (!window.ethereum) {
    throw new Error('No wallet detected. Install MetaMask.');
  }

  const provider = new BrowserProvider(window.ethereum);
  await provider.send('eth_requestAccounts', []);
  return provider;
}

// Read from contract (no wallet needed)
async function getBalance(userAddress: string, tokenAddress: string): Promise<string> {
  const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
  const vault = new Contract(VAULT_ADDRESS, VaultABI, provider);
  const balance = await vault.getBalance(userAddress, tokenAddress);
  return formatEther(balance);
}

// Write to contract (wallet required)
async function deposit(tokenAddress: string, amount: string): Promise<void> {
  const provider = await connectWallet();
  const signer = await provider.getSigner();

  // Approve token spending first
  const token = new Contract(tokenAddress, ERC20ABI, signer);
  const tx1 = await token.approve(VAULT_ADDRESS, parseEther(amount));
  await tx1.wait();

  // Deposit
  const vault = new Contract(VAULT_ADDRESS, VaultABI, signer);
  const tx2 = await vault.deposit(tokenAddress, parseEther(amount));
  const receipt = await tx2.wait();

  console.log('Deposited in block:', receipt?.blockNumber);
}

// Listen for events
async function watchDeposits(): Promise<void> {
  const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
  const vault = new Contract(VAULT_ADDRESS, VaultABI, provider);

  vault.on('Deposited', (user, token, amount) => {
    console.log(`${user} deposited ${formatEther(amount)} of ${token}`);
  });
}

viem + wagmi (Modern Alternative)

// wagmi config
import { createConfig, http } from 'wagmi';
import { mainnet, sepolia, base } from 'wagmi/chains';
import { injected, walletConnect } from 'wagmi/connectors';

export const config = createConfig({
  chains: [mainnet, sepolia, base],
  connectors: [
    injected(),
    walletConnect({ projectId: process.env.WC_PROJECT_ID! }),
  ],
  transports: {
    [mainnet.id]: http(),
    [sepolia.id]: http(),
    [base.id]: http(),
  },
});

// React hooks with wagmi
import { useAccount, useReadContract, useWriteContract, useWaitForTransactionReceipt } from 'wagmi';
import { parseEther } from 'viem';

function VaultDeposit() {
  const { address } = useAccount();

  const { data: balance } = useReadContract({
    address: VAULT_ADDRESS,
    abi: VaultABI,
    functionName: 'getBalance',
    args: [address!, TOKEN_ADDRESS],
    query: { enabled: !!address },
  });

  const { writeContract, data: hash } = useWriteContract();

  const { isLoading: isConfirming } = useWaitForTransactionReceipt({ hash });

  function handleDeposit() {
    writeContract({
      address: VAULT_ADDRESS,
      abi: VaultABI,
      functionName: 'deposit',
      args: [TOKEN_ADDRESS, parseEther('10')],
    });
  }

  return (
    <div>
      <p>Balance: {balance?.toString()}</p>
      <button onClick={handleDeposit} disabled={isConfirming}>
        {isConfirming ? 'Confirming...' : 'Deposit 10 tokens'}
      </button>
    </div>
  );
}

Gas Optimization Strategies

Storage Optimization

// BAD: Each variable uses a full 32-byte slot
contract Unoptimized {
    uint256 a;    // slot 0
    bool b;       // slot 1 (wastes 31 bytes)
    uint256 c;    // slot 2
    bool d;       // slot 3 (wastes 31 bytes)
}

// GOOD: Pack variables into fewer slots
contract Optimized {
    uint256 a;    // slot 0
    uint256 c;    // slot 1
    bool b;       // slot 2 (packed with d)
    bool d;       // slot 2
}

// Use smaller types when possible
contract PackedStruct {
    struct UserData {
        uint128 balance;     // 16 bytes
        uint64 lastUpdate;   // 8 bytes
        uint32 nonce;        // 4 bytes
        bool active;         // 1 byte
        // Total: 29 bytes, fits in 1 slot
    }
}

Code-Level Optimizations

// Use custom errors instead of strings (saves ~50 gas per revert)
error InsufficientBalance(uint256 requested, uint256 available);

// Use unchecked for safe arithmetic (saves ~100 gas per operation)
function sum(uint256[] calldata values) external pure returns (uint256 total) {
    for (uint256 i = 0; i < values.length;) {
        total += values[i];
        unchecked { ++i; } // Safe: i < values.length prevents overflow
    }
}

// Use calldata instead of memory for read-only external function params
function process(bytes calldata data) external pure returns (bytes32) {
    return keccak256(data);
}

// Cache storage reads in memory
function withdrawAll(address[] storage users) internal {
    uint256 length = users.length; // Cache length (1 SLOAD vs N)
    for (uint256 i = 0; i < length;) {
        _withdraw(users[i]);
        unchecked { ++i; }
    }
}

Layer 2 Solutions

L2 Type Best For Considerations
Optimism (OP Stack) Optimistic rollup General dapps, DeFi 7-day withdrawal, OP_STACK ecosystem
Arbitrum Optimistic rollup DeFi, complex contracts 7-day withdrawal, Nitro engine
Base Optimistic rollup (OP Stack) Consumer apps Coinbase ecosystem, low fees
zkSync Era ZK rollup Privacy, instant finality zkEVM, different gas model
Polygon zkEVM ZK rollup EVM compatibility Full EVM equivalence goal
Starknet ZK rollup (STARK) High-throughput Cairo language, not EVM

Multi-Chain Deployment Pattern

// deploy/deploy-multichain.ts
const CHAINS = {
  mainnet: { rpc: process.env.MAINNET_RPC, chainId: 1 },
  base: { rpc: 'https://mainnet.base.org', chainId: 8453 },
  arbitrum: { rpc: 'https://arb1.arbitrum.io/rpc', chainId: 42161 },
  optimism: { rpc: 'https://mainnet.optimism.io', chainId: 10 },
};

async function deployToChain(chainName: string, config: ChainConfig) {
  console.log(`\nDeploying to ${chainName}...`);
  const provider = new ethers.JsonRpcProvider(config.rpc);
  const deployer = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);

  const factory = new ethers.ContractFactory(abi, bytecode, deployer);
  const contract = await factory.deploy();
  await contract.waitForDeployment();

  const address = await contract.getAddress();
  console.log(`${chainName}: ${address}`);

  // Save deployment address
  return { chain: chainName, chainId: config.chainId, address };
}

DeFi Patterns

Automated Market Maker (AMM) Core Concept

// Simplified constant product AMM (x * y = k)
contract SimpleAMM {
    IERC20 public tokenA;
    IERC20 public tokenB;
    uint256 public reserveA;
    uint256 public reserveB;

    uint256 public constant FEE_NUMERATOR = 997;   // 0.3% fee
    uint256 public constant FEE_DENOMINATOR = 1000;

    /// @notice Swap tokenA for tokenB
    function swap(uint256 amountIn, uint256 minAmountOut) external returns (uint256 amountOut) {
        // Apply fee
        uint256 amountInWithFee = amountIn * FEE_NUMERATOR;

        // Constant product formula: (x + dx) * (y - dy) = x * y
        amountOut = (amountInWithFee * reserveB) /
                    (reserveA * FEE_DENOMINATOR + amountInWithFee);

        require(amountOut >= minAmountOut, "Slippage exceeded");

        // Update reserves
        reserveA += amountIn;
        reserveB -= amountOut;

        // Transfer tokens
        tokenA.transferFrom(msg.sender, address(this), amountIn);
        tokenB.transfer(msg.sender, amountOut);
    }
}

NFT Metadata and Storage

IPFS Metadata Pattern

// Upload metadata to IPFS (using Pinata or nft.storage)
import { PinataSDK } from 'pinata';

const pinata = new PinataSDK({
  pinataJwt: process.env.PINATA_JWT!,
});

async function uploadMetadata(tokenId: number, imageFile: File): Promise<string> {
  // 1. Upload image
  const imageUpload = await pinata.upload.file(imageFile);
  const imageURI = `ipfs://${imageUpload.IpfsHash}`;

  // 2. Create and upload metadata JSON
  const metadata = {
    name: `My NFT #${tokenId}`,
    description: 'A unique digital collectible',
    image: imageURI,
    attributes: [
      { trait_type: 'Background', value: 'Blue' },
      { trait_type: 'Rarity', value: 'Rare' },
      { display_type: 'number', trait_type: 'Generation', value: 1 },
    ],
  };

  const metadataUpload = await pinata.upload.json(metadata);
  return `ipfs://${metadataUpload.IpfsHash}`;
}

The Graph (Blockchain Indexing)

# subgraph.yaml schema
type Deposit @entity {
  id: Bytes!
  user: Bytes!
  token: Bytes!
  amount: BigInt!
  blockNumber: BigInt!
  blockTimestamp: BigInt!
  transactionHash: Bytes!
}

type User @entity {
  id: Bytes!
  totalDeposited: BigInt!
  depositCount: BigInt!
  deposits: [Deposit!]! @derivedFrom(field: "user")
}
// subgraph mapping handler
import { Deposited } from '../generated/SecureVault/SecureVault';
import { Deposit, User } from '../generated/schema';
import { BigInt, Bytes } from '@graphprotocol/graph-ts';

export function handleDeposited(event: Deposited): void {
  const deposit = new Deposit(event.transaction.hash.concatI32(event.logIndex.toI32()));
  deposit.user = event.params.user;
  deposit.token = event.params.token;
  deposit.amount = event.params.amount;
  deposit.blockNumber = event.block.number;
  deposit.blockTimestamp = event.block.timestamp;
  deposit.transactionHash = event.transaction.hash;
  deposit.save();

  // Update user aggregate
  let user = User.load(event.params.user);
  if (!user) {
    user = new User(event.params.user);
    user.totalDeposited = BigInt.zero();
    user.depositCount = BigInt.zero();
  }
  user.totalDeposited = user.totalDeposited.plus(event.params.amount);
  user.depositCount = user.depositCount.plus(BigInt.fromI32(1));
  user.save();
}

Alternative Chains

Solana (Anchor Framework)

// programs/vault/src/lib.rs
use anchor_lang::prelude::*;

declare_id!("VAULT_PROGRAM_ID");

#[program]
pub mod vault {
    use super::*;

    pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
        let vault = &mut ctx.accounts.vault;
        vault.balance += amount;

        // Transfer SOL
        anchor_lang::system_program::transfer(
            CpiContext::new(
                ctx.accounts.system_program.to_account_info(),
                anchor_lang::system_program::Transfer {
                    from: ctx.accounts.user.to_account_info(),
                    to: ctx.accounts.vault.to_account_info(),
                },
            ),
            amount,
        )?;

        Ok(())
    }
}

#[derive(Accounts)]
pub struct Deposit<'info> {
    #[account(mut)]
    pub user: Signer<'info>,
    #[account(mut)]
    pub vault: Account<'info, VaultState>,
    pub system_program: Program<'info, System>,
}

#[account]
pub struct VaultState {
    pub authority: Pubkey,
    pub balance: u64,
}

Security Checklist for Smart Contracts

Before deploying any smart contract to mainnet:

  • All functions have proper access control
  • CEI pattern followed for all state-changing functions with external calls
  • ReentrancyGuard on all payable/withdrawal functions
  • Custom errors used instead of require strings
  • Events emitted for all state changes
  • Input validation on all external/public functions
  • Integer arithmetic checked (Solidity 0.8+ or SafeMath)
  • No hardcoded addresses (use constructor parameters or admin setters)
  • Upgradeable proxy properly initialized (if applicable)
  • Slither static analysis passes with no high/medium findings
  • Fuzz testing with 10,000+ runs on critical functions
  • Invariant tests for protocol-level properties
  • Multi-sig or timelock on admin functions for mainnet
  • Emergency pause mechanism implemented
  • Test coverage above 95% on all critical paths
  • Independent audit completed (for contracts holding significant value)