블록체인

[ethereum] web3.js와 같은 ethers 알아보기

멍개. 2022. 8. 28. 08:13

ethers는 web3.js처럼 이더리움 네트워크를 조회하고 조작할 수 있는 인터페이스를 제공하는 라이브러리입니다. 그렇다면 web3.js대신 ethers.js를 사용해야하는 이유는 무엇일까?

ethers는 provider와 signer를 주입하는 형태로 유연한 코드 작성이 가능하다고 생각합니다.

· 설치

$ npm install --save ethers

ethers는 크게 4개의 객체를 제공합니다.

▶ CommonJS

const { providers, Wallet, utils, Contract } = require('ethers');

▶ ESM

import { providers, Wallet, utils, Contract } from "ethers";

▶ CDN

 
<script type="module">
    import { providers, Wallet, utils, Contract } from "https://cdn.ethers.io/lib/ethers-5.2.esm.min.js";
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ethers/5.6.9/ethers.umd.min.js" integrity="sha512-Veaz5IU2iRpa0BBrJlJeRgfJ7OAHWtVJZTXvgdH7s3ffsLUChllMCqC0Bb+eeRxGlrZ06iYIE/R3KsciCrgv3A==" crossorigin="anonymous" referrerpolicy="no-referrer">
</script>

<script>
  const { providers, Wallet, utils, Contract } = ethers.ethers;
</script>

● providers

providers는 이더리움 노드와 연결합니다. 블록, 트랜잭션 조회등을 수행할 수 있습니다.

· 노드연결

 
const provider = new providers.JsonRpcProvider(url);

const provider = new providers.JsonRpcProvider('http://localhost:8545');

만약, metamask의 provider를 이용하고 싶다면 다음과 같이 이용할 수 있습니다.

const provider = new ethers.providers.Web3Provider(window.ethereum)

· 최근 블록번호 조회

const block = await provider.getBlockNumber()

· 블록조회

const block0 = await provider.getBlock(0)

{
  hash: '0xe4b7283f03a18236615e883d91595dc6cf12119ed14d0179c7e29f5f80f2c94d',
  parentHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
  number: 0,
  timestamp: 1650158272,
  nonce: '0x0000000000000000',
  difficulty: 0,
  gasLimit: BigNumber { _hex: '0x6691b7', _isBigNumber: true },
  gasUsed: BigNumber { _hex: '0x00', _isBigNumber: true },
  miner: '0x0000000000000000000000000000000000000000',
  extraData: '0x',
  transactions: [], 
  _difficulty: BigNumber { _hex: '0x00', _isBigNumber: true }
}
 
const txs = await provider.getBlockWithTransactions(0)
{
  hash: '0xe4b7283f03a18236615e883d91595dc6cf12119ed14d0179c7e29f5f80f2c94d',
  parentHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
  number: 0,
  timestamp: 1650158272,
  nonce: '0x0000000000000000',
  difficulty: 0,
  gasLimit: BigNumber { _hex: '0x6691b7', _isBigNumber: true },
  gasUsed: BigNumber { _hex: '0x00', _isBigNumber: true },
  miner: '0x0000000000000000000000000000000000000000',
  extraData: '0x',
  transactions: [],
  _difficulty: BigNumber { _hex: '0x00', _isBigNumber: true }
}

getBlockgetBlockWithTransactions의 차이는 transactions에 있습니다. getBlock 트랜잭션 해시 목록을 가지고 있으며 getBlockWithTransactions트랜잭션 객체목록을 가지고 있습니다.

· 트랜잭션 조회

const tx = await provider.getTransaction("0x5b73e239c55d790e3c9c3bbb84092652db01bb8dbf49ccc9e4a318470419d9a0");
{
  accessList: null,
  blockHash: '0x8a179bc6cb299f936c4fd614995e62d597ec6108b579c23034fb220967ceaa94',
  blockNumber: 12598244,
  chainId: 1,
  confirmations: 1869134,
  creates: '0x733aF852514e910E2f8af40d61E00530377889E9',
  data: '0x608060405234801561001057600080fd5b5060405161062438038061062483398101604081905261002f916100cd565b60405163c47f002760e01b815260206004820152600d60248201526c0daead8e8d2c6c2d8d85ccae8d609b1b60448201526001600160a01b0382169063c47f002790606401602060405180830381600087803b15801561008e57600080fd5b505af11580156100a2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100c691906100fb565b5050610113565b6000602082840312156100de578081fd5b81516001600160a01b03811681146100f4578182fd5b9392505050565b60006020828403121561010c578081fd5b5051919050565b610502806101226000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80634c0770b914610030575b600080fd5b61004361003e366004610309565b61005b565b60405161005293929190610389565b60405180910390f35b600060608085841461006c57600080fd5b8567ffffffffffffffff81111561009357634e487b7160e01b600052604160045260246000fd5b6040519080825280602002602001820160405280156100bc578160200160208202803683370190505b5091508567ffffffffffffffff8111156100e657634e487b7160e01b600052604160045260246000fd5b60405190808252806020026020018201604052801561011957816020015b60608152602001906001900390816101045790505b50905060005b86811015610235576101cd8a8a8a8a8581811061014c57634e487b7160e01b600052603260045260246000fd5b905060200201602081019061016191906102db565b89898681811061018157634e487b7160e01b600052603260045260246000fd5b90506020028101906101939190610460565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061024592505050565b8483815181106101ed57634e487b7160e01b600052603260045260246000fd5b6020026020010184848151811061021457634e487b7160e01b600052603260045260246000fd5b6020908102919091010191909152528061022d816104a5565b91505061011f565b5043925096509650969350505050565b6000606060405190506000815260208101604052600080845160208601878afa9150843d101561028857603f3d01601f191681016040523d81523d6000602083013e5b94509492505050565b60008083601f8401126102a2578182fd5b50813567ffffffffffffffff8111156102b9578182fd5b6020830191508360208260051b85010111156102d457600080fd5b9250929050565b6000602082840312156102ec578081fd5b81356001600160a01b0381168114610302578182fd5b9392505050565b60008060008060008060808789031215610321578182fd5b8635955060208701359450604087013567ffffffffffffffff80821115610346578384fd5b6103528a838b01610291565b9096509450606089013591508082111561036a578384fd5b5061037789828a01610291565b979a9699509497509295939492505050565b60006060820185835260206060818501528186518084526080860191508288019350845b818110156103c9578451835293830193918301916001016103ad565b5050848103604086015285518082528282019350600581901b82018301838801865b8381101561045057601f1980868503018852825180518086528a5b81811015610421578281018a01518782018b01528901610406565b81811115610431578b8a83890101525b5098880198601f019091169390930186019250908501906001016103eb565b50909a9950505050505050505050565b6000808335601e19843603018112610476578283fd5b83018035915067ffffffffffffffff821115610490578283fd5b6020019150368190038213156102d457600080fd5b60006000198214156104c557634e487b7160e01b81526011600452602481fd5b506001019056fea264697066735822122083b5dc25b3c9256aa4244eddaf9e4b5fccd09a45ec4e0174f2c900de7144602d64736f6c63430008040033000000000000000000000000084b1c3c81545d370f3634392de611caabff8148',
  from: '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
  gasLimit: { BigNumber: "443560" },
  gasPrice: { BigNumber: "10100000000" },
  hash: '0x5b73e239c55d790e3c9c3bbb84092652db01bb8dbf49ccc9e4a318470419d9a0',
  nonce: 745,
  r: '0xaf2b969de6dfb234fb8843f47a029636abb1ef52f26bb8bb615bbabcf23808e9',
  s: '0x3dd61cd8df015e0af5689a249dd3224ee71f2b04917b7b4c14f7e68bb3a4ec17',
  to: null,
  transactionIndex: 315,
  type: 0,
  v: 38,
  value: { BigNumber: "0" },
  wait: [Function]
}
const tx = await provider.getTransactionReceipt("0x5b73e239c55d790e3c9c3bbb84092652db01bb8dbf49ccc9e4a318470419d9a0");

{
  blockHash: '0x8a179bc6cb299f936c4fd614995e62d597ec6108b579c23034fb220967ceaa94',
  blockNumber: 12598244,
  byzantium: true,
  confirmations: 1869134,
  contractAddress: '0x733aF852514e910E2f8af40d61E00530377889E9',
  cumulativeGasUsed: { BigNumber: "12102324" },
  effectiveGasPrice: { BigNumber: "10100000000" },
  from: '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
  gasUsed: { BigNumber: "443560" },
  logs: [
    {
      address: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e',
      blockHash: '0x8a179bc6cb299f936c4fd614995e62d597ec6108b579c23034fb220967ceaa94',
      blockNumber: 12598244,
      data: '0x000000000000000000000000084b1c3c81545d370f3634392de611caabff8148',
      logIndex: 160,
      topics: [
        '0xce0457fe73731f824cc272376169235128c118b49d344817417c6d108d155e82',
        '0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2',
        '0x4774f6d3b3d08b5ec00115f0e2fddb92604b39e52b0dc908c6f8fcb7aa5d2a9a'
      ],
      transactionHash: '0x5b73e239c55d790e3c9c3bbb84092652db01bb8dbf49ccc9e4a318470419d9a0',
      transactionIndex: 315
    },
    {
      address: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e',
      blockHash: '0x8a179bc6cb299f936c4fd614995e62d597ec6108b579c23034fb220967ceaa94',
      blockNumber: 12598244,
      data: '0x000000000000000000000000a2c122be93b0074270ebee7f6b7292c7deb45047',
      logIndex: 161,
      topics: [
        '0x335721b01866dc23fbee8b6b2c7b1e14d6f05c28cd35a2c934239f94095602a0',
        '0x790fdce97f7b2b1c4c5a709fb6a49bf878feffcaa85ed0245f6dff09abcefda7'
      ],
      transactionHash: '0x5b73e239c55d790e3c9c3bbb84092652db01bb8dbf49ccc9e4a318470419d9a0',
      transactionIndex: 315
    }
  ],
  logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000004000000000000010000000000000020000000000000000000040000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000040000000000000000100004000000000000008000040000000000000000000000000000000000005000000000041000000000000000000000000000000000000000000000000100000000000000001000000000000000000000',
  status: 1,
  to: null,
  transactionHash: '0x5b73e239c55d790e3c9c3bbb84092652db01bb8dbf49ccc9e4a318470419d9a0',
  transactionIndex: 315,
  type: 0
}

· 이더조회

const balance = await provider.getBalance('0x86B6acf21e6F4aE60bbAdFDBc4F5D00741823d17')

BigNumber { _hex: '0x056bc75e2d63100000', _isBigNumber: true }

단위변경은 utils를 이용하면 됩니다.

· 수수료 조회

트랜잭션을 만들기 위해 필요한 수수료 조회 메서드입니다. 이는 EIP1559 방식을 기준으로 조회합니다.

const fee = await provider.getFeeData();

{
  maxFeePerGas: null,
  maxPriorityFeePerGas: null,
  gasPrice: BigNumber { _hex: '0x04a817c800', _isBigNumber: true }
}

maxFeePerGas와 maxPriorityFeePerGas 모두 gasPrice와 같은 데이터 타입을 가집니다.

· 수수료 추청

트랜잭션이 정상적으로 실행되었을 때 사용되는 가스량 추청을 할 수 있습니다.

await provider.estimateGas({
  to: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",   // Wrapped ETH address
  data: "0xd0e30db0",   // `function deposit() payable`
  value: parseEther("1.0")   // 1 ether
});

{ BigNumber: "27938" }

· nonce 조회

nonce란 발생된 트랜잭션 수 입니다.

const address = '0x86B6acf21e6F4aE60bbAdFDBc4F5D00741823d17';
const nonce = await provider.getTransactionCount(address, "latest")

0

· 트랜잭션 발생

const signedTx = "0xf8690401825208945555763613a12d8f3e73be831dff8598089d3dca882b992b75cbeb600080820a96a0e3767d1e99c6090a5d00532fe11e2acf3be9228f1f8df7ce9d3c5c6ca3dac151a0594869ee73fbf7394c82e080b1bc734536b67832d2abab5b7bedc79f297bb338";
await provider.sendTransaction(signedTx);

{
  chainId: 1337,
  confirmations: 0,
  data: '0x',
  from: '0x77C44C0D1D37050e9250415Ee96401B5ac270856',
  gasLimit: { BigNumber: "21000" },
  gasPrice: { BigNumber: "1" },
  hash: '0x1c392c05e5f293ce4e1367101233513ce09575c67d7f3ee519d59c2d22b9dde9',
  nonce: 4,
  r: '0xe3767d1e99c6090a5d00532fe11e2acf3be9228f1f8df7ce9d3c5c6ca3dac151',
  s: '0x594869ee73fbf7394c82e080b1bc734536b67832d2abab5b7bedc79f297bb338',
  to: '0x5555763613a12D8F3e73be831DFf8598089d3dCa',
  type: null,
  v: 2710,
  value: { BigNumber: "3141590000000000000" },
  wait: [Function]
}

서명된 트랜잭은 다음과 같이 만들 수 있습니다.

const signer = Wallet.createRandom()
const signed = await signer.signTransaction({
  to: '0x6BAE09696c44D0fcF157Cf72b1930632AF435A97',
  from: signer.address,
  value: utils.parseEther('0.1'),
})

await provider.sendTransaction(signedTx);

또한 sendTransaction은 서명된 문자열이 아닌 오브젝트를 받을 수 있습니다. 이때 from은 생성된 지갑주소가 들어갑니다. wallet을 생성하고 니모닉, 키스토어 파일, 개인키를 이용하여 복구하는 방법은 아래에서 다룹니다.

const signer = Wallet.createRandom()

const txHash = await signer.sendTransaction({
  to: '0xAd46355359aE32263EaFE152a408D9D620844eda',
  value: utils.parseUnits('0.1', 'ether').toHexString()
})

· 이벤트 수신

provider.on('block', (block) => {
  console.log(block)
})

1
2
3

해당 네트워크에 블록이 생성되면 생성된 블록번호를 전달받습니다.

provider.on("pending", (tx) => {
  console.log(tx)
});

{
  hash: '0x5a224e5f2ab73bfbe382dda2260adb8c5e2957606991b8b9b2ab7c33324c8323',
  type: 0,
  accessList: null,
  blockHash: '0xb13106233b4173006fda0e39bd2af24264d49f1b88f2f828b0b4f1fd3fee77b6',
  blockNumber: 9,
  transactionIndex: 0,
  confirmations: 1,
  from: '0x86B6acf21e6F4aE60bbAdFDBc4F5D00741823d17',
  gasPrice: BigNumber { _hex: '0x04a817c800', _isBigNumber: true },
  gasLimit: BigNumber { _hex: '0x011a59', _isBigNumber: true },
  to: null,
  value: BigNumber { _hex: '0x00', _isBigNumber: true },
  nonce: 8,
  data: '0x60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea264697066735822122024e006b709493803f07277c09b5c327910ed1595e233a5d24959eacff659640364736f6c634300080d0033',
  r: '0xa93dc61c988f92aba88db265bdcd03e8412066c2d2b409c97b34ab8da882aa1a',
  s: '0x71babaf613b91c91a6979661dae2f28dfb1d5e7f9e9c2abbac7d84f5e286a516',
  v: 38,
  creates: '0x85E8E89867bc699278Da7276d32E4fd856B3Afc8',
  chainId: 1,
  wait: [Function (anonymous)]
}

팬딩중인 트랜잭션을 수신합니다.

● Wallet

Wallet은 개인키/공개키를 관리합니다.

· 키(주소) 생성

const signer = Wallet.createRandom()

console.log(signer)
console.log(signer._signingKey())
console.log(signer._mnemonic())

Wallet {
  _isSigner: true,
  _signingKey: [Function (anonymous)],
  address: '0xf3a1F20176cd7F877737012ca8FCd3e32B5bbc81',
  _mnemonic: [Function (anonymous)],
  provider: null
}
SigningKey {
  curve: 'secp256k1',
  privateKey: '0xc9ea026885ea45fd27d06049614bc1e6474fd5208dcd008a25e4f5c8297a11fd',
  publicKey: '0x0462c4b51b0a8623510168d1ac42d8731bbd38b6aa3c037b96782b987e76d695913efa32b8552d42c51fd7dc2c6ff06c8e17d75beeda5e55fa7da955850fe50ef4',
  compressedPublicKey: '0x0262c4b51b0a8623510168d1ac42d8731bbd38b6aa3c037b96782b987e76d69591',
  _isSigningKey: true
}
{
  phrase: 'clown unveil parrot rebuild trial convince box glass gun trumpet garbage electric',
  path: "m/44'/60'/0'/0/0",
  locale: 'en'
}

create 메서드를 이용하면 키스토어를 생성할 수 있습니다.

const password = '123456'
const signer = Wallet.createRandom()
const keystore = await signer.encrypt(password);
console.log(keystore)

{"address":"84db1f6c4b16cda79f4dc34404445fabe55b97bf","id":"fb49150e-baa5-4325-a3c1-4f8547c118cb","version":3,"Crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"39fe92c0b9ccba464e25e63686695f05"},"ciphertext":"476c18b348830e566c815df1669e281e45777e282494deea857425864b778421","kdf":"scrypt","kdfparams":{"salt":"a4f06b8ee274dbb763e540cf93d3cded6ceaa43a928381c463767b7e2ee76f33","n":131072,"dklen":32,"p":1,"r":8},"mac":"3aed307763f9e53e5f3bd9533922546c554fbf8db20608c99a08103c18d7e924"},"x-ethers":{"client":"ethers.js","gethFilename":"UTC--2022-04-17T02-06-38.0Z--84db1f6c4b16cda79f4dc34404445fabe55b97bf","mnemonicCounter":"08d67dd51426a9488c97ed1ca75f3e82","mnemonicCiphertext":"b9c25a345f6bef62ea2fc287c1cc123c","path":"m/44'/60'/0'/0/0","locale":"en","version":"0.1"}}

· 지갑복구

지갑 복구는 크게 3가지 방법이 있습니다. 개인키 복구, 키스토어 파일 복구, 니모닉 복구

▶ 개인키 복구

 
const pk = '0xe6deaed561bbeb9ebc1941b10beb9ff060060d9ea637554abe475eb8f1b13ddc';
const signer = new Wallet(pk)
console.log(signer)

Wallet {
  _isSigner: true,
  _signingKey: [Function (anonymous)],
  _mnemonic: [Function (anonymous)],
  address: '0x86B6acf21e6F4aE60bbAdFDBc4F5D00741823d17',
}

▶ 키스토어 파일 복구

 
const password = '123456'
const keystore = {"address":"84db1f6c4b16cda79f4dc34404445fabe55b97bf","id":"fb49150e-baa5-4325-a3c1-4f8547c118cb","version":3,"Crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"39fe92c0b9ccba464e25e63686695f05"},"ciphertext":"476c18b348830e566c815df1669e281e45777e282494deea857425864b778421","kdf":"scrypt","kdfparams":{"salt":"a4f06b8ee274dbb763e540cf93d3cded6ceaa43a928381c463767b7e2ee76f33","n":131072,"dklen":32,"p":1,"r":8},"mac":"3aed307763f9e53e5f3bd9533922546c554fbf8db20608c99a08103c18d7e924"},"x-ethers":{"client":"ethers.js","gethFilename":"UTC--2022-04-17T02-06-38.0Z--84db1f6c4b16cda79f4dc34404445fabe55b97bf","mnemonicCounter":"08d67dd51426a9488c97ed1ca75f3e82","mnemonicCiphertext":"b9c25a345f6bef62ea2fc287c1cc123c","path":"m/44'/60'/0'/0/0","locale":"en","version":"0.1"}}
const signer = await Wallet.fromEncryptedJson(JSON.stringify(keystore), password)
console.log(signer)

Wallet {
  _isSigner: true,
  _signingKey: [Function (anonymous)],
  address: '0x84db1f6c4B16CdA79F4dc34404445fabE55B97bF',
  _mnemonic: [Function (anonymous)],
  provider: null
}

▶ 니모닉 복구

const mnemonic = "announce room limb pattern dry unit scale effort smooth jazz weasel alcohol"
const signer = Wallet.fromMnemonic(mnemonic)
console.log(signer)

Wallet {
  _isSigner: true,
  _signingKey: [Function (anonymous)],
  address: '0x71CB05EE1b1F506fF321Da3dac38f25c0c9ce6E1',
  _mnemonic: [Function (anonymous)],
  provider: null
}

· 서명하기

서명은 트랜잭션 서명과 메시지 서명이 있습니다.

▶ 트랜잭션 서명

 
const signer = Wallet.createRandom()
const signed = await signer.signTransaction({
  to: '0x6BAE09696c44D0fcF157Cf72b1930632AF435A97',
  from: signer.address,
  value: utils.parseEther('0.1'),
})
console.log(signed)

0xf865808080946bae09696c44d0fcf157cf72b1930632af435a9788016345785d8a0000801ba03d0a131d414b315f5696c93627d188ca8651a31785f3b8d243c82476dd1ede56a00bc31b5e094d9ed1f3b503d96ed125009f03f3ea853d058255dc4484e232f9b5

▶ 메시지 서명

const signer = Wallet.createRandom()
const signed = await signer.signMessage("Hello World")
console.log(signed)

0xfd5a5ffed4227cd31d374b40f8ba59ed4702a74cbf6d6dca44f971525c71a6df4bb499d48f64012a7a42e13815351cc26d8fb8baef5e9a821de8db3836b84f751b

· Provider 연결

트랜잭션을 발생하기 위해선 Provider를 가지고 있어야 합니다.

const provider = new providers.JsonRpcProvider(url);

const signer = Wallet.createRandom()
const signed = await signer.signMessage("Hello World")

const wallet = signer.connect(provider)

· 이더리움 조회, nonce 조회, 트랜잭션 발생

await wallet.getBalance();
 { BigNumber: "42" }

await wallet.getTransactionCount();
 0

// Sending ether
await wallet.sendTransaction(tx)
{
  chainId: 1337,
  confirmations: 0,
  data: '0x',
  from: '0x77C44C0D1D37050e9250415Ee96401B5ac270856',
  gasLimit: { BigNumber: "21000" },
  gasPrice: { BigNumber: "1" },
  hash: '0x5037de21cbba9fbab45b779a8d4e52c610c06bb236f6e38df90a3a69f457aa12',
  nonce: 5,
  r: '0x62623cd493f7dc2a433c5bfa3b0d6802f498220c0ba1e3af0b9851ac8bfe8fe6',
  s: '0x140682e759a02e5a6d68087ff3d531fbdacb9b6ddd637a498c83507cfe347790',
  to: '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
  type: null,
  v: 2709,
  value: { BigNumber: "1000000000000000000" },
  wait: [Function]
}

● utils

utils는 이더 단위를 바꾸거나 하는 메서드를 제공합니다. 다음 내용은 ethers.utils가 export 한 목록입니다.

 
export {
    AbiCoder,
    defaultAbiCoder,

    Fragment,
    ConstructorFragment,
    ErrorFragment,
    EventFragment,
    FunctionFragment,
    ParamType,
    FormatTypes,

    checkResultErrors,
    Result,

    Logger,

    RLP,

    _fetchData,
    fetchJson,
    poll,

    checkProperties,
    deepCopy,
    defineReadOnly,
    getStatic,
    resolveProperties,
    shallowCopy,

    arrayify,

    concat,
    stripZeros,
    zeroPad,

    isBytes,
    isBytesLike,

    defaultPath,
    HDNode,
    SigningKey,

    Interface,

    LogDescription,
    TransactionDescription,

    base58,
    base64,

    hexlify,
    isHexString,
    hexConcat,
    hexStripZeros,
    hexValue,
    hexZeroPad,
    hexDataLength,
    hexDataSlice,

    nameprep,
    _toEscapedUtf8String,
    toUtf8Bytes,
    toUtf8CodePoints,
    toUtf8String,
    Utf8ErrorFuncs,

    formatBytes32String,
    parseBytes32String,

    dnsEncode,
    hashMessage,
    namehash,
    isValidName,
    id,

    _TypedDataEncoder,

    getAddress,
    getIcapAddress,
    getContractAddress,
    getCreate2Address,
    isAddress,

    formatEther,
    parseEther,

    formatUnits,
    parseUnits,

    commify,

    computeHmac,
    keccak256,
    ripemd160,
    sha256,
    sha512,

    randomBytes,
    shuffled,

    solidityPack,
    solidityKeccak256,
    soliditySha256,

    splitSignature,
    joinSignature,

    accessListify,
    parseTransaction,
    serializeTransaction,
    TransactionTypes,

    getJsonWalletAddress,

    computeAddress,
    recoverAddress,

    computePublicKey,
    recoverPublicKey,

    verifyMessage,
    verifyTypedData,

    getAccountPath,
    mnemonicToEntropy,
    entropyToMnemonic,
    isValidMnemonic,
    mnemonicToSeed,


    ////////////////////////
    // Enums

    SupportedAlgorithm,

    UnicodeNormalizationForm,
    Utf8ErrorReason,

    ////////////////////////
    // Types

    Bytes,
    BytesLike,
    Hexable,

    AccessList,
    AccessListish,
    UnsignedTransaction,

    CoerceFunc,

    Indexed,

    Mnemonic,

    Deferrable,

    Utf8ErrorFunc,

    ConnectionInfo,
    OnceBlockable,
    OncePollable,
    PollOptions,
    FetchJsonResponse,

    EncryptOptions,
    ProgressCallback
}

· 단위변경

balance = await provider.getBalance("ethers.eth")
{ BigNumber: "82826475815887608" }

// wei -> ether
utils.formatEther(balance)
'0.082826475815887608'

// ether -> wei
utils.parseEther("1.0")
{ BigNumber: "1000000000000000000" }

// 특정 단위적용
utils.parseUnits('5', 'gwei')
BigNumber { _hex: '0x012a05f200', _isBigNumber: true }
0.000000005 Ether

· 주소 유효성 검사

console.log(utils.isAddress('0x86B6acf21e6F4aE60bbAdFDBc4F5D00741823d17'))
console.log(utils.isAddress('0x86B6acf21e6F4aE6bAdFDBc4F5D00741823d17'))

true
false

 

● Contract

Contract는 트랜잭션을 발생하고 컨트랙트에서 관리하는 상태를 조회할 수 있습니다.

pragma solidity 0.8.13;
 
contract test {
  uint public a = 10;

  event onEvent1(uint indexed a);
  event onEvent2(uint a);
  
  function event1() public {
    emit onEvent1(1);
  }
  
  function event2() public {
    emit onEvent2(2);
  }

  function getA() view public returns (uint) {
    return a;
  }

  function setA(uint _a) public {
    a = _a;
  }
}
CA: 0xFc0D07749d61479727d5EE0743D4d03f4C5Eb3B0

· 컨트랙트 객체 생성

const ABI = [
  {
    "inputs": [],
    "name": "event1",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "event2",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "a",
        "type": "uint256"
      }
    ],
    "name": "onEvent1",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "a",
        "type": "uint256"
      }
    ],
    "name": "onEvent2",
    "type": "event"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "_a",
        "type": "uint256"
      }
    ],
    "name": "setA",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "a",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "getA",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
];
const signer = new Wallet(
  '0xe6deaed561bbeb9ebc1941b10beb9ff060060d9ea637554abe475eb8f1b13ddc',
  provider
)
const contract = new Contract(
  '0xFc0D07749d61479727d5EE0743D4d03f4C5Eb3B0',
  ABI,
  signer
)
const rst0 = await contract.getA();
const rst1 = await contract.a();
console.log(rst0)
console.log(rst1)

BigNumber { _hex: '0x0a', _isBigNumber: true }
BigNumber { _hex: '0x0a', _isBigNumber: true }

ethers는 ABI를 다음과 같이 정의할 수 있습니다.

const ABI = [
  'event onEvent1(uint indexed a)',
  'event onEvent2(uint a)',
  'function getA() view public returns (uint)',
  'function a() view public returns (uint)',
  'function setA(uint _a) public'
];
const signer = new Wallet(
  '0xe6deaed561bbeb9ebc1941b10beb9ff060060d9ea637554abe475eb8f1b13ddc',
  provider
)
const contract = new Contract(
  '0xFc0D07749d61479727d5EE0743D4d03f4C5Eb3B0',
  ABI,
  signer
)
const rst0 = await contract.getA();
const rst1 = await contract.a();
console.log(rst0)
console.log(rst1)

Contract 객체를 만들 때 signer가 아니라 provider를 전달할 수 있는데 signer(Wallet)를 전달하지 않으면 읽기전용입니다.

· 트랜잭션 발생

const ABI = [
  'event onEvent1(uint indexed a)',
  'event onEvent2(uint a)',
  'function getA() view public returns (uint)',
  'function a() view public returns (uint)',
  'function setA(uint _a) public'
];
const signer = new Wallet(
  '0xe6deaed561bbeb9ebc1941b10beb9ff060060d9ea637554abe475eb8f1b13ddc',
  provider
)
const contract = new Contract(
  '0xFc0D07749d61479727d5EE0743D4d03f4C5Eb3B0',
  ABI,
  signer
)
const rst0 = await contract.setA(123);
console.log(rst0)

const rst1 = await contract.a();
console.log(rst1)

{
  nonce: 10,
  gasPrice: BigNumber { _hex: '0x04a817c800', _isBigNumber: true },
  gasLimit: BigNumber { _hex: '0x682c', _isBigNumber: true },
  to: '0xFc0D07749d61479727d5EE0743D4d03f4C5Eb3B0',
  value: BigNumber { _hex: '0x00', _isBigNumber: true },
  data: '0xee919d50000000000000000000000000000000000000000000000000000000000000007b',
  chainId: 1337,
  v: 2709,
  r: '0x229c8022469d2a4cf0123e10b6b109b038545619db3f861ee28859d1d492fd34',
  s: '0x00817868e24ce66cc00ebfbe1f2a86fabba40d43bebafc15f78a73497a02d680',
  from: '0x86B6acf21e6F4aE60bbAdFDBc4F5D00741823d17',
  hash: '0xb09ab043d685e55312f95d786c37f81eb8afb70fa8fe9e63894b0dcc96cd76e9',
  type: null,
  confirmations: 0,
  wait: [Function (anonymous)]
}
BigNumber { _hex: '0x7b', _isBigNumber: true }

· 이벤트 수신

contract.on('onEvent1', (a) => {
  console.log(a)
})
contract.on('onEvent2', (a) => {
  console.log(a)
})

이벤트를 발생하는 함수를 호출하면 각각의 이벤트 리스터를 호출합니다.