CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-hardhat-deploy

Hardhat plugin providing comprehensive deployment system for Ethereum smart contracts with replicable deployments and enhanced testing capabilities.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

named-accounts.mddocs/

Named Accounts

Address management system providing cleaner deployment scripts through named address mappings with network-specific configuration support.

Capabilities

Named Account Access

Access configured named accounts for deployment scripts and tests.

/**
 * Get named accounts as configured in hardhat.config
 * @returns Promise resolving to mapping of account names to addresses
 */
getNamedAccounts(): Promise<{ [name: string]: Address }>;

/**
 * Get unnamed accounts (remaining accounts not assigned names)
 * @returns Promise resolving to array of unused account addresses
 */
getUnnamedAccounts(): Promise<string[]>;

/**
 * Get current network chain ID
 * @returns Promise resolving to chain ID as string
 */
getChainId(): Promise<string>;

Usage Examples:

import { getNamedAccounts } from "hardhat";

// In deploy scripts
const func: DeployFunction = async function (hre) {
  const { deployments } = hre;
  const { deploy } = deployments;
  const { deployer, tokenOwner, multisig } = await getNamedAccounts();

  await deploy("MyContract", {
    from: deployer,
    args: [tokenOwner, multisig],
    log: true,
  });
};

// In tests
describe("Contract Tests", function () {
  it("should work with named accounts", async function () {
    const { deployer, user, admin } = await getNamedAccounts();
    
    // Deploy or interact using named accounts
    await deployments.deploy("TestContract", {
      from: deployer,
      args: [admin],
    });
    
    // Execute transactions
    await deployments.execute(
      "TestContract",
      { from: user },
      "userFunction"
    );
  });
});

// Access unnamed accounts for additional signers
const func: DeployFunction = async function (hre) {
  const { deployer, admin } = await getNamedAccounts();
  const unnamedAccounts = await hre.getUnnamedAccounts();
  
  // Use first unnamed account as temporary signer
  const tempSigner = unnamedAccounts[0];
  
  await deployments.execute(
    "MyContract",
    { from: tempSigner },
    "temporaryOperation"
  );
};

Configuration

Configure named accounts in hardhat.config with network-specific overrides.

Usage Examples:

// hardhat.config.ts
export default {
  namedAccounts: {
    // Simple configuration
    deployer: {
      default: 0, // Use account index 0 by default
      1: "0x1234567890123456789012345678901234567890", // Override for mainnet (chainId 1)
      4: "0x9876543210987654321098765432109876543210", // Override for rinkeby (chainId 4)
    },
    
    // Multi-network configuration
    tokenOwner: {
      default: 1, // Use account index 1 by default
      localhost: 0, // Use account 0 on localhost
      hardhat: 0, // Use account 0 on hardhat network
      mainnet: "0xMainnetTokenOwner...",
      polygon: "0xPolygonTokenOwner...",
    },
    
    // Fixed addresses across networks
    treasury: {
      default: "0xTreasuryAddress...", // Same address everywhere
    },
    
    // Conditional configuration
    multisig: {
      default: 2, // Use account index 2 by default
      1: "0xMainnetMultisig...", // Mainnet multisig
      137: "0xPolygonMultisig...", // Polygon multisig
      localhost: null, // No multisig on localhost
      hardhat: null, // No multisig on hardhat
    },
    
    // User accounts for testing
    user1: { default: 3 },
    user2: { default: 4 },
    user3: { default: 5 },
    
    // Protocol-specific accounts
    governance: {
      default: "0xGovernanceContract...",
      testnet: 6, // Use account index on testnets
    },
  },
  
  networks: {
    mainnet: {
      // Network configuration
    },
    polygon: {
      // Network configuration
    },
    localhost: {
      // Local development
    },
  },
};

// Using in deploy scripts with network awareness
const func: DeployFunction = async function (hre) {
  const { deployments, network } = hre;
  const { deploy } = deployments;
  const { deployer, tokenOwner, multisig, governance } = await getNamedAccounts();

  await deploy("GovernanceToken", {
    from: deployer,
    args: [tokenOwner, governance],
    log: true,
  });

  // Only set multisig on networks where it's configured
  if (multisig) {
    await deployments.execute(
      "GovernanceToken",
      { from: tokenOwner },
      "transferOwnership",
      multisig
    );
  }
};

Account Resolution

Named accounts support various resolution strategies based on configuration.

Usage Examples:

// Configuration showing different resolution types
export default {
  namedAccounts: {
    // Account index resolution
    deployer: {
      default: 0, // Use accounts[0]
      mainnet: 1, // Use accounts[1] on mainnet
    },
    
    // Direct address resolution
    treasury: {
      default: "0x1234567890123456789012345678901234567890",
      polygon: "0x9876543210987654321098765432109876543210",
    },
    
    // Null resolution (account not available)
    multisig: {
      default: null, // No multisig by default
      mainnet: "0xMainnetMultisig...", // Only on mainnet
    },
    
    // Network name based resolution
    admin: {
      default: 2,
      localhost: 0, // Different account for local development
      hardhat: 0, // Different account for hardhat network
      rinkeby: "0xRinkebyAdmin...", // Specific address for testnet
    },
    
    // Chain ID based resolution
    validator: {
      default: 3,
      1: "0xMainnetValidator...", // Ethereum mainnet
      137: "0xPolygonValidator...", // Polygon
      80001: "0xMumbaiValidator...", // Polygon Mumbai testnet
    },
  },
};

// Usage with conditional logic
const func: DeployFunction = async function (hre) {
  const { deployments, network } = hre;
  const accounts = await getNamedAccounts();
  const { deployer, admin, multisig } = accounts;

  console.log(`Deploying on ${network.name} with deployer: ${deployer}`);
  
  if (admin) {
    console.log(`Admin account: ${admin}`);
  }
  
  if (multisig) {
    console.log(`Multisig available: ${multisig}`);
  } else {
    console.log("No multisig configured for this network");
  }

  await deploy("MyContract", {
    from: deployer,
    args: [admin || deployer], // Fallback to deployer if no admin
    log: true,
  });
};

Integration with Hardhat Features

Named accounts integrate seamlessly with Hardhat's account management and network switching.

Usage Examples:

// Using with hardhat-ethers
import { ethers, getNamedAccounts } from "hardhat";

describe("Contract Tests", function () {
  it("should work with ethers signers", async function () {
    const { deployer, user } = await getNamedAccounts();
    
    // Get signers for named accounts
    const deployerSigner = await ethers.getSigner(deployer);
    const userSigner = await ethers.getSigner(user);
    
    // Use with contract instances
    const contract = await ethers.getContract("MyContract", deployerSigner);
    await contract.connect(userSigner).userMethod();
  });
});

// Using with account impersonation
describe("Impersonation Tests", function () {
  it("should impersonate named accounts", async function () {
    const { multisig } = await getNamedAccounts();
    
    if (multisig) {
      // hardhat-deploy automatically enables impersonation for named accounts
      await deployments.execute(
        "MyContract",
        { from: multisig }, // This will be impersonated on localhost/hardhat
        "adminFunction"
      );
    }
  });
});

// Using with different networks
task("deploy-cross-network", "Deploy across networks")
  .setAction(async (taskArgs, hre) => {
    const { deployer, admin } = await getNamedAccounts();
    
    console.log(`Network: ${hre.network.name}`);
    console.log(`Deployer: ${deployer}`);
    console.log(`Admin: ${admin}`);
    
    // Deploy logic here
  });

// Run with different networks:
// npx hardhat deploy-cross-network --network localhost
// npx hardhat deploy-cross-network --network mainnet
// npx hardhat deploy-cross-network --network polygon

Companion Networks

Configure companion networks to access deployments from other networks during deployment scripts.

/**
 * Companion network configuration in hardhat.config
 */
interface CompanionNetworksConfig {
  [companionName: string]: string; // Network name to reference
}

/**
 * Companion network access in deployment scripts
 */
interface CompanionNetwork {
  deployments: {
    get(name: string): Promise<Deployment>;
    getOrNull(name: string): Promise<Deployment | null>;
    all(): Promise<{ [name: string]: Deployment }>;
  };
  getNamedAccounts(): Promise<{ [name: string]: Address }>;
  getChainId(): Promise<string>;
}

Configuration Examples:

// hardhat.config.ts
export default {
  networks: {
    // Layer 1 network
    mainnet: {
      url: "https://mainnet.infura.io/v3/...",
      accounts: ["0x..."],
    },
    
    // Layer 2 network with L1 companion
    optimism: {
      url: "https://mainnet.optimism.io",
      accounts: ["0x..."],
      companionNetworks: {
        l1: "mainnet", // Reference mainnet as L1
        ethereum: "mainnet", // Alternative reference name
      },
    },
    
    // Polygon with Ethereum companion
    polygon: {
      url: "https://polygon-rpc.com",
      accounts: ["0x..."],
      companionNetworks: {
        ethereum: "mainnet",
        l1: "mainnet",
      },
    },
    
    // Testnet with companion testnet
    mumbai: {
      url: "https://rpc-mumbai.maticvigil.com",
      accounts: ["0x..."],
      companionNetworks: {
        ethereum: "goerli",
        l1: "goerli",
      },
    },
    
    // Local development with multiple companions
    localhost: {
      url: "http://127.0.0.1:8545",
      companionNetworks: {
        l1: "localhost", // Self reference for testing
        mainnet: "localhost", // Use localhost as mainnet for testing
      },
    },
  },
  
  namedAccounts: {
    deployer: {
      default: 0,
    },
    // Cross-chain accounts can be different
    bridgeOperator: {
      default: 1,
      mainnet: "0xMainnetBridgeOperator...",
      optimism: "0xOptimismBridgeOperator...",
      polygon: "0xPolygonBridgeOperator...",
    },
  },
};

Usage Examples:

// Accessing companion network deployments
const func: DeployFunction = async function (hre) {
  const { deployments, companionNetworks } = hre;
  const { deploy } = deployments;
  const { deployer, bridgeOperator } = await getNamedAccounts();

  // Access L1 contracts from L2 deployment
  if (companionNetworks.l1) {
    const l1TokenBridge = await companionNetworks.l1.deployments.get("TokenBridge");
    const l1ChainId = await companionNetworks.l1.getChainId();
    
    console.log(`L1 TokenBridge at: ${l1TokenBridge.address} (Chain: ${l1ChainId})`);
    
    // Deploy L2 contract with L1 bridge reference
    await deploy("L2TokenBridge", {
      from: deployer,
      args: [
        l1TokenBridge.address, // L1 bridge address
        l1ChainId, // L1 chain ID
        bridgeOperator, // L2 bridge operator
      ],
      log: true,
    });
  }
  
  // Access multiple companion networks
  if (companionNetworks.ethereum) {
    const ethereumDeployments = await companionNetworks.ethereum.deployments.all();
    console.log(`Found ${Object.keys(ethereumDeployments).length} Ethereum contracts`);
    
    // Use Ethereum contract addresses in L2 deployment
    const ethereumToken = await companionNetworks.ethereum.deployments.get("WETH");
    
    await deploy("L2WrappedToken", {
      from: deployer,
      args: [
        ethereumToken.address, // Reference to L1 token
        "Wrapped ETH (L2)",
        "WETH-L2",
      ],
      log: true,
    });
  }
};

// Cross-chain validation
const func: DeployFunction = async function (hre) {
  const { companionNetworks, network } = hre;
  
  console.log(`Deploying on ${network.name}`);
  
  // Validate L1 deployments exist before L2 deployment
  if (companionNetworks.l1) {
    try {
      const l1Contracts = await companionNetworks.l1.deployments.all();
      const requiredContracts = ["TokenBridge", "MessagePasser", "StateCommitmentChain"];
      
      for (const contractName of requiredContracts) {
        if (!l1Contracts[contractName]) {
          throw new Error(`Required L1 contract ${contractName} not found`);
        }
        console.log(`✓ Found L1 ${contractName} at ${l1Contracts[contractName].address}`);
      }
    } catch (error) {
      console.error("L1 validation failed:", error);
      throw error;
    }
  }
  
  // Proceed with L2 deployment
  await deployments.deploy("L2Contract", {
    from: deployer,
    args: [],
    log: true,
  });
};

// Multi-network synchronization
const func: DeployFunction = async function (hre) {
  const { deployments, companionNetworks } = hre;
  
  // Get accounts from different networks
  const localAccounts = await getNamedAccounts();
  const l1Accounts = companionNetworks.l1 
    ? await companionNetworks.l1.getNamedAccounts()
    : {};
    
  console.log("Local deployer:", localAccounts.deployer);
  console.log("L1 deployer:", l1Accounts.deployer);
  
  // Use different signers for different networks
  await deploy("CrossChainContract", {
    from: localAccounts.deployer, // Local signer
    args: [
      l1Accounts.bridgeOperator || localAccounts.deployer, // L1 reference
    ],
    log: true,
  });
};

Types

/**
 * Named accounts configuration in hardhat.config
 */
interface NamedAccountsConfig {
  [name: string]:
    | string  // Direct address
    | number  // Account index
    | {
        [network: string]: null | number | string;
        default?: number | string;
      };
}

/**
 * Resolved named accounts mapping
 */
type NamedAccounts = { [name: string]: Address };

type Address = string;

docs

contract-interaction.md

core-deployment.md

deployment-management.md

diamond-deployment.md

index.md

named-accounts.md

proxy-deployment.md

test-fixtures.md

verification.md

tile.json