# Web3-Core

## Introduction
Web3-Core offers essential functionalities to facilitate user interactions with EVM-compatible networks. It locally configures assets and chain information, encapsulating transactions throughout their entire lifecycle. The output is presented in a user-friendly format, encompassing parsed function calls, identified addresses, transaction data, emitted events, errors, and more. Interaction with the web3 world begins here!

## Installation

```shell
yarn add @derivation-tech/web3-core
```

## Getting started with a ChainContext instance

### Initialization
Simplify initialization using the network name or ID in the following TypeScript code:
```typescript
  // Initialization by chain ID
  const ctx = ChainContext.getInstance(5);
  // Initialization by chain name
  const ctx = ChainContext.getInstance('goerli');
```


Web3-Core streamlines the initialization of JsonRpc/WebSocket provider instances by retrieving endpoint information from environment variables: {CHAIN_NAME_UPPERCASE}_RPC and {CHAIN_NAME_UPPERCASE}_WSS, respectively. For instance, if you are working on Goerli network, ensure you configure GOERLI_RPC and, optionally, GOERLI_WSS in your environment variables (you can also specify them in a .env file).


```env
GOERLI_RPC=https://goerli.infura.io/v3/77e667fxxx95476299975383d80a7ce3
# optional
GOERLI_WSS=wss://goerli.infura.io/ws/v3/77e667fxxx5476299975383d80a7ce3
```
if the rpc endpoint need authentication, you can also set {CHAIN_NAME_UPPERCASE}_RPC_AUTH in your environment variables, for example, `GOERLI_RPC_AUTH=xxx:yyy`

```env
GOERLI_RPC_AUTH=userName:password
# optional
GOERLI_WSS_AUTH=userName:password
```

#### ChainContext is a singleton
The ChainContext is designed following the [singleton pattern](https://en.wikipedia.org/wiki/Singleton_pattern), enabling you to access it seamlessly throughout your project by invoking ChainContext.getInstance(). Subsequent calls with the same network parameter will consistently yield the same instance.

```typescript
const ctx1 = ChainContext.getInstance('goerli');
const ctx2 = ChainContext.getInstance('goerli');
const ctx3 = ChainContext.getInstance('goerli');

console.log(ctx1 === ctx2); // true
console.log(ctx2 === ctx3); // true
```

#### Prepare Local Network

For local network, you should call `prepareLocalNetwork` before getting instance.

```typescript
await prepareLocalNetwork();
const ctx = ChainContext.getInstance('local');
```
This is because `wrappedNativeToken`, `tokenAssistant` and `multicall3` and some popular ERC20 tokens are essential contracts for `ChainContext` to work.
These essential contracts will be deployed automatically during the `prepareLocalNetwork` process.

Again, once you have called `await prepareLocalNetwork()`, you can do `ChainContext.getInstance('local')` anywhere, which returns the same instance.

### Customize Call Options
Also, Web3-Core provides a powerful way to customize your transaction request, you can specify the following options to meet your needs.

```typescript
export interface CallOption {
    // tx.wait or not
    waitReceipt?: boolean;
    // wait timeout in seconds
    waitTimeout?: number;
    // gas limit scaler, default: 1.5
    gasLimitScaler?: number;
    // estimate gas or not
    estimateGas?: boolean;
    // gas price scaler, default: 1.2
    // for legacy tx, gasPrice = gasPrice * gasPriceScaler
    // for EIP-1559 tx, maxFeePerGas = maxFeePerGas * gasPriceScaler
    gasPriceScaler?: number;
    // gas estimator is used for estimating gas price
    gasEstimator?: EthGasEstimator;
}
```
The default call option is:

```typescript
export const DEFAULT_CALL_OPTION: CallOption = {
    waitReceipt: true,
    waitTimeout: 3 * 60,
    estimateGas: true,
    gasLimitScaler: 1.5,
    gasPriceScaler: 1.2,
};
```
By the way, If you want to customize your own gas estimator, simply implement the EthGasEstimator interface and pass the instance through callOption.

### Obtaining Signers
Web3-Core provides three straightforward methods for obtaining signers: one involves configured private keys, another entails using a mnemonic, and the last option is to acquire a signer from a Ledger wallet.

- Get Signers by Private Keys

To obtain signers using private keys, set {CUSTOM_IDENTIFIER}_PRIVATE_KEY in your environment variables (excluding the '0x' prefix) and then call ctx.getSigner({CUSTOM_IDENTIFIER}). For example:


```env
ALICE_PRIVATE_KEY=abababababab...
```

```typescript
const ctx = ChainContext.getInstance('goerli');
const signer = await ctx.getSigner('alice'); // not case-sensitive. 'ALICE' do the same thing.
```

- Get Signers by Mnemonic


To obtain signers using a mnemonic, set {CUSTOM_IDENTIFIER}_MNEMONIC in your environment variables. For example,


```env
BOB_MNEMONIC=how are you fine thank you and you
```

```typescript
const ctx = ChainContext.getInstance('goerli');
// get first 3 wallets derived by BOB_MNEMONIC with DEFAULT HD_PATH
const signer0 = await ctx.getSigner('BOB:0');
const signer1 = await ctx.getSigner('BOB:1');
const signer2 = await ctx.getSigner('BOB:2');
// get the forth wallet derived from BOB_MNEMONIC with HD_PATH: `m/44'/60'/0'/0/`
const signer3 = await ctx.getSigner(`bob:m/44'/60'/0'/0/3`);
```


Besides, you can config HD_PATH in environment variables as follows: {CUSTOM_IDENTIFIER}_{CHAIN_NAME_UPPERCASE}_HD_PATH, eg: `DAVID_GOERLI_HD_PATH=m/44'/60'/0'/1`

```typescript
const ctx = ChainContext.getInstance('goerli');
const signer0 = await ctx.getSigner('DAVID:0');
```


- Get Signers from Ledger wallet

Thus,ledger is a key word, it is specifically used for Ledger wallet.

```typescript
const ctx = ChainContext.getInstance('goerli');
const ledgerSigner0 = await ctx.getSigner('ledger:1');
const ledgerSigner12 = await ctx.getSigner("ledger:m/44'/60'/0'/0/12")
```

### Basic Chain Info
ChainInfo interface provides comprehensive details about the blockchain, including the chain ID, name, aliases, default transaction type, native and wrapped native token information, explorer URL, token assistant URL, multicall3 URL, and a list of ERC20 tokens.

Definition of the ChainInfo interface
```typescript
export interface ChainInfo {
    chainId: CHAIN_ID;
    chainName: string;
    chainAlias: string[];
    defaultTxType: string;
    nativeToken: TokenInfo;
    wrappedNativeToken: TokenInfo;
    explorer: string;
    tokenAssistant: string;
    multicall3: string;
    erc20: TokenInfo[];
}
```

Example usage to retrieve chain information for the Goerli network:
```typescript
const ctx = ChainContext.getInstance('goerli');
const info = ctx.info;
```

### Asset Information
Obtain ERC20 or native token information by symbol or address:
If the parameter is a symbol, the search is conducted locally. If it is an address, the local search is performed first. If the information is not found locally, an attempt is made to query on-chain.

```typescript
const ctx = ChainContext.getInstance('goerli');
// By symbol
await ctx.getTokenInfo('USDC');
// By address
await ctx.getTokenInfo('0xA375A26dbb09F5c57fB54264f393Ad6952d1d2de');
```

This functionality allows you to seamlessly retrieve information about ERC20 or native tokens using either their symbols or addresses, with a preference for local data when available.


### Transactions

- Execute an ERC20 transfer using sendTx

```typescript
const ctx = ChainContext.getInstance('goerli');
const erc20Info = await ctx.getTokenInfo('USDC');
const signer = await ctx.getSigner('alice');
const contract = ERC20__factory.connect(erc20Info.address, signer);
const target = '0x0E038F13d9D5732223cF9b4b61Eed264ccd44641';
// Set the address and its name if you want to identify it in the transaction
ctx.registerAddress(target, 'Alice');
// Optional: If not set, the default parser will be used, and function and event arguments will be parsed as origin
ctx.registerContractParser(contract.address, new ERC20Parser(ERC20__factory.createInterface(), erc20Info));

const unsignedTx = await contract.populateTransaction.transfer(
    target,
    ethers.utils.parseUnits('4', erc20Info.decimals)
);
await ctx.sendTx(signer, unsignedTx);
```

### Parsers

- Register parsers

Parsers play a crucial role in parsing function calls, events, and errors. They are associated with addresses, and Web3-Core locates the relevant parser by searching for the address in the parser map. You can register a parser using the following syntax:


```typescript
ctx.registerContractParser(erc20Address, new ERC20Parser(ERC20__factory.createInterface(), erc20Info));
```

Additionally, you can register an address for the Web3-core to idenitfy it:

```typescript
ctx.registerAddress(target, 'Alice');
```


- Customize Parsers

Parsers are optional; if not set, the default parser will be used, and function and event arguments will be parsed as origin. If you wish to customize your parser, you only need to extend the base class: ContractParser and re-implement function parseBaseParam. Now, let me provide you with an example of how an ERC20 parser is implemented.


```typescript
export class Erc20Parser extends ContractParser {
    tokenInfo: TokenInfo;

    constructor(
        iface: ethers.utils.Interface,
        tokenInfo: TokenInfo,
        addressParser?: (address: string) => Promise<string>,
    ) {
        super(iface, addressParser);
        this.tokenInfo = tokenInfo;
    }

    override async parseBaseParam(
        description: TransactionDescription | LogDescription | ErrorDescription,
        paramType: ethers.utils.ParamType,
        value: BigNumber | number,
    ) {
        switch (paramType.type) {
            case 'uint256':
                // different erc20 handles maxed approval differently:
                // some reduce the approval gradually as you spend, while others simply check and maintain the maximum approval during spending
                // for better human readability, we will simply show MAX if the approval is greater than MAX_UINT256/2
                if (
                    (description.name.toLowerCase() === 'approve' || description.name.toLowerCase() === 'approval') &&
                    BigNumber.from(value).gte(MAX_UINT256.div(2))
                ) {
                    return 'MAX';
                }
                return `${formatUnits(value, this.tokenInfo.decimals)} ${this.tokenInfo.symbol}`;
            default:
                return value.toString();
        }
    }
}
```

### Supported Networks
- ETHEREUM
- ARBITRUM
- OPTIMISM
- BASE
- MANTLE
- LINEA
- BSC
- CONFLUX
- POLYGON
- POLYGON_ZKEVM
- SCROLL
- ZKSYNC_ERA
- MAPO
- KLAYTN
- GOERLI
- SEPOLIA
- BERA_ARTIO
