[hardhat] 기본 계정

https://hardhat.org/hardhat-network/docs/reference#initial-state 에서 찾을 수 있다. 다음 address 들이다

  • 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
  • 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
  • 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
  • 0x90F79bf6EB2c4f870365E785982E1f101E93b906
  • 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65
  • 0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc
  • 0x976EA74026E726554dB657fA54763abd0C3a0aa9
  • 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955
  • 0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f
  • 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720
  • 0xBcd4042DE499D14e55001CcbB24a551F3b954096
  • 0x71bE63f3384f5fb98995898A86B02Fb2426c5788
  • 0xFABB0ac9d68B0B445fB7357272Ff202C5651694a
  • 0x1CBd3b2770909D4e10f157cABC84C7264073C9Ec
  • 0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097
  • 0xcd3B766CCDd6AE721141F452C550Ca635964ce71
  • 0x2546BcD3c84621e976D8185a91A922aE77ECEc30
  • 0xbDA5747bFD65F08deb54cb465eB87D40e51B197E
  • 0xdD2FD4581271e230360230F9337D5c0430Bf44C0
  • 0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199

사용하지 않도록 한다.

[hardhat] npx hardhat을 hh로 줄여 쓰기

npx hardhat을 매번 치기 귀찮다. 그리고 커맨드라인 자동완성 기능도 있다. hardhat-shorthand 패키지를 설치하면 된다.

$ npm install --global hardhat-shorthand

공식 도움말은 여기를 참고하자: https://hardhat.org/hardhat-runner/docs/guides/command-line-completion

[Ethereum] 트랜잭션 해시로부터 Public key 및 주소 얻기

Ethereum의 경우 버전에 따라 트랜잭션에 gasPrice를 쓸 지, maxFeePerGas/maxPriorityFeePerGas 를 쓸 지가 정해진다. https://ethereum.stackexchange.com/questions/147692/how-to-get-public-key-by-the-transaction-hash 를 참고.

주소를 얻을 때, Public key에서 앞의 한 바이트(0x04)를 빼고, keccak256을 돌리는 부분에 유의하자.

const { ethers } = require("hardhat");

// 트랜잭션 서명 값
// const r = "0xe680637b83a1dd102364503bd77979b87c92ba651132a4df8e839af69c20af95";
// const s = "0x65becfa32384747adb05f543397baaf23d1c55fa28d0c9a38924912dae6df28f";
// const v = 12266684226873;
// const chainId = 6133342113419;

// 트랜잭션 해시
const txHash = "0xc7159866fb5b94d291e8624bdc731b7a6d46533b398065de7aa7dd1af7b6aa36";
const ethermainHash = "0xef120deb6ff2e516ad44724d220c0cd73166d169a2a0b0f66dfb5613d2d6169c"
const sepoliaHash = "0xfe4ddad4cae9ea353fc91441ee5db6c70bd5468673d907926be92dd4b8a63f63"

// 실제 트랜잭션에 사용된 private key. 아무거나 넣자.
const privateKey = "0x0000000000000000000000000000000000000000000000000000000000000001";

// Web3 프로바이더 설정 (Hardhat 로컬 노드 사용 예시)
const provider = ethers.provider;

// 서명된 트랜잭션 정보 가져오기
async function getPublicKeyFromTransactionHash(provider, txHash) {
  // Fetch the transaction using the transaction hash and provier
  const tx = await provider.getTransaction(txHash);

  console.log(tx);

  // Extract the all the relevant fields from the transaction (We need all of them)
  const unsignedTx = {
    gasPrice: tx.gasPrice,
    gasLimit: tx.gasLimit,
    value: tx.value,
    nonce: tx.nonce,
    data: tx.data,
    chainId: tx.chainId,
    to: tx.to,
    type: tx.type,
    // maxFeePerGas: tx.maxFeePerGas,
    // maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
  };

  // Serializing tx without the signature
  const serializedTx = ethers.utils.serializeTransaction(unsignedTx);

  // Extract the signature (v, r, s) from the transaction
  const { v, r, s } = tx;

  // Join splitted signature
  const signature = ethers.utils.joinSignature({ v, r, s });

  const recoveredPublicKey = ethers.utils.recoverPublicKey(
    ethers.utils.keccak256(serializedTx),
    signature,
  );
  console.log("recoveredPublicKey:", recoveredPublicKey);
  const keccak = ethers.utils.keccak256("0x" + recoveredPublicKey.slice(4))
  console.log("keccak256 of recoveredPublicKey:", keccak, ethers.utils.keccak256("0x" + recoveredPublicKey.slice(4)));

  // Recover the address or public key with (replace recoverAddress by recoverPublicKey) associated with the transaction
  return ethers.utils.recoverAddress(
    ethers.utils.keccak256(serializedTx),
    signature
  );
}

async function main() {
  const address = await getPublicKeyFromTransactionHash(provider, txHash);

  console.log("Address:", address);
}

main()
.then(() => process.exit(0))
.catch((error) => {
    console.error(error);
    process.exit(1);
});

[hardhat/ethers.js] getLedgerSigner

import { subtask } from "hardhat/config";
import { LedgerSigner } from "@ethers-ext/signer-ledger";
import HIDTransport from "@ledgerhq/hw-transport-node-hid";

const USE_LEDGER = {
  "61331819613419": true,
  "2022": true
};

subtask("getLedgerSigner", "Get LedgerSigner")
  .addOptionalPositionalParam("accountNumber", "Account number of the Ledger", "0")
  .setAction(async (taskArgs) => {
    const accountNumber = taskArgs.accountNumber;
    const chainId = (await ethers.provider.getNetwork()).chainId;
    const ledgerNeeded = USE_LEDGER[chainId] ?? false;
    console.log("ChainID:", chainId, "LedgerNeeded:", ledgerNeeded);
    if (ledgerNeeded) {
      const _master = new LedgerSigner(HIDTransport, ethers.provider, `m/44'/60'/${accountNumber}'/0/0`);
      _master.getFeeData = async () => {
        return {
            gasPrice: ethers.BigNumber.from(0),
            lastBaseFeePerGas: ethers.BigNumber.from(0),
            maxFeePerGas: ethers.utils.parseUnits("800", "gwei"),
            maxPriorityFeePerGas: ethers.utils.parseUnits("800", "gwei"),
        };
      };
      console.log(`LedgerSinger: m/44'/60'/${accountNumber}'/0/0:`, await _master.getAddress());
      _master.getBalance = async function() {
        return await this.provider.getBalance(this.getAddress());
      };

      return _master;
    }

    const accounts = await ethers.getSigners();
    return accounts[accountNumber];
  });

사용할 때는 hre를 이용해서 다음과 같이 하면 된다.

import { task } from "hardhat/config";
import './subtasks/getLedgerSigner';

task("ledgerBalance", "Prints an account's balance")
  .addOptionalPositionalParam("accountNumber", "The account number of the ledger", "0")
  .setAction(async (taskArgs) => {
    const signer = await hre.run("getLedgerSigner", { accountNumber: taskArgs.accountNumber });
    console.log("Signer Address:", await signer.getAddress());
    const balance = await signer.getBalance();

    console.log(ethers.utils.formatEther(balance), "ETH");
  });

[hardhat] hardhat network에서 다른 계정인 척 쓰기

const hwwallet = "0xda50da50da50da50da50da50da50da50da50da50";

//  impersonating HW wallet
await network.provider.request({
  method: "hardhat_impersonateAccount",
  params: [hwwallet],
});

const _master = await ethers.getSigner(hwwallet);
_master.getFeeData = async () => {
  return {
    gasPrice: ethers.BigNumber.from(0),
    lastBaseFeePerGas: ethers.BigNumber.from(0),
    maxFeePerGas: ethers.utils.parseUnits("800", "gwei"),
    maxPriorityFeePerGas: ethers.utils.parseUnits("800", "gwei"),
  };
};
master = _master;

console.log('master: ', await master.getAddress());

[hardhat/ethers.js] HW Ledger 체크

ethers.js v5 기준 hardhat 태스크

import { LedgerSigner } from "@anders-t/ethers-ledger";
import { task } from "hardhat/config";

task("checkHW", "Check HW Wallet")
  .addOptionalPositionalParam("accountNumber", "Index of the address of the HW wallet")
  .setAction(async (taskArgs) => {
  const accountNumber = taskArgs.accountNumber;
    const _master = new LedgerSigner(ethers.provider, `m/44'/60'/${accountNumber}'/0/0`);
  _master.getFeeData = async () => {
    return {
      gasPrice: ethers.BigNumber.from(0),
      lastBaseFeePerGas: ethers.BigNumber.from(0),
      maxFeePerGas: ethers.utils.parseUnits("800", "gwei"),
      maxPriorityFeePerGas: ethers.utils.parseUnits("800", "gwei"),
    };
  };
  const master = _master;

  console.log(`HW Ledger: [${accountNumber}]:${await master.getAddress()}: ${ethers.utils.formatEther(await master.getBalance())} ETH`);
});

사용은 hh checkHW 0 와 같이 한다.

[ethers.js] ERC20 balance 체크

hardhat task로 만든 ERC20 잔액 확인 태스크

task("balanceERC20", "Check the balance of ERC20 token")
  .addPositionalParam("tokenAddress", "ERC20 Token contract address")
  .addPositionalParam("address", "Address to be checked")
  .setAction(async (taskArgs) => {
    const ERC20 = await ethers.getContractFactory("@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20");
    const erc20 = await ERC20.attach(taskArgs.tokenAddress);
    console.log(ethers.formatEther(await erc20.balanceOf(taskArgs.address)), await erc20.symbol());
  });

[ethers.js] Transaction details 얻기 및 ERC20 Transfer 정보 얻기

hardhat task로 만든 txHash로 transaction details 얻어서 ERC20 Transfer() 이면 정보 출력.

task("trasactionDetails", "Get the transaction deatils")
  .addPositionalParam("txHash", "Transaction Hash")
  .setAction(async (taskArgs, hre) => {
    const provider = ethers.provider;
    const transaction = await provider.getTransaction(taskArgs.txHash);
    console.log(transaction);
    if (transaction.value === 0n) {
      if (transaction.data.slice(0, 10) === "0xa9059cbb") {
        console.log("---- ERC20 Transfer -------------------------");
        await hre.run("parseERC20TransferData", { data: transaction.data });
      }
    }
  });

task("parseERC20TransferData", "Parsing")
  .addPositionalParam("data", "Transacton Data")
  .setAction(async (taskArgs) => {
    const abiCoder = new ethers.AbiCoder();
    const decodedData = abiCoder.decode(['address', 'uint256'], "0x" + taskArgs.data.slice(10));
    const to = decodedData[0];
    const value = decodedData[1].toString();

    console.log('To Address:', to);
    console.log('Token Amount:', ethers.formatEther(value));
  });

[ethers.js] Contract 시그니처와 hash 확인

const ca = await ethers.getContractFactory("DasomToken");
const cabi = ca.interface;
cabi.format() // Fragments 확인
cabi.forEachFunction((func, index) => { console.log(func.format()); }); // 각 함수의 시그니처 확인
cabi.forEachFunction((func, index) => { console.log(func.format(), ethers.keccak256(ethers.toUtf8Bytes(func.format()))); }); // 시그니처와 해시 값 확인.