or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

contract-interaction.mdcore-deployment.mddeployment-management.mddiamond-deployment.mdindex.mdnamed-accounts.mdproxy-deployment.mdtest-fixtures.mdverification.md
tile.json

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;