Repository: Conflux-Chain/python-conflux-sdk (language: python)

Issue:
Fix web3 contract constructor build_transaction compatibility problem
Fix web3 contract constructor build_transaction compatibility problem
Add 2 examples

<!-- Reviewable:start -->
- - -
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/conflux-chain/python-conflux-sdk/5)
<!-- Reviewable:end -->



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 1f6992c..7e4f3cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@ tests/__pycache__
 venv
 conflux/__init__.pyc
 conflux.egg-info
+conflux_web3.egg-info
 bin/
 lib/
 include/
diff --git a/CHANGELOGS.md b/CHANGELOGS.md
new file mode 100644
index 0000000..f0e0d7a
--- /dev/null
+++ b/CHANGELOGS.md
@@ -0,0 +1,16 @@
+# Change Logs
+
+## 1.0.0-dev2
+
+* Optimizes web3.py hacking mechanisms
+  * hacking codes are moved to `conflux_web3._hook.py`
+* Basic support for ethpm (furthur supports are not in plan)
+* The default account will be added to wallet if `w3.cfx.default_account` setter receives a `LocalAccount` object
+  * `w3.cfx.default_account` is still a `Base32Address`
+* `.finalized()` apis are tests.
+* Fix contract constructor's `build_transaction()` bugs
+* Add `name` parameter to `w3.cfx.contract()`
+  * supports internal contracts including `AdminControl`, `SponsorWhitelistControl`, `Staking`, `PoSRegister`, `CrossSpaceCall`, `ParamsControl`
+  * supports specific contracts added in genesis block, including `Create2Factory`, `ERC1820`
+  * supports `Faucet` in testnet
+  * supports `cUSDT` and `FC` in mainnet and testnet
diff --git a/Makefile b/Makefile
index bad0da9..85e6ee8 100644
--- a/Makefile
+++ b/Makefile
@@ -17,5 +17,5 @@ publish:
 # 	make html
 
 test:
-	pytest tests 
+	pytest tests && export USE_TESTNET=1 && pytest tests
 # cd ./docs && make doctest
diff --git a/cfxpm/README.md b/cfxpm/README.md
new file mode 100644
index 0000000..b9f57b8
--- /dev/null
+++ b/cfxpm/README.md
@@ -0,0 +1,3 @@
+## cfxpm
+
+web3.py's ethpm is implemented with the instructions from ERC1319. However, noted that ERC-1319 is not a finalized ERC, we only provide very basic support for it.
diff --git a/cfxpm/__init__.py b/cfxpm/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/cfxpm/contract.py b/cfxpm/contract.py
new file mode 100644
index 0000000..2a809a3
--- /dev/null
+++ b/cfxpm/contract.py
@@ -0,0 +1,65 @@
+from typing import (  # noqa: F401
+    TYPE_CHECKING,
+    Any,
+    Optional,
+)
+from eth_utils.toolz import (
+    assoc, # type: ignore
+)
+from eth_utils import (
+    to_bytes, # type: ignore
+)
+from ethpm.contract import (
+    LinkableContract
+)
+from ethpm.exceptions import (
+    BytecodeLinkingError,
+)
+from ethpm.contract import (
+    is_prelinked_bytecode,
+    
+)
+from conflux_web3.contract import (
+    ConfluxContract,
+    ConfluxContractConstructor,
+)
+
+if TYPE_CHECKING:
+    from conflux_web3 import Web3
+
+# totally borrows the implementation of LinkableContract except for base class
+# used to hack ethpm Package
+# hack activated in conflux_web3._hook
+
+# the implementation below does not work because super() won't work
+# ConfluxLinkableContract = type(
+#     'LinkableContract', (ConfluxContract, ), dict(LinkableContract.__dict__)
+# )
+class ConfluxLinkableContract(LinkableContract, ConfluxContract):
+    def __init__(self, address: bytes, **kwargs: Any) -> None:
+        if self.needs_bytecode_linking:
+            raise BytecodeLinkingError(
+                "Contract cannot be instantiated until its bytecode is linked."
+            )
+
+        # type ignored to allow for undefined **kwargs on `Contract` base class __init__
+        super().__init__(address=address, **kwargs)  # type: ignore
+
+    @classmethod
+    def factory(cls, w3: "Web3", class_name: Optional[str] = None, **kwargs: Any) -> ConfluxContract:
+        dep_link_refs = kwargs.get("unlinked_references")
+        bytecode = kwargs.get("bytecode")
+        needs_bytecode_linking = False
+        if dep_link_refs and bytecode:
+            if not is_prelinked_bytecode(to_bytes(hexstr=bytecode), dep_link_refs):
+                needs_bytecode_linking = True
+        kwargs = assoc(kwargs, "needs_bytecode_linking", needs_bytecode_linking)
+        return super().factory(w3, class_name, **kwargs) # type: ignore
+
+    @classmethod
+    def constructor(cls, *args: Any, **kwargs: Any) -> ConfluxContractConstructor:
+        if cls.needs_bytecode_linking:
+            raise BytecodeLinkingError(
+                "Contract cannot be deployed until its bytecode is linked."
+            )
+        return super().constructor(*args, **kwargs) # type: ignore
diff --git a/cfxpm/package.py b/cfxpm/package.py
new file mode 100644
index 0000000..1c70d75
--- /dev/null
+++ b/cfxpm/package.py
@@ -0,0 +1,20 @@
+# Note that ERC1319 is not a finalized standard. We only provide VERY BASIC support for it
+from typing import (
+    TYPE_CHECKING,
+)
+from cfxpm.contract import ConfluxLinkableContract
+
+from ethpm.package import Package as EthPackage
+
+if TYPE_CHECKING:
+    from conflux_web3 import Web3
+
+# used to hook Package
+def set_conflux_linkable_contract(self: "Package", *args, **kwargs):
+    self.w3.cfx.defaultContractFactory = ConfluxLinkableContract
+
+class Package(EthPackage):
+    w3: "Web3"
+        
+    def get_contract_factory(self, name: str) -> ConfluxLinkableContract:
+        return super().get_contract_factory(name) # type: ignore
diff --git a/conflux_web3/__init__.py b/conflux_web3/__init__.py
index 283c019..343e1fa 100644
--- a/conflux_web3/__init__.py
+++ b/conflux_web3/__init__.py
@@ -1,3 +1,6 @@
+# should be placed at the most front
+import conflux_web3._hook
+
 from conflux_web3.main import Web3
 from conflux_web3.dev import (
     get_local_web3,
diff --git a/conflux_web3/_hook.py b/conflux_web3/_hook.py
new file mode 100644
index 0000000..1e790ea
--- /dev/null
+++ b/conflux_web3/_hook.py
@@ -0,0 +1,30 @@
+from cfx_utils.post_import_hook import when_imported
+from conflux_web3._utils.decorators import (
+    cfx_web3_condition,
+    conditional_func,
+    conditional_post_func,
+)
+
+@when_imported("web3._utils.contracts")
+def hook_encode_abi(mod):
+    from conflux_web3._utils.contracts import cfx_encode_abi
+    mod.encode_abi = conditional_func(
+        cfx_encode_abi,
+        cfx_web3_condition
+    )(mod.encode_abi)
+
+@when_imported("web3.contract")
+def hook_parse_block_identifier(mod):
+    from conflux_web3.contract import cfx_parse_block_identifier
+    mod.parse_block_identifier = conditional_func(
+        cfx_parse_block_identifier,
+        cfx_web3_condition
+    )(mod.parse_block_identifier)
+    
+@when_imported("ethpm.package")
+def hook_ethpm_package(mod):
+    from cfxpm.package import set_conflux_linkable_contract
+    mod.Package.__init__ = conditional_post_func(
+        set_conflux_linkable_contract,
+        cfx_web3_condition
+    )(mod.Package.__init__)
diff --git a/conflux_web3/_utils/abi.py b/conflux_web3/_utils/abi.py
index 868d42f..61c7a23 100644
--- a/conflux_web3/_utils/abi.py
+++ b/conflux_web3/_utils/abi.py
@@ -10,10 +10,10 @@
     build_default_registry, 
     AddressEncoder,
 )
-from conflux_web3._utils.validation import (
+from cfx_address import (
+    Base32Address,
     validate_base32
 )
-from cfx_address import Base32Address
 
 class Base32AddressEncoder(AddressEncoder):
     
diff --git a/conflux_web3/_utils/contracts.py b/conflux_web3/_utils/contracts.py
index 77d71e1..b68f430 100644
--- a/conflux_web3/_utils/contracts.py
+++ b/conflux_web3/_utils/contracts.py
@@ -24,14 +24,11 @@
     abi_string_to_text,
 )
 
-from conflux_web3._utils.decorators import (
-    cfx_web3_condition,
-    conditional_func
-)
-
 if TYPE_CHECKING:
     from conflux_web3 import Web3
 
+# this api is used to hook web3._utils.contracts.encode_abi
+# hook is activated in conflux_web3._hook
 def cfx_encode_abi(
     web3: "Web3", abi: ABIFunction, arguments: Sequence[Any], data: Optional[HexStr] = None
 ) -> HexStr:
@@ -69,11 +66,5 @@ def cfx_encode_abi(
         return to_hex(HexBytes(data) + encoded_arguments)
     else:
         return encode_hex(encoded_arguments)
-    
-# hack the encode_abi function of _util.contracts module
-contracts.encode_abi = conditional_func(
-    cfx_encode_abi,
-    cfx_web3_condition
-)(contracts.encode_abi)
 
 prepare_transaction = contracts.prepare_transaction
diff --git a/conflux_web3/_utils/decorators.py b/conflux_web3/_utils/decorators.py
index 462aade..34be218 100644
--- a/conflux_web3/_utils/decorators.py
+++ b/conflux_web3/_utils/decorators.py
@@ -49,6 +49,29 @@ def wrapper(*args, **kwargs):
         return wrapper
     return inner
 
+def conditional_post_func(post_func: Callable, condition: Callable[..., bool]) -> Callable:
+    """decorate a function to optionally execute post operation
+    rtn = original_func
+    if condition:
+        post_func
+    return rtn
+
+    Args:
+        post_func (Callable): function to be executed after original function if condition
+        condition (Callable[..., bool]): receives func arguments, returns a bool
+    """
+    def inner(func) :
+        def wrapper(*args, **kwargs):
+            rtn = func(*args, **kwargs)
+            if condition(*args, **kwargs):
+                post_func(*args, **kwargs)
+                print("do post!")
+            else:
+                print("do not post")
+            return rtn
+        return wrapper
+    return inner
+
 def cfx_web3_condition(*args, **kwargs) -> bool:
     """
 
diff --git a/conflux_web3/_utils/disabled_eth_apis.py b/conflux_web3/_utils/disabled_eth_apis.py
index 8cc1be6..da55575 100644
--- a/conflux_web3/_utils/disabled_eth_apis.py
+++ b/conflux_web3/_utils/disabled_eth_apis.py
@@ -12,7 +12,7 @@
     # "get_transaction_count",
     # "replace_transaction",
     # "modify_transaction"
-    "estimate_gas",
+    # "estimate_gas",
     "fee_history",
     "get_filter_changes",
     "get_filter_logs",
diff --git a/conflux_web3/_utils/normalizers.py b/conflux_web3/_utils/normalizers.py
deleted file mode 100644
index 377ff4e..0000000
--- a/conflux_web3/_utils/normalizers.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from conflux_web3.types import Base32Address
-from conflux_web3._utils.validation import validate_base32_address
-
-
-def normalize_address(ens: None, address: Base32Address) -> Base32Address:
-    if address:
-        validate_base32_address(address)
-    return address
diff --git a/conflux_web3/_utils/transactions.py b/conflux_web3/_utils/transactions.py
index 34375a0..40294d2 100644
--- a/conflux_web3/_utils/transactions.py
+++ b/conflux_web3/_utils/transactions.py
@@ -35,7 +35,7 @@
 
 
 @curry
-def fill_formal_transaction_defaults(w3: "Web3", transaction: TxParam) -> TxParam:
+def fill_transaction_defaults(w3: "Web3", transaction: TxParam) -> TxParam:
     """
     Fill the necessary fields to "send" a transaction
     Before this function is invoked, ensure 'from' field is filled
diff --git a/conflux_web3/_utils/validation.py b/conflux_web3/_utils/validation.py
deleted file mode 100644
index 0284820..0000000
--- a/conflux_web3/_utils/validation.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from typing import (
-    Any
-)
-from web3._utils import validation 
-from web3._utils.validation import validate_address as validate_web3_address
-from cfx_address.utils import validate_base32
-from cfx_utils.exceptions import InvalidAddress
-
-class InvalidBase32Address(ValueError):
-    """
-    The supplied address is not a valid Base32 address, as defined in CIP-37
-    """
-
-# def validate_universal_address(value: Any):
-#     try:
-#         validate_web3_address(value)
-#         return 
-#     except:
-#         try:
-#             validate_base32(value)
-#         except:
-#             raise InvalidAddress
-
-# validation.validate_address = validate_universal_address
diff --git a/conflux_web3/client.py b/conflux_web3/client.py
index f21dd5e..0145a10 100644
--- a/conflux_web3/client.py
+++ b/conflux_web3/client.py
@@ -14,10 +14,12 @@
     overload
 )
 import functools
+import warnings
 from hexbytes import HexBytes
 
-from eth_utils.toolz import (
-    keyfilter  # type: ignore
+from toolz import (
+    keyfilter,
+    merge
 )
 from eth_typing.encoding import (
     HexStr
@@ -47,7 +49,10 @@
 )
 from web3._utils.blocks import is_hex_encoded_block_hash as is_hash32_str
 
-from cfx_address import Base32Address as CfxAddress
+from cfx_address import (
+    Base32Address as CfxAddress,
+    validate_base32
+)
 from cfx_account import Account as CfxAccount
 from cfx_account.account import (
     LocalAccount
@@ -94,11 +99,11 @@
 from conflux_web3.contract import (
     ConfluxContract
 )
-from conflux_web3._utils.validation import (
-    validate_base32
+from conflux_web3.contract.metadata import (
+    get_contract_metadata
 )
 from conflux_web3._utils.transactions import (
-    fill_formal_transaction_defaults
+    fill_transaction_defaults
 )
 from conflux_web3.method import (
     ConfluxMethod
@@ -128,15 +133,13 @@ def default_account(self) -> Base32Address:
     @default_account.setter
     def default_account(self, account: Union[AddressParam, LocalAccount, Empty]) -> None:
         """set default account address
-        Args:
-            account: an address or a local account (but only address field works)
         """
-        if getattr(account, "address", None):
-            validate_base32(account.address) # type: ignore
-            self._default_account = account.address # type: ignore
+        if isinstance(account, LocalAccount):
+            self._default_account = Base32Address(account.address)
+            if (self.w3.wallet is not None and account.address not in self.w3.wallet):
+                self.w3.wallet.add_account(account)
         else:
-            validate_base32(account)
-            self._default_account = account # type: ignore
+            self._default_account = Base32Address(account) # type: ignore
     
     def remove_default_account(self):
         self._default_account = empty
@@ -144,7 +147,7 @@ def remove_default_account(self):
     def send_transaction_munger(self, transaction: TxParam) -> Tuple[TxParam]:
         if 'from' not in transaction and self.default_account :
             transaction = assoc(transaction, 'from', self.default_account)
-        transaction = fill_formal_transaction_defaults(self.w3, transaction)
+        transaction = fill_transaction_defaults(self.w3, transaction)
         return (transaction,)
     
     def estimate_gas_and_collateral_munger(
@@ -362,9 +365,17 @@ def contract(
     def contract(  # noqa: F811
         self,
         address: Optional[AddressParam] = None,
+        name: Optional[str] = None,
         **kwargs: Any,
     ) -> Union[Type[ConfluxContract], ConfluxContract]:
-        return super().contract(address, **kwargs)  # type: ignore
+        metadata = {}
+        if name is not None:
+            metadata = get_contract_metadata(name, self.chain_id) # type: ignore
+            # the latter one shares greater priority when merging
+            kwargs = merge(metadata, kwargs)
+        if address is not None:
+            kwargs["address"] = address
+        return super().contract(**kwargs)  # type: ignore
     
 
 class ConfluxClient(BaseCfx, Eth):
@@ -463,7 +474,8 @@ def epoch_number_by_tag(self, epochTag: EpochLiteral) -> EpochNumber:
     def cahched_chain_id(self) -> int:
         return self._get_status()["chainId"]
     
-    @property
+    # TODO: change to @cache after cache middleware is added
+    @functools.cached_property
     def chain_id(self) -> int:
         """We don't use functools.cached_property here in case provider changes network.
         Always get status to avoid unexpected circumstances
@@ -511,6 +523,41 @@ def get_transaction_count(self, address: Optional[AddressParam]=None, block_iden
     def estimate_gas_and_collateral(self, transaction: TxParam, block_identifier: Optional[EpochNumberParam]=None) -> EstimateResult:
         return self._estimate_gas_and_collateral(transaction, block_identifier)
 
+    def estimate_gas(self, transaction: TxParam, block_identifier: Optional[EpochNumberParam] = None) -> EstimateResult:
+        """
+        Compatibility API for conflux. Equivalent to estimate_gas_and_collateral
+
+        Parameters
+        ----------
+        transaction : TxParam
+            {
+                "chainId": int,
+                "data": Union[bytes, HexStr],
+                "from": Base32Address,
+                "gas": int,
+                "gasPrice": Drip,
+                "nonce": Nonce,
+                "to": Base32Address,
+                "value": Drip,
+                "epochHeight": int,
+                "storageLimit": int
+            }
+        block_identifier : Optional[EpochNumberParam], optional
+            _description_, by default None
+
+        Returns
+        -------
+        EstimateResult
+        
+        e.g.
+        {
+            "gasLimit": 28000,
+            "gasUsed": 21000,
+            "storageCollateralized": 0
+        }
+        """        
+        return self.estimate_gas_and_collateral(transaction, block_identifier)
+
     def send_raw_transaction(self, raw_transaction: Union[HexStr, bytes]) -> TransactionHash:
         return self._send_raw_transaction(raw_transaction)
     
@@ -618,6 +665,7 @@ def wait_till_transaction_confirmed(
     def wait_till_transaction_finalized(
         self, transaction_hash: _Hash32, timeout: float = 1200, poll_latency: float = 0.5
     ) -> TxReceipt:
+        warnings.warn("10 ~ 15 minutes are required to finalize a transaction", UserWarning)
         try:
             with Timeout(timeout) as _timeout:
                 while True:
@@ -818,7 +866,7 @@ def check_balance_against_transaction(
     
     def get_logs(self, filter_params: Optional[FilterParams]=None, **kwargs):
         if filter_params is None:
-            filter_params = keyfilter(lambda key: key in FilterParams.__annotations__.keys(), kwargs)
+            filter_params = keyfilter(lambda key: key in FilterParams.__annotations__.keys(), kwargs) # type: ignore
             return self._get_logs(filter_params)
         else:
             if len(kwargs.keys()) != 0:
diff --git a/conflux_web3/contract/__init__.py b/conflux_web3/contract/__init__.py
index fc74e46..ed794f3 100644
--- a/conflux_web3/contract/__init__.py
+++ b/conflux_web3/contract/__init__.py
@@ -5,14 +5,10 @@
     cast
 )
 
-from eth_typing.evm import (
-    ChecksumAddress
-)
 from web3.contract import (
     Contract,
 )
 
-from web3 import contract
 from web3._utils.blocks import (
     is_hex_encoded_block_hash,
 )
@@ -27,8 +23,13 @@
 from cfx_address import (
     Base32Address,
 )
+from cfx_address.utils import (
+    validate_address_agaist_network_id,
+)
+
 from cfx_utils.exceptions import (
-    InvalidEpochNumebrParam
+    InvalidEpochNumebrParam,
+    Base32AddressNotMatch,
 )
 from conflux_web3.types import (
     Base32Address,
@@ -36,28 +37,25 @@
     EpochNumberParam,
     EpochLiteral,
 )
-from conflux_web3._utils.validation import (
-    validate_base32,
-)
-from conflux_web3._utils.decorators import (
-    conditional_func,
-    cfx_web3_condition,
-)
-from .function import (
+from conflux_web3.contract.function import (
     ConfluxContractFunction,
     ConfluxContractFunctions,
 )
-from .caller import (
+from conflux_web3.contract.caller import (
     ConfluxContractCaller
 )
-from .event import (
+from conflux_web3.contract.event import (
     ConfluxContractEvents
 )
+from conflux_web3.contract.constructor import (
+    ConfluxContractConstructor
+)
 
 if TYPE_CHECKING:
     from conflux_web3 import Web3
 
-# begin hacking 
+# used to hook web3.contract.parse_block_identifier
+# hook is activated in conflux_web3._hook
 def cfx_parse_block_identifier(
     w3: "Web3", block_identifier: EpochNumberParam
 ) -> EpochNumberParam:
@@ -74,16 +72,10 @@ def cfx_parse_block_identifier(
     else:
         raise InvalidEpochNumebrParam
 
-contract.parse_block_identifier = conditional_func(
-    cfx_parse_block_identifier,
-    cfx_web3_condition
-)(contract.parse_block_identifier)
-
 
 class ConfluxContract(Contract):
     address: AddressParam
     w3: 'Web3'
-    _hex_address: ChecksumAddress
     functions: ConfluxContractFunctions
     caller: "ConfluxContractCaller"
     events: "ConfluxContractEvents"
@@ -99,12 +91,14 @@ def __init__(self, address: AddressParam) -> None:
                 '`web3.contract` interface to create your contract class.'
             )
 
+        # address should match chainId
         if address:
-            # TODO: validate is a contract address
-            # TODO: validate chainid matches
-            validate_base32(address)
-            self.address = address
-            self._hex_address = cast(ChecksumAddress, Base32Address(address).eth_checksum_address)
+            validate_address_agaist_network_id(address, self.w3.cfx.chain_id, True)
+            address = Base32Address(address, self.w3.cfx.chain_id)
+            if address.address_type != "contract" and address.address_type != "builtin":
+                raise Base32AddressNotMatch(f"expected an address of contract type or builtin type"
+                                            f"receives {address} of {address.address_type}")
+            self.address = Base32Address(address, self.w3.cfx.chain_id)
 
         if not self.address:
             raise TypeError("The address argument is required to instantiate a contract.")
@@ -112,8 +106,8 @@ def __init__(self, address: AddressParam) -> None:
         self.functions = ConfluxContractFunctions(self.abi, self.w3, self.address)
         self.caller = ConfluxContractCaller(self.abi, self.w3, self.address) 
         self.events = ConfluxContractEvents(self.abi, self.w3, self.address)
-        self.fallback = Contract.get_fallback_function(self.abi, self.w3, self.address) # type: ignore
-        self.receive = Contract.get_receive_function(self.abi, self.w3, self.address) # type: ignore
+        self.fallback = Contract.get_fallback_function(self.abi, self.w3, ConfluxContractFunction, self.address) # type: ignore
+        self.receive = Contract.get_receive_function(self.abi, self.w3, ConfluxContractFunction, self.address) # type: ignore
 
     @classmethod
     def factory(cls, w3: "Web3", class_name: Optional[str] = None, **kwargs: Any) -> "Contract":
@@ -150,3 +144,18 @@ def factory(cls, w3: "Web3", class_name: Optional[str] = None, **kwargs: Any) ->
         )
 
         return contract
+
+    @classmethod
+    def constructor(cls, *args: Any, **kwargs: Any) -> "ConfluxContractConstructor":
+        """
+        :param args: The contract constructor arguments as positional arguments
+        :param kwargs: The contract constructor arguments as keyword arguments
+        :return: a contract constructor object
+        """
+        if cls.bytecode is None:
+            raise ValueError(
+                "Cannot call constructor on a contract that does not have "
+                "'bytecode' associated with it"
+            )
+
+        return ConfluxContractConstructor(cls.w3, cls.abi, cls.bytecode, *args, **kwargs)
diff --git a/conflux_web3/contract/constructor.py b/conflux_web3/contract/constructor.py
new file mode 100644
index 0000000..edf408a
--- /dev/null
+++ b/conflux_web3/contract/constructor.py
@@ -0,0 +1,53 @@
+from typing import (
+    TYPE_CHECKING,
+    Optional
+)
+from web3.contract import (
+    ContractConstructor
+)
+
+from cfx_utils.decorators import (
+    combomethod
+)
+from conflux_web3.types.transaction_hash import (
+    TransactionHash
+)
+from conflux_web3.types import (
+    TxParam,
+    EpochNumberParam,
+    EstimateResult
+)
+from conflux_web3._utils.transactions import (
+    fill_transaction_defaults
+)
+
+if TYPE_CHECKING:
+    from conflux_web3 import Web3
+
+class ConfluxContractConstructor(ContractConstructor):
+    w3: "Web3"
+    
+    @combomethod
+    def transact(self, transaction: Optional[TxParam] = None) -> TransactionHash:
+        return super().transact(transaction)
+    
+    @combomethod
+    def build_transaction(self, transaction: Optional[TxParam] = None) -> TxParam:
+        built_transaction = self._build_transaction(transaction)
+        return fill_transaction_defaults(self.w3, built_transaction)
+    
+    @combomethod
+    def estimate_gas(
+        self,
+        transaction: Optional[TxParam] = None,
+        block_identifier: Optional[EpochNumberParam] = None,
+    ) -> EstimateResult:
+        return self.estimate_gas_and_collateral(transaction, block_identifier)
+    
+    @combomethod
+    def estimate_gas_and_collateral(
+        self,
+        transaction: Optional[TxParam] = None,
+        block_identifier: Optional[EpochNumberParam] = None,
+    ) -> EstimateResult:
+        return super().estimate_gas(transaction, block_identifier)
diff --git a/conflux_web3/contract/function.py b/conflux_web3/contract/function.py
index 0d66257..42a53a4 100644
--- a/conflux_web3/contract/function.py
+++ b/conflux_web3/contract/function.py
@@ -28,12 +28,12 @@
     prepare_transaction,
 )
 from conflux_web3._utils.transactions import (
-    fill_formal_transaction_defaults,
+    fill_transaction_defaults,
 )
 
 if TYPE_CHECKING:
     from conflux_web3 import Web3
-    from conflux_web3.transaction_hash import TransactionHash
+    from conflux_web3.types.transaction_hash import TransactionHash
 
 def build_transaction_for_function(
         address: Base32Address,
@@ -60,7 +60,7 @@ def build_transaction_for_function(
         fn_kwargs=kwargs,
     ) 
 
-    prepared_transaction = fill_formal_transaction_defaults(web3, prepared_transaction)
+    prepared_transaction = fill_transaction_defaults(web3, prepared_transaction)
 
     return prepared_transaction  # type: ignore
 
diff --git a/conflux_web3/contract/metadata/AdminControl.json b/conflux_web3/contract/metadata/AdminControl.json
new file mode 100644
index 0000000..9a844ff
--- /dev/null
+++ b/conflux_web3/contract/metadata/AdminControl.json
@@ -0,0 +1,54 @@
+{
+    "abi": [
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "contractAddr",
+                    "type": "address"
+                }
+            ],
+            "name": "destroy",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "contractAddr",
+                    "type": "address"
+                }
+            ],
+            "name": "getAdmin",
+            "outputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "contractAddr",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "newAdmin",
+                    "type": "address"
+                }
+            ],
+            "name": "setAdmin",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/conflux_web3/contract/metadata/Create2Factory.json b/conflux_web3/contract/metadata/Create2Factory.json
new file mode 100644
index 0000000..469121f
--- /dev/null
+++ b/conflux_web3/contract/metadata/Create2Factory.json
@@ -0,0 +1,47 @@
+{
+  "abi": [
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "addr",
+          "type": "address"
+        }
+      ],
+      "name": "isDeployed",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes",
+          "name": "code",
+          "type": "bytes"
+        },
+        {
+          "internalType": "uint256",
+          "name": "salt",
+          "type": "uint256"
+        }
+      ],
+      "name": "deploy",
+      "outputs": [
+        {
+          "internalType": "address",
+          "name": "",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/conflux_web3/contract/metadata/CrossSpaceCall.json b/conflux_web3/contract/metadata/CrossSpaceCall.json
new file mode 100644
index 0000000..a6c080d
--- /dev/null
+++ b/conflux_web3/contract/metadata/CrossSpaceCall.json
@@ -0,0 +1,266 @@
+{
+  "abi": [
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "bytes20",
+          "name": "sender",
+          "type": "bytes20"
+        },
+        {
+          "indexed": true,
+          "internalType": "bytes20",
+          "name": "receiver",
+          "type": "bytes20"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "nonce",
+          "type": "uint256"
+        },
+        {
+          "indexed": false,
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        }
+      ],
+      "name": "Call",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "bytes20",
+          "name": "sender",
+          "type": "bytes20"
+        },
+        {
+          "indexed": true,
+          "internalType": "bytes20",
+          "name": "contract_address",
+          "type": "bytes20"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "nonce",
+          "type": "uint256"
+        },
+        {
+          "indexed": false,
+          "internalType": "bytes",
+          "name": "init",
+          "type": "bytes"
+        }
+      ],
+      "name": "Create",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": false,
+          "internalType": "bool",
+          "name": "success",
+          "type": "bool"
+        }
+      ],
+      "name": "Outcome",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "bytes20",
+          "name": "sender",
+          "type": "bytes20"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "receiver",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "nonce",
+          "type": "uint256"
+        }
+      ],
+      "name": "Withdraw",
+      "type": "event"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes",
+          "name": "init",
+          "type": "bytes"
+        }
+      ],
+      "name": "createEVM",
+      "outputs": [
+        {
+          "internalType": "bytes20",
+          "name": "",
+          "type": "bytes20"
+        }
+      ],
+      "stateMutability": "payable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes20",
+          "name": "to",
+          "type": "bytes20"
+        }
+      ],
+      "name": "transferEVM",
+      "outputs": [
+        {
+          "internalType": "bytes",
+          "name": "output",
+          "type": "bytes"
+        }
+      ],
+      "stateMutability": "payable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes20",
+          "name": "to",
+          "type": "bytes20"
+        },
+        {
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        }
+      ],
+      "name": "callEVM",
+      "outputs": [
+        {
+          "internalType": "bytes",
+          "name": "output",
+          "type": "bytes"
+        }
+      ],
+      "stateMutability": "payable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes20",
+          "name": "to",
+          "type": "bytes20"
+        },
+        {
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        }
+      ],
+      "name": "staticCallEVM",
+      "outputs": [
+        {
+          "internalType": "bytes",
+          "name": "output",
+          "type": "bytes"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "deployEip1820",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        }
+      ],
+      "name": "withdrawFromMapped",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "addr",
+          "type": "address"
+        }
+      ],
+      "name": "mappedBalance",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "addr",
+          "type": "address"
+        }
+      ],
+      "name": "mappedNonce",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/conflux_web3/contract/metadata/ERC1820.json b/conflux_web3/contract/metadata/ERC1820.json
new file mode 100644
index 0000000..33cabe1
--- /dev/null
+++ b/conflux_web3/contract/metadata/ERC1820.json
@@ -0,0 +1,208 @@
+{
+    "abi": [
+        {
+          "constant": false,
+          "inputs": [
+            {
+              "name": "_addr",
+              "type": "address"
+            },
+            {
+              "name": "_interfaceHash",
+              "type": "bytes32"
+            },
+            {
+              "name": "_implementer",
+              "type": "address"
+            }
+          ],
+          "name": "setInterfaceImplementer",
+          "outputs": [],
+          "payable": false,
+          "stateMutability": "nonpayable",
+          "type": "function"
+        },
+        {
+          "constant": true,
+          "inputs": [
+            {
+              "name": "_addr",
+              "type": "address"
+            }
+          ],
+          "name": "getManager",
+          "outputs": [
+            {
+              "name": "",
+              "type": "address"
+            }
+          ],
+          "payable": false,
+          "stateMutability": "view",
+          "type": "function"
+        },
+        {
+          "constant": false,
+          "inputs": [
+            {
+              "name": "_addr",
+              "type": "address"
+            },
+            {
+              "name": "_newManager",
+              "type": "address"
+            }
+          ],
+          "name": "setManager",
+          "outputs": [],
+          "payable": false,
+          "stateMutability": "nonpayable",
+          "type": "function"
+        },
+        {
+          "constant": true,
+          "inputs": [
+            {
+              "name": "_interfaceName",
+              "type": "string"
+            }
+          ],
+          "name": "interfaceHash",
+          "outputs": [
+            {
+              "name": "",
+              "type": "bytes32"
+            }
+          ],
+          "payable": false,
+          "stateMutability": "pure",
+          "type": "function"
+        },
+        {
+          "constant": false,
+          "inputs": [
+            {
+              "name": "_contract",
+              "type": "address"
+            },
+            {
+              "name": "_interfaceId",
+              "type": "bytes4"
+            }
+          ],
+          "name": "updateERC165Cache",
+          "outputs": [],
+          "payable": false,
+          "stateMutability": "nonpayable",
+          "type": "function"
+        },
+        {
+          "constant": true,
+          "inputs": [
+            {
+              "name": "_addr",
+              "type": "address"
+            },
+            {
+              "name": "_interfaceHash",
+              "type": "bytes32"
+            }
+          ],
+          "name": "getInterfaceImplementer",
+          "outputs": [
+            {
+              "name": "",
+              "type": "address"
+            }
+          ],
+          "payable": false,
+          "stateMutability": "view",
+          "type": "function"
+        },
+        {
+          "constant": true,
+          "inputs": [
+            {
+              "name": "_contract",
+              "type": "address"
+            },
+            {
+              "name": "_interfaceId",
+              "type": "bytes4"
+            }
+          ],
+          "name": "implementsERC165InterfaceNoCache",
+          "outputs": [
+            {
+              "name": "",
+              "type": "bool"
+            }
+          ],
+          "payable": false,
+          "stateMutability": "view",
+          "type": "function"
+        },
+        {
+          "constant": true,
+          "inputs": [
+            {
+              "name": "_contract",
+              "type": "address"
+            },
+            {
+              "name": "_interfaceId",
+              "type": "bytes4"
+            }
+          ],
+          "name": "implementsERC165Interface",
+          "outputs": [
+            {
+              "name": "",
+              "type": "bool"
+            }
+          ],
+          "payable": false,
+          "stateMutability": "view",
+          "type": "function"
+        },
+        {
+          "anonymous": false,
+          "inputs": [
+            {
+              "indexed": true,
+              "name": "addr",
+              "type": "address"
+            },
+            {
+              "indexed": true,
+              "name": "interfaceHash",
+              "type": "bytes32"
+            },
+            {
+              "indexed": true,
+              "name": "implementer",
+              "type": "address"
+            }
+          ],
+          "name": "InterfaceImplementerSet",
+          "type": "event"
+        },
+        {
+          "anonymous": false,
+          "inputs": [
+            {
+              "indexed": true,
+              "name": "addr",
+              "type": "address"
+            },
+            {
+              "indexed": true,
+              "name": "newManager",
+              "type": "address"
+            }
+          ],
+          "name": "ManagerChanged",
+          "type": "event"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/conflux_web3/contract/metadata/ERC20.json b/conflux_web3/contract/metadata/ERC20.json
new file mode 100644
index 0000000..ea85000
--- /dev/null
+++ b/conflux_web3/contract/metadata/ERC20.json
@@ -0,0 +1,297 @@
+{
+  "contractName": "ERC20",
+  "abi": [
+    {
+      "inputs": [
+        {
+          "internalType": "string",
+          "name": "name",
+          "type": "string"
+        },
+        {
+          "internalType": "string",
+          "name": "symbol",
+          "type": "string"
+        },
+        {
+          "internalType": "uint256",
+          "name": "initialSupply",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "constructor"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        }
+      ],
+      "name": "Approval",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "from",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        }
+      ],
+      "name": "Transfer",
+      "type": "event"
+    },
+    {
+      "inputs": [],
+      "name": "name",
+      "outputs": [
+        {
+          "internalType": "string",
+          "name": "",
+          "type": "string"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "symbol",
+      "outputs": [
+        {
+          "internalType": "string",
+          "name": "",
+          "type": "string"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "decimals",
+      "outputs": [
+        {
+          "internalType": "uint8",
+          "name": "",
+          "type": "uint8"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "initialSupply",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        }
+      ],
+      "name": "balanceOf",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "recipient",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "transfer",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        }
+      ],
+      "name": "allowance",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "approve",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "sender",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "recipient",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "transferFrom",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "addedValue",
+          "type": "uint256"
+        }
+      ],
+      "name": "increaseAllowance",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "subtractedValue",
+          "type": "uint256"
+        }
+      ],
+      "name": "decreaseAllowance",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    }
+  ],
+  "bytecode": "0x60806040523480156200001157600080fd5b50604051620019da380380620019da83398181016040528101906200003791906200033e565b82600390805190602001906200004f92919062000205565b5081600490805190602001906200006892919062000205565b506200008a6200007d6200009360201b60201c565b826200009b60201b60201c565b5050506200064a565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156200010e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620001059062000419565b60405180910390fd5b62000122600083836200020060201b60201c565b8060026000828254620001369190620004d0565b92505081905550806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546200018d9190620004d0565b925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051620001f491906200043b565b60405180910390a35050565b505050565b82805462000213906200056d565b90600052602060002090601f01602090048101928262000237576000855562000283565b82601f106200025257805160ff191683800117855562000283565b8280016001018555821562000283579182015b828111156200028257825182559160200191906001019062000265565b5b50905062000292919062000296565b5090565b5b80821115620002b157600081600090555060010162000297565b5090565b6000620002cc620002c6846200048c565b62000458565b905082815260208101848484011115620002e557600080fd5b620002f284828562000537565b509392505050565b600082601f8301126200030c57600080fd5b81516200031e848260208601620002b5565b91505092915050565b600081519050620003388162000630565b92915050565b6000806000606084860312156200035457600080fd5b600084015167ffffffffffffffff8111156200036f57600080fd5b6200037d86828701620002fa565b935050602084015167ffffffffffffffff8111156200039b57600080fd5b620003a986828701620002fa565b9250506040620003bc8682870162000327565b9150509250925092565b6000620003d5601f83620004bf565b91507f45524332303a206d696e7420746f20746865207a65726f2061646472657373006000830152602082019050919050565b62000413816200052d565b82525050565b600060208201905081810360008301526200043481620003c6565b9050919050565b600060208201905062000452600083018462000408565b92915050565b6000604051905081810181811067ffffffffffffffff8211171562000482576200048162000601565b5b8060405250919050565b600067ffffffffffffffff821115620004aa57620004a962000601565b5b601f19601f8301169050602081019050919050565b600082825260208201905092915050565b6000620004dd826200052d565b9150620004ea836200052d565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115620005225762000521620005a3565b5b828201905092915050565b6000819050919050565b60005b83811015620005575780820151818401526020810190506200053a565b8381111562000567576000848401525b50505050565b600060028204905060018216806200058657607f821691505b602082108114156200059d576200059c620005d2565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6200063b816200052d565b81146200064757600080fd5b50565b611380806200065a6000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190611015565b60405180910390f35b6100e660048036038101906100e19190610c8e565b610308565b6040516100f39190610ffa565b60405180910390f35b610104610326565b6040516101119190611117565b60405180910390f35b610134600480360381019061012f9190610c3f565b610330565b6040516101419190610ffa565b60405180910390f35b610152610431565b60405161015f9190611132565b60405180910390f35b610182600480360381019061017d9190610c8e565b61043a565b60405161018f9190610ffa565b60405180910390f35b6101b260048036038101906101ad9190610bda565b6104e6565b6040516101bf9190611117565b60405180910390f35b6101d061052e565b6040516101dd9190611015565b60405180910390f35b61020060048036038101906101fb9190610c8e565b6105c0565b60405161020d9190610ffa565b60405180910390f35b610230600480360381019061022b9190610c8e565b6106b4565b60405161023d9190610ffa565b60405180910390f35b610260600480360381019061025b9190610c03565b6106d2565b60405161026d9190611117565b60405180910390f35b6060600380546102859061127b565b80601f01602080910402602001604051908101604052809291908181526020018280546102b19061127b565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b600061031c610315610759565b8484610761565b6001905092915050565b6000600254905090565b600061033d84848461092c565b6000600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610388610759565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610408576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103ff90611097565b60405180910390fd5b61042585610414610759565b858461042091906111bf565b610761565b60019150509392505050565b60006012905090565b60006104dc610447610759565b848460016000610455610759565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546104d79190611169565b610761565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60606004805461053d9061127b565b80601f01602080910402602001604051908101604052809291908181526020018280546105699061127b565b80156105b65780601f1061058b576101008083540402835291602001916105b6565b820191906000526020600020905b81548152906001019060200180831161059957829003601f168201915b5050505050905090565b600080600160006105cf610759565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508281101561068c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610683906110f7565b60405180910390fd5b6106a9610697610759565b8585846106a491906111bf565b610761565b600191505092915050565b60006106c86106c1610759565b848461092c565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156107d1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107c8906110d7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610841576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161083890611057565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161091f9190611117565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561099c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610993906110b7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610a0c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a0390611037565b60405180910390fd5b610a17838383610bab565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610a9d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a9490611077565b60405180910390fd5b8181610aa991906111bf565b6000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610b399190611169565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b9d9190611117565b60405180910390a350505050565b505050565b600081359050610bbf8161131c565b92915050565b600081359050610bd481611333565b92915050565b600060208284031215610bec57600080fd5b6000610bfa84828501610bb0565b91505092915050565b60008060408385031215610c1657600080fd5b6000610c2485828601610bb0565b9250506020610c3585828601610bb0565b9150509250929050565b600080600060608486031215610c5457600080fd5b6000610c6286828701610bb0565b9350506020610c7386828701610bb0565b9250506040610c8486828701610bc5565b9150509250925092565b60008060408385031215610ca157600080fd5b6000610caf85828601610bb0565b9250506020610cc085828601610bc5565b9150509250929050565b610cd381611205565b82525050565b6000610ce48261114d565b610cee8185611158565b9350610cfe818560208601611248565b610d078161130b565b840191505092915050565b6000610d1f602383611158565b91507f45524332303a207472616e7366657220746f20746865207a65726f206164647260008301527f65737300000000000000000000000000000000000000000000000000000000006020830152604082019050919050565b6000610d85602283611158565b91507f45524332303a20617070726f766520746f20746865207a65726f20616464726560008301527f73730000000000000000000000000000000000000000000000000000000000006020830152604082019050919050565b6000610deb602683611158565b91507f45524332303a207472616e7366657220616d6f756e742065786365656473206260008301527f616c616e636500000000000000000000000000000000000000000000000000006020830152604082019050919050565b6000610e51602883611158565b91507f45524332303a207472616e7366657220616d6f756e742065786365656473206160008301527f6c6c6f77616e63650000000000000000000000000000000000000000000000006020830152604082019050919050565b6000610eb7602583611158565b91507f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008301527f64726573730000000000000000000000000000000000000000000000000000006020830152604082019050919050565b6000610f1d602483611158565b91507f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008301527f72657373000000000000000000000000000000000000000000000000000000006020830152604082019050919050565b6000610f83602583611158565b91507f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008301527f207a65726f0000000000000000000000000000000000000000000000000000006020830152604082019050919050565b610fe581611231565b82525050565b610ff48161123b565b82525050565b600060208201905061100f6000830184610cca565b92915050565b6000602082019050818103600083015261102f8184610cd9565b905092915050565b6000602082019050818103600083015261105081610d12565b9050919050565b6000602082019050818103600083015261107081610d78565b9050919050565b6000602082019050818103600083015261109081610dde565b9050919050565b600060208201905081810360008301526110b081610e44565b9050919050565b600060208201905081810360008301526110d081610eaa565b9050919050565b600060208201905081810360008301526110f081610f10565b9050919050565b6000602082019050818103600083015261111081610f76565b9050919050565b600060208201905061112c6000830184610fdc565b92915050565b60006020820190506111476000830184610feb565b92915050565b600081519050919050565b600082825260208201905092915050565b600061117482611231565b915061117f83611231565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156111b4576111b36112ad565b5b828201905092915050565b60006111ca82611231565b91506111d583611231565b9250828210156111e8576111e76112ad565b5b828203905092915050565b60006111fe82611211565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b8381101561126657808201518184015260208101905061124b565b83811115611275576000848401525b50505050565b6000600282049050600182168061129357607f821691505b602082108114156112a7576112a66112dc565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b611325816111f3565b811461133057600080fd5b50565b61133c81611231565b811461134757600080fd5b5056fea2646970667358221220bbc5aae98f8b36e3e8354fefe2f6958dd682c2313fe014f514f7d70d1b00ddd664736f6c63430008000033"
+}
\ No newline at end of file
diff --git a/conflux_web3/contract/metadata/Faucet.json b/conflux_web3/contract/metadata/Faucet.json
new file mode 100644
index 0000000..85bc0f7
--- /dev/null
+++ b/conflux_web3/contract/metadata/Faucet.json
@@ -0,0 +1,160 @@
+{
+  "contractName": "Faucet",
+  "abi": [
+    {
+      "inputs": [],
+      "stateMutability": "nonpayable",
+      "type": "constructor"
+    },
+    {
+      "inputs": [],
+      "name": "defaultAmount",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "defaultInterval",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "stateMutability": "payable",
+      "type": "receive"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "newManager",
+          "type": "address"
+        }
+      ],
+      "name": "changeManager",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "tokenContractAddress",
+          "type": "address"
+        }
+      ],
+      "name": "getClaimInterval",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "tokenContractAddress",
+          "type": "address"
+        }
+      ],
+      "name": "getClaimAmount",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint256",
+          "name": "intervalSeconds",
+          "type": "uint256"
+        }
+      ],
+      "name": "setDefaultIntervalSeconds",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "setDefaultAmount",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "tokenContractAddress",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "interval",
+          "type": "uint256"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "setClaimSetting",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "tokenContractAddress",
+          "type": "address"
+        }
+      ],
+      "name": "claimToken",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "claimCfx",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/conflux_web3/contract/metadata/ParamsControl.json b/conflux_web3/contract/metadata/ParamsControl.json
new file mode 100644
index 0000000..b68771c
--- /dev/null
+++ b/conflux_web3/contract/metadata/ParamsControl.json
@@ -0,0 +1,190 @@
+{
+  "abi": [
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "uint64",
+          "name": "vote_round",
+          "type": "uint64"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "addr",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "uint16",
+          "name": "topic_index",
+          "type": "uint16"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256[3]",
+          "name": "votes",
+          "type": "uint256[3]"
+        }
+      ],
+      "name": "CastVote",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "uint64",
+          "name": "vote_round",
+          "type": "uint64"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "addr",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "uint16",
+          "name": "topic_index",
+          "type": "uint16"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256[3]",
+          "name": "votes",
+          "type": "uint256[3]"
+        }
+      ],
+      "name": "RevokeVote",
+      "type": "event"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint64",
+          "name": "vote_round",
+          "type": "uint64"
+        },
+        {
+          "components": [
+            {
+              "internalType": "uint16",
+              "name": "topic_index",
+              "type": "uint16"
+            },
+            {
+              "internalType": "uint256[3]",
+              "name": "votes",
+              "type": "uint256[3]"
+            }
+          ],
+          "internalType": "struct ParamsControl.Vote[]",
+          "name": "vote_data",
+          "type": "tuple[]"
+        }
+      ],
+      "name": "castVote",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "currentRound",
+      "outputs": [
+        {
+          "internalType": "uint64",
+          "name": "",
+          "type": "uint64"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint64",
+          "name": "",
+          "type": "uint64"
+        }
+      ],
+      "name": "posStakeForVotes",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "addr",
+          "type": "address"
+        }
+      ],
+      "name": "readVote",
+      "outputs": [
+        {
+          "components": [
+            {
+              "internalType": "uint16",
+              "name": "topic_index",
+              "type": "uint16"
+            },
+            {
+              "internalType": "uint256[3]",
+              "name": "votes",
+              "type": "uint256[3]"
+            }
+          ],
+          "internalType": "struct ParamsControl.Vote[]",
+          "name": "",
+          "type": "tuple[]"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint64",
+          "name": "vote_round",
+          "type": "uint64"
+        }
+      ],
+      "name": "totalVotes",
+      "outputs": [
+        {
+          "components": [
+            {
+              "internalType": "uint16",
+              "name": "topic_index",
+              "type": "uint16"
+            },
+            {
+              "internalType": "uint256[3]",
+              "name": "votes",
+              "type": "uint256[3]"
+            }
+          ],
+          "internalType": "struct ParamsControl.Vote[]",
+          "name": "",
+          "type": "tuple[]"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/conflux_web3/contract/metadata/PoSRegister.json b/conflux_web3/contract/metadata/PoSRegister.json
new file mode 100644
index 0000000..891e403
--- /dev/null
+++ b/conflux_web3/contract/metadata/PoSRegister.json
@@ -0,0 +1,188 @@
+{
+  "abi": [
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "bytes32",
+          "name": "identifier",
+          "type": "bytes32"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint64",
+          "name": "votePower",
+          "type": "uint64"
+        }
+      ],
+      "name": "IncreaseStake",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "bytes32",
+          "name": "identifier",
+          "type": "bytes32"
+        },
+        {
+          "indexed": false,
+          "internalType": "bytes",
+          "name": "blsPubKey",
+          "type": "bytes"
+        },
+        {
+          "indexed": false,
+          "internalType": "bytes",
+          "name": "vrfPubKey",
+          "type": "bytes"
+        }
+      ],
+      "name": "Register",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "bytes32",
+          "name": "identifier",
+          "type": "bytes32"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint64",
+          "name": "votePower",
+          "type": "uint64"
+        }
+      ],
+      "name": "Retire",
+      "type": "event"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "addr",
+          "type": "address"
+        }
+      ],
+      "name": "addressToIdentifier",
+      "outputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "identifier",
+          "type": "bytes32"
+        }
+      ],
+      "name": "getVotes",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        },
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "identifier",
+          "type": "bytes32"
+        }
+      ],
+      "name": "identifierToAddress",
+      "outputs": [
+        {
+          "internalType": "address",
+          "name": "",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint64",
+          "name": "votePower",
+          "type": "uint64"
+        }
+      ],
+      "name": "increaseStake",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "identifier",
+          "type": "bytes32"
+        },
+        {
+          "internalType": "uint64",
+          "name": "votePower",
+          "type": "uint64"
+        },
+        {
+          "internalType": "bytes",
+          "name": "blsPubKey",
+          "type": "bytes"
+        },
+        {
+          "internalType": "bytes",
+          "name": "vrfPubKey",
+          "type": "bytes"
+        },
+        {
+          "internalType": "bytes[2]",
+          "name": "blsPubKeyProof",
+          "type": "bytes[2]"
+        }
+      ],
+      "name": "register",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint64",
+          "name": "votes",
+          "type": "uint64"
+        }
+      ],
+      "name": "retire",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/conflux_web3/contract/metadata/SponsorWhitelistControl.json b/conflux_web3/contract/metadata/SponsorWhitelistControl.json
new file mode 100644
index 0000000..d3679a0
--- /dev/null
+++ b/conflux_web3/contract/metadata/SponsorWhitelistControl.json
@@ -0,0 +1,235 @@
+{
+    "abi": [
+        {
+            "inputs": [
+                {
+                    "internalType": "address[]",
+                    "name": "",
+                    "type": "address[]"
+                }
+            ],
+            "name": "addPrivilege",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "contractAddr",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address[]",
+                    "name": "addresses",
+                    "type": "address[]"
+                }
+            ],
+            "name": "addPrivilegeByAdmin",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "contractAddr",
+                    "type": "address"
+                }
+            ],
+            "name": "getSponsorForCollateral",
+            "outputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "contractAddr",
+                    "type": "address"
+                }
+            ],
+            "name": "getSponsorForGas",
+            "outputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "contractAddr",
+                    "type": "address"
+                }
+            ],
+            "name": "getSponsoredBalanceForCollateral",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "contractAddr",
+                    "type": "address"
+                }
+            ],
+            "name": "getSponsoredBalanceForGas",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "contractAddr",
+                    "type": "address"
+                }
+            ],
+            "name": "getSponsoredGasFeeUpperBound",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "contractAddr",
+                    "type": "address"
+                }
+            ],
+            "name": "isAllWhitelisted",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "contractAddr",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "user",
+                    "type": "address"
+                }
+            ],
+            "name": "isWhitelisted",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address[]",
+                    "name": "",
+                    "type": "address[]"
+                }
+            ],
+            "name": "removePrivilege",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "contractAddr",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address[]",
+                    "name": "addresses",
+                    "type": "address[]"
+                }
+            ],
+            "name": "removePrivilegeByAdmin",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "contractAddr",
+                    "type": "address"
+                }
+            ],
+            "name": "setSponsorForCollateral",
+            "outputs": [],
+            "stateMutability": "payable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "contractAddr",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "upperBound",
+                    "type": "uint256"
+                }
+            ],
+            "name": "setSponsorForGas",
+            "outputs": [],
+            "stateMutability": "payable",
+            "type": "function"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/conflux_web3/contract/metadata/Staking.json b/conflux_web3/contract/metadata/Staking.json
new file mode 100644
index 0000000..7e4b76f
--- /dev/null
+++ b/conflux_web3/contract/metadata/Staking.json
@@ -0,0 +1,115 @@
+{
+    "abi": [
+        {
+            "inputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "deposit",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "user",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "blockNumber",
+                    "type": "uint256"
+                }
+            ],
+            "name": "getLockedStakingBalance",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "user",
+                    "type": "address"
+                }
+            ],
+            "name": "getStakingBalance",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "user",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "blockNumber",
+                    "type": "uint256"
+                }
+            ],
+            "name": "getVotePower",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "unlockBlockNumber",
+                    "type": "uint256"
+                }
+            ],
+            "name": "voteLock",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "withdraw",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/conflux_web3/contract/metadata/__init__.py b/conflux_web3/contract/metadata/__init__.py
new file mode 100644
index 0000000..da6c3b3
--- /dev/null
+++ b/conflux_web3/contract/metadata/__init__.py
@@ -0,0 +1,64 @@
+import json
+from pathlib import (
+    Path
+)
+from typing import (
+    Dict,
+    Optional
+)
+from toolz import (
+    keyfilter
+)
+
+from cfx_address.utils import (
+    validate_address_agaist_network_id
+)
+
+METADATA_DIR = Path(__file__).parent
+
+# DEPLOYMENT_INFO
+DEPLOYMENT_INFO = {
+    "AdminControl": "0x0888000000000000000000000000000000000000",
+    "SponsorWhitelistControl": "0x0888000000000000000000000000000000000001",
+    "Staking": "0x0888000000000000000000000000000000000002",
+    # "ConfluxContext": "0x0888000000000000000000000000000000000003",
+    "PoSRegister": "0x0888000000000000000000000000000000000005",
+    "CrossSpaceCall": "0x0888000000000000000000000000000000000006",
+    "ParamsControl": "0x0888000000000000000000000000000000000007",
+    "Create2Factory": "0x8A3A92281Df6497105513B18543fd3B60c778E40",
+    "ERC1820": "0x88887eD889e776bCBe2f0f9932EcFaBcDfCd1820",
+    "Faucet": "cfxtest:acejjfa80vj06j2jgtz9pngkv423fhkuxj786kjr61",
+    "cUSDT": {
+        1: "cfxtest:acepe88unk7fvs18436178up33hb4zkuf62a9dk1gv",
+        1029: "cfx:acf2rcsh8payyxpg6xj7b0ztswwh81ute60tsw35j7",
+    },
+    "FC": {
+        1: "cfxtest:achkx35n7vngfxgrm7akemk3ftzy47t61yk5nn270s",
+        1029: "cfx:achc8nxj7r451c223m18w2dwjnmhkd6rxawrvkvsy2",
+    }
+}
+
+METADATA_INFO = {
+    "cUSDT": "ERC20",
+    "FC": "ERC20",
+}
+
+def list_embedded_contract_names():
+    pass
+
+# TODO: normalize metadata["bin"] to metadata["bytecode"]
+def get_contract_metadata(contract_name: str, chain_id: Optional[int]=None) -> Dict:
+    metadata_path = METADATA_DIR / f"{METADATA_INFO.get(contract_name, contract_name)}.json"
+    with open(metadata_path) as f:
+        metadata = json.load(f)
+    metadata = keyfilter(lambda x: x in ["abi", "bytecode"], metadata)
+    address_info = DEPLOYMENT_INFO.get(contract_name, None)
+    if address_info is not None:
+        if isinstance(address_info, str):
+            validate_address_agaist_network_id(address_info, chain_id, accept_hex=True)
+            metadata["address"] = address_info
+        elif isinstance(address_info, dict):
+            metadata["address"] = address_info[chain_id]
+        else:
+            raise Exception("unexpected error")
+    return metadata
diff --git a/conflux_web3/main.py b/conflux_web3/main.py
index 402042b..09a8930 100644
--- a/conflux_web3/main.py
+++ b/conflux_web3/main.py
@@ -98,7 +98,7 @@ def client_version(self) -> str:
     
     @property
     def wallet(self) -> "Wallet":
-        return self.middleware_onion["wallet"] # type: ignore
+        return self.middleware_onion.get("wallet", None) # type: ignore
     
     def isConnected(self) -> bool:
         return self.is_connected()
diff --git a/conflux_web3/middleware/pending.py b/conflux_web3/middleware/pending.py
index 8d3cbf1..18b679a 100644
--- a/conflux_web3/middleware/pending.py
+++ b/conflux_web3/middleware/pending.py
@@ -2,7 +2,7 @@
 from conflux_web3._utils.rpc_abi import (
     RPC
 )
-from conflux_web3.transaction_hash import (
+from conflux_web3.types.transaction_hash import (
     TransactionHash
 )
 
diff --git a/conflux_web3/types.py b/conflux_web3/types/__init__.py
similarity index 100%
rename from conflux_web3/types.py
rename to conflux_web3/types/__init__.py
diff --git a/conflux_web3/transaction_hash.py b/conflux_web3/types/transaction_hash.py
similarity index 84%
rename from conflux_web3/transaction_hash.py
rename to conflux_web3/types/transaction_hash.py
index 6f1e654..15b3508 100644
--- a/conflux_web3/transaction_hash.py
+++ b/conflux_web3/types/transaction_hash.py
@@ -1,18 +1,21 @@
-from typing import TYPE_CHECKING, Optional, Type, Union, cast
+from typing import (
+    TYPE_CHECKING, Optional, Type, Union, cast
+)
 from hexbytes import HexBytes
 from web3._utils.method_formatters import (
     to_hexbytes
 )
-from conflux_web3.types import (
-    TxReceipt,
-    TxData,
-)
+
 from conflux_web3.exceptions import (
     NoWeb3Exception
 )
 
 if TYPE_CHECKING:
     from conflux_web3 import Web3
+    from conflux_web3.types import (
+        TxReceipt,
+        TxData,
+    )
 
 def requires_web3(func):
     def inner(self, *args, **kwargs):
@@ -41,21 +44,19 @@ def set_w3(self, w3: "Web3"):
     #     pass
     
     @requires_web3
-    def mined(self, timeout: float = 60, poll_latency: float = 0.5) -> TxData:
+    def mined(self, timeout: float = 60, poll_latency: float = 0.5) -> "TxData":
         return self._w3.cfx.wait_till_transaction_mined(self, timeout, poll_latency)
     
     @requires_web3
-    def executed(self, timeout: float = 300, poll_latency: float = 0.5) -> TxReceipt:
+    def executed(self, timeout: float = 300, poll_latency: float = 0.5) -> "TxReceipt":
         return self._w3.cfx.wait_for_transaction_receipt(self, timeout, poll_latency) 
     
     @requires_web3
-    def confirmed(self, timeout: float = 600, poll_latency: float = 0.5) -> TxReceipt:
+    def confirmed(self, timeout: float = 600, poll_latency: float = 0.5) -> "TxReceipt":
         return self._w3.cfx.wait_till_transaction_confirmed(self, timeout, poll_latency) 
     
     @requires_web3
-    def finalized(self, timeout: float = 1200, poll_latency: float = 1) -> TxReceipt: # type: ignore
-        # TODO
-        # warnings.warn("Several minutes are required to finalize a transaction", UserWarning)
+    def finalized(self, timeout: float = 1200, poll_latency: float = 1) -> "TxReceipt":
         return self._w3.cfx.wait_till_transaction_finalized(self, timeout, poll_latency)
     
     def __repr__(self) -> str:
diff --git a/conflux_web3/utils.py b/conflux_web3/utils.py
new file mode 100644
index 0000000..eac0620
--- /dev/null
+++ b/conflux_web3/utils.py
@@ -0,0 +1,8 @@
+# export utils from _utils
+from conflux_web3._utils.transactions import (
+    fill_transaction_defaults
+)
+
+__all__ = [
+    "fill_transaction_defaults"
+]
diff --git a/examples/01-quick_start.py b/examples/01-quick_start.py
index 2a3bd78..5260e5f 100644
--- a/examples/01-quick_start.py
+++ b/examples/01-quick_start.py
@@ -1,4 +1,4 @@
-import os
+import os, pprint
 from conflux_web3 import Web3
 
 web3 = Web3(Web3.HTTPProvider("https://test.confluxrpc.com"))
@@ -20,4 +20,4 @@
 # in Conflux, the transaction will be executed only after it appears on the chain for 5 epoch 
 # tx_hash.executed() is equivalent to web3.cfx.wait_for_transaction_receipt(tx_hash)
 tx_receipt = tx_hash.executed()
-print(tx_receipt)
+pprint.pprint(dict(tx_receipt))
diff --git a/examples/04-interact_with_contracts_and_logs.py b/examples/04-interact_with_contracts_and_logs.py
index 17f5092..53f1ea7 100644
--- a/examples/04-interact_with_contracts_and_logs.py
+++ b/examples/04-interact_with_contracts_and_logs.py
@@ -1,4 +1,4 @@
-import json, os
+import json, os, pprint
 from conflux_web3 import Web3
 
 web3 = Web3(Web3.HTTPProvider("https://test.confluxrpc.com"))
@@ -7,9 +7,9 @@
 )
 web3.cfx.default_account = account.address
 
-# you can compile erc20 yourself 
+# you can compile ERC20 yourself 
 # or use our pre-compiled file https://raw.githubusercontent.com/Conflux-Chain/python-conflux-sdk/v2/tests/_test_helpers/ERC20.json
-erc20_metadata = json.load(open("path/to/erc20metadata.json"))
+erc20_metadata = json.load(open("path/to/ERC20metadata.json"))
 erc20 = web3.cfx.contract(bytecode=erc20_metadata["bytecode"], abi=erc20_metadata["abi"])
 
 # you might need to change the argument name depending on the solidity source code
@@ -41,8 +41,8 @@
 # parameter definitions: https://developer.confluxnetwork.org/conflux-doc/docs/json_rpc#cfx_getlogs
 fromEpoch = transfer_receipt["epochNumber"]
 logs = web3.cfx.get_logs(fromEpoch=fromEpoch, address=contract_address)
-print("raw logs: ")
-print(logs)
+print("raw log: ")
+pprint.pprint(dict(logs[0]))
 print()
     
 # use contract event to process logs
@@ -51,9 +51,9 @@
 assert processed_log["args"]["from"] == web3.cfx.default_account
 assert processed_log["args"]["to"] == random_account.address
 assert processed_log["args"]["value"] == 100
-# for log in transaction["logs"], field "logIndex" and "transactionIndex" are not included
+# for log in transaction["logs"], field "logIndex" are not included
 print("processed log: (no logIndex)")
-print(processed_log)
+pprint.pprint(dict(processed_log))
 print()
 
 # generate topics to use getLogs
@@ -62,8 +62,8 @@
     to=random_account.address
 )
 new_logs = web3.cfx.get_logs(fromEpoch=fromEpoch, topics=filter_topics)
-print("logs filter by topics:")
-print(new_logs)
+print("log filtered by topics:")
+pprint.pprint(dict(new_logs[0]))
 print()
 
 # event get_logs will return processed logs
@@ -74,6 +74,6 @@
     },
     fromEpoch=fromEpoch
 )
-print("processed logs from contract event get_logs")
-print(new_processed_logs)
+print("processed log from contract event get_logs")
+pprint.pprint(dict(new_processed_logs[0]))
 print()
diff --git a/examples/10-send_raw_transaction.py b/examples/10-send_raw_transaction.py
new file mode 100644
index 0000000..1649f9e
--- /dev/null
+++ b/examples/10-send_raw_transaction.py
@@ -0,0 +1,72 @@
+# This example shows how to sign and send transactions without using web3.wallet
+# This method provides is more flexible but is also more complex than using wallet
+# If you are new to web3 development, we recommend you refer to the example to send transactions in examples/01_quick_start.py
+
+import os, json, pprint
+from conflux_web3 import Web3
+from conflux_web3.utils import fill_transaction_defaults
+
+web3 = Web3(Web3.HTTPProvider("https://test.confluxrpc.com"))
+account = web3.account.from_key(os.environ.get("TESTNET_SECRET"))
+
+# Basiclly, we follow the workflow of 4 steps to send a transaction:
+#    "build transaction" 
+# -> "sign transaction" 
+# -> "send transaction to the blockchain node(s)" 
+# -> "wait for transaction execution result"
+# if any account is added to the wallet, the first 3 steps will be combined,
+# otherwise you will need to do the first 3 steps individually
+
+# "build transaction"
+# we provide necessary params here, and other params are filled
+# see 11-constuct_transaction_from_scratch.py to see how to construct a transaction from scratch
+built_trivial_tx = fill_transaction_defaults(web3, {
+    'from': account.address,
+    'to': web3.account.create().address,
+    'value': 100,
+})
+# "sign transaction"
+signed_tx = account.sign_transaction(built_trivial_tx)
+# "send transaction to the blockchain node(s)"
+h = web3.cfx.send_raw_transaction(signed_tx.rawTransaction)
+# "wait"
+tx_receipt = h.executed()
+pprint.pprint(dict(tx_receipt))
+
+
+# deploy a contract
+# you can compile erc20 yourself 
+# or use our pre-compiled file https://raw.githubusercontent.com/Conflux-Chain/python-conflux-sdk/v2/tests/_test_helpers/ERC20.json
+# erc20_metadata = json.load(open("path/to/erc20metadata.json"))
+erc20_metadata = json.load(open("tests/_test_helpers/ERC20.json"))
+erc20 = web3.cfx.contract(bytecode=erc20_metadata["bytecode"], abi=erc20_metadata["abi"])
+
+# "build transaction"
+built_constuct_tx = erc20.constructor(name="ERC20", symbol="C", initialSupply=10**18).build_transaction({
+    'from': account.address,
+})
+# "sign transaction"
+construct_tx = account.sign_transaction(built_constuct_tx)
+# "send" & "wait"
+contract_address = web3.cfx.send_raw_transaction(construct_tx.rawTransaction).executed()['contractCreated']
+print(f"deployed contract address: f{contract_address}")
+
+
+# interact with contract
+contract = web3.cfx.contract(address=contract_address, abi=erc20_metadata["abi"])
+# "build transaction"
+built_transfer_tx = contract.functions.transfer(
+    web3.account.create().address,
+    100
+).build_transaction({
+    'from': account.address
+})
+# "sign transaction"
+signed_transfer_tx = account.sign_transaction(
+    built_transfer_tx
+)
+# "send" and "wait"
+print("erc20 transfer receipt: ")
+pprint.pprint(
+    dict(web3.cfx.send_raw_transaction(signed_transfer_tx.rawTransaction).executed())
+)
diff --git a/examples/11-construct_transaction_from_scratch.py b/examples/11-construct_transaction_from_scratch.py
new file mode 100644
index 0000000..27a89dd
--- /dev/null
+++ b/examples/11-construct_transaction_from_scratch.py
@@ -0,0 +1,32 @@
+# this example shows how to constuct a transaction from scratch
+# If you are new to web3 development, we recommend you refer to the example to send transactions in examples/01_quick_start.py
+
+import os, pprint
+from conflux_web3 import Web3
+
+
+web3 = Web3(Web3.HTTPProvider("https://test.confluxrpc.com"))
+account = web3.account.from_key(os.environ.get("TESTNET_SECRET"))
+
+prebuilt_tx = {
+    'from': account.address,
+    'nonce': web3.cfx.get_next_nonce(account.address),
+    'to': web3.account.create().address,
+    'value': 100,
+    'gasPrice': web3.cfx.gas_price,
+    'chainId': web3.cfx.chain_id,
+    # 'gas': 21000, 
+    # 'storageLimit': 0,
+    'epochHeight': web3.cfx.epoch_number
+}
+
+# estimate
+estimate_result = web3.cfx.estimate_gas_and_collateral(prebuilt_tx)
+
+prebuilt_tx['gas'] = estimate_result['gasLimit']
+prebuilt_tx['storageLimit'] = estimate_result['storageCollateralized']
+
+tx_receipt = web3.cfx.send_raw_transaction(
+    account.sign_transaction(prebuilt_tx).rawTransaction 
+).executed()
+pprint.pprint(dict(tx_receipt))
diff --git a/setup.py b/setup.py
index 859ae6c..6974b61 100644
--- a/setup.py
+++ b/setup.py
@@ -83,9 +83,9 @@
     url='https://github.com/conflux-chain/python-conflux-sdk',
     install_requires=[
         "web3~=6.0.0b4",
-        "cfx-address>=1.0.0b6",
+        "cfx-address>=1.0.0b8",
         "cfx-account>=0.1.0b3",
-        "cfx-utils>=1.0.0b6"
+        "cfx-utils>=1.0.0b7"
         # "eth-account>=0.6.0,<0.7.0"
     ],  # add any additional packages that
     # needs to be installed along with your package. Eg: 'caer'


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index e8587ae..e5a9c75 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -5,9 +5,7 @@ name: Python package
 
 on:
   push:
-    branches: [ "dev", "v2" ]
-  # pull_request:
-  #   branches: [ "v2" ]
+    branches: [ "dev", "v1" ]
 
 jobs:
   build:
@@ -27,7 +25,9 @@ jobs:
     - name: Install dependencies
       run: |
         pip install ".[tester]"
-    - name: Test with pytest
+    - name: Run test
       run: |
         pytest tests
-        
\ No newline at end of file
+    - name: Run test on testnet
+      run: |
+        export USE_TESTNET=1 && export TEST_FINALIZATION=1 && pytest tests
diff --git a/tests/_test_helpers/ENV_SETTING.py b/tests/_test_helpers/ENV_SETTING.py
index 6bc85be..3df026f 100644
--- a/tests/_test_helpers/ENV_SETTING.py
+++ b/tests/_test_helpers/ENV_SETTING.py
@@ -1,10 +1,27 @@
 import json
+import os
 
 TAG = "latest"
+TESTNET_TAG = "2.1.0-testnet"
 REPO_NAME = "confluxchain/conflux-rust"
-IMAGE_FULL_NAME = f"{REPO_NAME}:{TAG}"
+DEV_IMAGE_FULL_NAME = f"{REPO_NAME}:{TAG}"
+TESTNET_IMAGE_FULL_NAME = f"{REPO_NAME}:{TESTNET_TAG}"
 LOCAL_NODE_NAME = "python-sdk-env"
+TESTNET_NODE_NAME = "python-sdk-env-testnet"
 LOCAL_HOST = "127.0.0.1"
 PORT = "12537"
+TESTNET_HOST_PORT = "12637"
 
+# TODO: replace with Contract metadata
 erc20_metadata = json.load(open("tests/_test_helpers/ERC20.json"))
+
+TESTNET_CONFIG_DIR = os.path.join(
+    os.path.dirname(__file__), 
+    "testnet"
+)
+VOLUMES = {
+    TESTNET_CONFIG_DIR: {
+        "bind": "/root/run",
+        "mode": "rw",
+    }
+}
diff --git a/tests/_test_helpers/node.py b/tests/_test_helpers/node.py
index 370ef1c..1a5342f 100644
--- a/tests/_test_helpers/node.py
+++ b/tests/_test_helpers/node.py
@@ -14,14 +14,19 @@
     NotFound
 )
 
+from cfx_account import Account
 from conflux_web3 import (
     Web3,
 )
 from tests._test_helpers.ENV_SETTING import (
-    IMAGE_FULL_NAME,
+    DEV_IMAGE_FULL_NAME,
+    TESTNET_IMAGE_FULL_NAME,
     LOCAL_NODE_NAME,
+    TESTNET_NODE_NAME,
     LOCAL_HOST,
     PORT,
+    VOLUMES,
+    TESTNET_HOST_PORT
 )
 
 def setup_docker_env(client: docker.client.DockerClient, image_name:str, node_name: str):
@@ -91,7 +96,7 @@ class LocalNode(BaseNode):
     if container with node_name (default as "python-sdk-env") already exists, no extra work needs be done
     else pull image and create environment
     """
-    def __init__(self, image_name=IMAGE_FULL_NAME, node_name=LOCAL_NODE_NAME):
+    def __init__(self, image_name=DEV_IMAGE_FULL_NAME, node_name=LOCAL_NODE_NAME):
         self._image_name = image_name
         self._node_name = node_name
         self._url = f"http://{LOCAL_HOST}:{PORT}"
@@ -107,14 +112,13 @@ def __init__(self, image_name=IMAGE_FULL_NAME, node_name=LOCAL_NODE_NAME):
                                                         # auto_remove=True,
                                                         ports={
                                                             f"{PORT}/tcp": f"{PORT}"
-                                                        }) 
+                                                        })
             self._wait_for_start()
 
     @functools.cached_property
     def secrets(self) -> List[str]:
         raw = self._container.exec_run("cat genesis_secret.txt").output.decode("utf-8")  # type: ignore
         secrets = raw.split("\n")
-        secrets.append("0x46b9e861b63d3509c88b7817275a30d22d62c8cd8fa6486ddee35ef0d8e0495f")
         return secrets
     
     def _wait_for_start(self):
@@ -152,7 +156,35 @@ def exit(self):
             self._container.remove(force=True)  # type: ignore
         if self._client:
             self._client.close()
+
+class LocalTestnetNode(LocalNode):
+    def __init__(self, image_name=TESTNET_IMAGE_FULL_NAME, node_name=TESTNET_NODE_NAME, volumes=VOLUMES):
+        self._image_name = image_name
+        self._node_name = node_name
+        self._url = f"http://{LOCAL_HOST}:{TESTNET_HOST_PORT}"
+        self._client = docker.from_env()
         
+        if container := get_existed_container(self._client, self._node_name):
+            self._container = container
+        else:
+            pull_image(self._client, self._image_name)
+            self._container = self._client.containers.run(self._image_name, 
+                                                        name=self._node_name, 
+                                                        detach=True, 
+                                                        # auto_remove=True,
+                                                        volumes=volumes,
+                                                        ports={
+                                                            f"{PORT}/tcp": f"{TESTNET_HOST_PORT}" # use a different port on host
+                                                        })
+            self._wait_for_start()
+    
+    @functools.cached_property
+    def secrets(self) -> List[str]:
+        secrets = [Account.create().privateKey]
+        return secrets 
+    
+    def _wait_for_embedded_tx_finished(self):
+        return
 
 class RemoteTestnetNode(BaseNode):
     def __init__(self) -> None:
@@ -160,8 +192,7 @@ def __init__(self) -> None:
     
     @functools.cached_property
     def secrets(self) -> List[str]:
-        testnet_secret = os.environ.get("TESTNET_SECRET")
+        testnet_secret = os.environ.get("TESTNET_SECRET", None)
         if not testnet_secret:
-            raise RuntimeError("Environment secret is not set")
+            return [Account.create().privateKey]
         return [testnet_secret]
-        
\ No newline at end of file
diff --git a/tests/_test_helpers/testnet/conflux.toml b/tests/_test_helpers/testnet/conflux.toml
new file mode 100644
index 0000000..1f942e5
--- /dev/null
+++ b/tests/_test_helpers/testnet/conflux.toml
@@ -0,0 +1,550 @@
+# bootnodes is a list of nodes that a conflux node trusts, and will be used to sync the blockchain when a node starts. 
+# The value is a string divided by comma without space, and every entry is a node
+# A node is identified by cfxnode://NODEID@PUBLICIP:PORT
+# By default, no bootnodes are provided. What's provided here is a list of nodes that Conflux Team maintains across the world.
+
+bootnodes="cfxnode://604deed272ec2a6f75538f0852e9cd71c616b35655ea8331b5987b3cc682097da76b9c8d2e90601d818acfac54f15af08a6389af245d4149d7d7f4a952bebfb3@39.97.223.148:32323,cfxnode://77bd8823c286e6b159432607d309983eac76830ecdd2bb2b6be7bf3b8e90bbae92f980dd7c8a6c284670679090d3ebf8e43ef91755cdf2eb6caa76532400be4c@182.92.92.126:32323,cfxnode://6582f6e20cfb975ff0b1fe6cb1d97f1e85c0502d44de441f43b032d7c8b986ebbd6a52f228ca55639b49742505bcf3f541551b2485d248e041d9ff1389bb016d@59.110.70.5:32323,cfxnode://24caea3455cf5ff9bbc532c6c6ca39b7edd0ecbbf29725dccdd36b5be629f1b0967391fdd99e81f9e35eb48a85602b2cff7c4c6893b7a906cc6b832c09ae9f33@101.201.127.86:32323,cfxnode://d2d4d2421da4b3b7314b83da06a2ed803965968ebc1614ac41eec5391a136d681a1433dbb56d067ad7e6cab682f35454a87a96e65a3b7eac867eea36b5d8535b@123.57.45.90:32323,cfxnode://ba297dc48bbba48e48c2f01dd0478d3a128b49ba4eccc65d0b800677bb8cc8a72eda57395f5a4b54269ec93189127efbd8ed235836db505f00b0e4558c1c6d97@39.97.208.206:32323,cfxnode://31f9397cfe1bbbecd5667dd13a5b6629201d5bdb9709813c8ad45e21a7ff767f17eb296ee635bb6c1265293bc4ad864db4ca229ad415d31e39121bf2a52405f9@13.229.52.100:32323,cfxnode://a7052f8b9d263549f737334b3cfe4d073fbedf69a21f25754a49a5cd7e83eb998eac1b223e55ae37779c1537bdd429719d0da45637d2d65dbaa917ad2c7085f2@20.43.79.41:32323,cfxnode://869ad83c0f6d8d9f571f62e444dd9a00bb5291e05b65b2d3920db912cdaaa9f283e72a5638ec71aadeeb37f53cdf40fb041214a80d7a6748f6ba96ec62b2909f@137.135.25.219:32323"
+
+# Set the node type to Full node, Archive node, or Light node.
+# Possible values are "full", "archive", or "light".
+# The command line parameter `--full`, `--archive`, or `--light` will overwrite this parameter.
+#
+node_type = "light"
+
+# Some preset develepment configurations.
+# It should not be set in production environment.
+#
+# For both `test` and `dev` modes, we will
+#     * Set initial difficulty to 4
+#
+# `test` mode is for Conflux testing and debugging, we will
+#     * Add latency to peer connections
+#     * Skip handshake encryption check
+#     * Skip header timestamp verification
+#     * Handle NewBlockHash even in catch-up mode
+#     * Allow data propagation test
+#     * Allow setting genesis accounts and generate tx from secrets
+#
+# `dev` mode is for users to run a single node that automatically
+#     generates blocks with fixed intervals
+#     * You are expected to also set `jsonrpc_ws_port`, `jsonrpc_tcp_port`,
+#       and `jsonrpc_http_port` if you want RPC functionalities.
+#     * generate blocks without PoW (either after receiving a transaction or
+#       in fixed period, see ``dev_block_interval_ms'')
+#     * Skip catch-up mode even there is no peer
+#
+# mode = ""
+
+# If you want to setup a single node running Conflux for development. You should
+# COMMENT the bootnodes setting and you should use the following parameters:
+#
+# mode = "dev"
+
+# ``dev_block_interval_ms'' controls the mining rate in the dev mode.
+#
+# If it's not set, blocks will only be generated after receiving a transaction.
+# Otherwise, blocks are automatically generated every ``dev_block_interval_ms'' ms.
+#
+# dev_block_interval_ms = 250
+
+# ----------------- Mining Configuration -----------------
+
+# `mining_author` is the address to receive mining rewards.
+# If set, `mining_type` will be "stratum" by default.
+# The value is a 40-digit hex string or a valid CIP-37 base32 address.
+# By default, the value is not set.
+#
+# mining_author="cfx:aarc9abycue0hhzgyrr53m6cxedgccrmmyybjgh4xg"
+
+# `mining_type` controls whether the mining process goes through the
+# stratum protocol, uses CPU-mining, or disable mining.
+# Possible values are "stratum", "cpu", and "disable".
+# The default value is "stratum" if `mining_author` is set.
+# If the value is set and not "disable", `mining_author` must be set.
+#
+# mining_type = "stratum"
+
+# Listen address for stratum
+#
+# stratum_listen_address = "127.0.0.1"
+
+# Port for stratum.
+#
+# stratum_port = 32525
+
+# Window size for PoW manager
+#
+# pow_problem_window_size = 1
+
+# Secret key for stratum.
+# The value is 64-digit hex string.
+# If not set, the RPC subscription will not check the authorization.
+#
+# stratum_secret = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+
+# -------------- Log-related Configuration -------------
+
+# `log_conf` the path of the log4rs configuration file. The configuration in the file will overwrite the value set by `log_level`.
+# By default, the value is not set.
+#
+log_conf="log.yaml"
+
+# `log_file` is the path of the log file"
+# If not set, the log will only be printed to stdout, and not persisted to files.
+# By default, the value is not set.
+#
+# log_file="conflux.log"
+
+# `log_level` is the printed log level.
+# The value should be one of "error", "warn", "info", "debug", "trace", "off"
+#
+# log_level="info"
+
+# -------------- Network Configuration -------------
+
+# `public_address` is the address of this node used for other nodes to connect to.
+# If not set, the process will try to find out the public IP with best effort, and use `tcp_port` as public port.
+# However, it's HIGHLY RECOMMENDED to set the value manually, especially for machines with IP translated by NAT.
+#
+# public_address="1.1.1.1"
+
+# `tcp_port` is the TCP port that the process listens for P2P messages. The default is 32323.
+#
+# tcp_port=32323
+
+# `public_tcp_port` is the public TCP port that other nodes should connect to. It might be different from
+# the tcp_port in case the machine is behind a NAT. The default is as same as `tcp_port`.
+#
+# public_tcp_port=32323
+
+# `udp_port` is the UDP port used for node discovery.
+# If not set, it will be the same as `port`.
+#
+# udp_port=32323
+
+# `jsonrpc_http_threads` is used to control how many threads to process HTTP rpc requests.
+#
+# jsonrpc_http_threads=1
+
+# `jsonrpc_http_keep_alive` is used to control whether to set KeepAlive for rpc HTTP connections.
+#
+# jsonrpc_http_keep_alive=false
+
+# `jsonrpc_cors` is used to control the rpc domain validation policies.
+# The value should be "none", "all", or a list string split by commas without space.
+# If not set, domain validation is disabled.
+# By default, the values are not set.
+#
+# jsonrpc_cors="all"
+
+# The following parameters are the ports for the node to provide rpc service. If not set,
+# the node will not start rpc services. By default, the `jsonrpc_local_http_port` is set,
+# so as to support the Conflux CLI subcommands. What's provided here is the recommended
+# value if you want to start rpc services for other front-end applications.
+# Note that to serve transaction-related RPCs, `persist_tx_index` should also be set to `true` or
+# the node will only be able to handle very recent transactions.
+#
+# jsonrpc_ws_port=12535
+# jsonrpc_tcp_port=12536
+jsonrpc_http_port=12537
+# jsonrpc_local_tcp_port=12538
+jsonrpc_local_http_port=12539
+# jsonrpc_local_ws_port=12540
+# jsonrpc_http_eth_port=8545
+# jsonrpc_ws_eth_port=8546
+
+# Specify the APIs available through the public JSON-RPC interfaces (HTTP, TCP, WebSocket)
+# using a comma-delimited list of API names.
+# Possible names are: all, safe, cfx, pos, debug, pubsub, test, trace, txpool.
+# `safe` only includes `cfx` and `pubsub`, `txpool`.
+#
+# public_rpc_apis = "safe"
+# public_evm_rpc_apis = "evm"
+
+# --------------- Performance-related Network Parameters ----------------------
+
+# Timeout for block-related requests (GetBlock, GetCmpctBlock, GetBlockTxn)
+#
+# blocks_request_timeout_ms = 20_000
+
+# Time interval to check timeout requests periodically.
+#
+# check_request_period_ms=5000
+
+# Chunk size for snapshot retrieval
+#
+# chunk_size_byte = 4194304
+
+# Control whether to demote peers to unstrusted
+#
+# demote_peer_for_timeout = false
+
+# Maximum network queue size. When reached, the queue will refuse any new data.
+# The unit is MB.
+#
+# egress_queue_capacity = 256
+
+# Minimum queue size for throttling in manner of ratio.
+# The unit is MB.
+#
+# egress_min_throttle = 10
+
+# Maximum queue size for throttling in manner of ratio.
+# The unit is MB.
+#
+# egress_max_throttle = 10
+
+# Time interval to to garbage-collect not block-graph-ready blocks periodically.
+#
+# expire_block_gc_period_s = 900
+
+# Timeout for header-related requests (GetBlockHeaders)
+#
+# headers_request_timeout_ms=10_000
+
+# Time interval to broadcast Status as heartbeat periodically
+#
+# heartbeat_period_interval_ms = 30_000
+
+# Time to maintain transaction digests inflight status.
+#
+# inflight_pending_tx_index_maintain_timeout_ms = 30_000
+
+# Maximum number of timeout allowed in `timeout_observing_period_s`.
+# If the max is reached, the peer will be disconnected.
+# `demote_peer_for_timeout` controls if the peer will be demoted in this case.
+#
+# max_allowed_timeout_in_observing_period = 10
+
+# Maximum number of peers to download state chunks from.
+#
+# max_download_state_peers = 8
+
+# Maximum number of handshaking sessions at the same time.
+#
+# max_handshakes = 64
+
+# Maximum number of incoming connections.
+#
+# max_incoming_peers = 48
+
+# Maximum number of outgoing connections.
+#
+# max_outgoing_peers = 16
+
+# Maximum number of outgoing connections to archive nodes. 0 represents
+# not required to connect to archive nodes. E.g. light node or full node
+# need not to connect to archive nodes.
+#
+# max_outgoing_peers_archive = 0
+
+# Maximum number of inflight requests for each peer.
+# If the max is reached, requests will be buffered until inflight requests are responded or timeout.
+#
+# max_inflight_request_count = 64
+
+# Maximum number of peers to broadcast transaction digests.
+#
+# max_peers_tx_propagation = 128
+
+# Maximum cached received block size waiting to be processed.
+#
+# max_unprocessed_block_size_mb = 128
+
+# Minimum number of peers to broadcast transaction digests.
+#
+# min_peers_tx_propagation = 8
+
+# The time to maintain received transactions to avoid duplicated requests.
+#
+# received_tx_index_maintain_timeout_ms = 300_000
+
+# Whether to request blocks with public key during catch-up to avoid recovering
+# the public keys for transactions.
+#
+# request_block_with_public = false
+
+# Time interval to broadcast transaction digests periodically.
+#
+# send_tx_period_ms = 1300
+
+# Timeout for requesting snapshot candidate.
+#
+# snapshot_candidate_request_timeout_ms = 10_000
+
+# Timeout for requesting snapshot chunks.
+#
+# snapshot_chunk_request_timeout_ms = 30_000
+
+# Timeout for requesting snapshot manifests.
+#
+# snapshot_manifest_request_timeout_ms = 30_000
+
+# `throttling_conf` is configuration file in TOML format to throttle RPCs, P2P messages.
+# Throttling is enabled only when the parameter specified.
+#
+# throttling_conf="throttling.toml"
+
+# The time period to observe if a peers has too many timeouts.
+#
+# timeout_observing_period_s = 600
+
+# Timeout for transaction requests.
+#
+# transaction_request_timeout_ms = 30_000
+
+# Time to maintain information of sent-out transactions for answering requests.
+#
+# tx_maintained_for_peer_timeout_ms = 600_000
+
+# --------------- Peer Management Parameters -------------
+
+# Timeout duration for refreshing discovery protocol when there are NOT enough outgoing connections.
+#
+# discovery_fast_refresh_timeout_ms = 10_000
+
+# Timeout duration for initiating peer connection management.
+#
+# discovery_housekeeping_timeout_ms = 1_000
+
+# Period between consecutive rounds of the same current discovery process.
+#
+# discovery_round_timeout_ms = 500
+
+# `enable_discovery` is used to control whether the node will ask its neighbors for new peers
+# and whether it will respond to other nodes' discovery requests.
+#
+# enable_discovery=true
+
+# `netconf_dir` is the directory to store network related persistent data, including `net_key`,
+# a list of trusted nodes and a list of untrusted nodes.
+#
+# By default, it is stored under the directory configured with `conflux_data_dir` with the directory name `net_config`.
+# If set, the directory path will not be related to `conflux_data_dir` anymore.
+#
+# netconf_dir="./blockchain_data/net_config"
+
+# `net_key` is the 256-bit private key to generate a unique node id for this node.
+# The value is a 64-digit hex string.
+# If not set, the node will try to read from the file "key" under the directory `netconf_dir`.
+# If the file is not found, the node will generate a random key.
+# By default, the value is not set.
+# If a node is restarted, it's suggested to keep the key unchanged.
+#
+# net_key="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+
+# Timeout duration for persisting node table.
+#
+# node_table_timeout_s = 300
+
+# Connection lifetime threshold for promotion.
+#
+# node_table_promotion_timeout_s = 259200
+
+
+# `session_ip_limits` limits the number of TCP connections per IP address or subnet for security consideration.
+# Its format is "n1,n2,n3,n4", where n1 is the quota of TCP connections for a single IP address, and n2/n3/n4
+# are the quotas for subnet a/b/c. The default value is "1,8,4,2", which means:
+#   1) Only 1 TCP connection allowed for a single IP address.
+#   2) 8 TCP connections allowd for subnet a, e.g. 192.xxx.xxx.xxx/8
+#   3) 4 TCP connections allowd for subnet b, e.g. 192.168.xxx.xxx/16
+#   4) 2 TCP connections allowd for subnet c, e.g. 192.169.0.xxx/24
+# Note, 0 represents unlimited.
+#
+# session_ip_limits="1,8,4,2"
+
+# `subnet_quota` limits the number of nodes for a subnet B (e.g. 192.168.xxx.xxx/16) stored in database.
+# Nodes in database are used to establish outgoing TCP connections for P2P communications.
+# Note, 0 represents unlimited.
+#
+# subnet_quota=32
+
+# ---------------- Block number index parameters -----------------
+
+# Whether to persist block number indices.
+# This only needs to be enabled if you want to use RPCs that take block numbers as an input.
+#
+# persist_block_number_index = true
+
+# ---------------- Transaction Cache Parameters -----------------
+
+# Whether to persist transaction indices.
+# This only needs to be enabled if you want to reliably answer transaction-related RPCs.
+#
+# persist_tx_index = false
+
+# Time to keep transactions in in-memory transaction cache.
+#
+# tx_cache_index_maintain_timeout_ms = 300_000
+
+# Maximum number of transactions allowed in the transaction pool.
+#
+# tx_pool_size = 500_000
+
+# Minimum allowed transaction gas price in the transaction pool.
+#
+# tx_pool_min_tx_gas_price = 1
+
+# ------------------ Storage Parameters ----------------------
+
+# The number of additional snapshot before the current stable checkpoint that we will maintain.
+# If it's 0, all snapshot before stable genesis will be deleted and the states are unavailable.
+#
+# additional_maintained_snapshot_count = 0
+
+# The additional number of epochs to keep different kinds of data before the current era genesis checkpoint.
+# For full/light nodes, the default value is 0, meaning all data before the era checkpoint will be removed.
+# For archive nodes, the default behavior is keeping all these data, while setting these parameters manually
+# will overwrite the default behavior of corresponding data types and garbage collect them accordingly.
+#
+# additional_maintained_block_body_epoch_count = 0
+# additional_maintained_execution_result_epoch_count = 0
+# additional_maintained_reward_epoch_count = 0
+# additional_maintained_trace_epoch_count = 0
+# additional_maintained_transaction_index_epoch_count = 0
+
+# Time interval to evict old data from in-memory data cache.
+#
+# block_cache_gc_period_ms = 5_000
+
+# Database type to store block-related data.
+# Supported: rocksdb, sqlite.
+#
+# block_db_type = "rocksdb"
+
+# The root directory of all data (block data, state data, and node database).
+#
+# conflux_data_dir = "./blockchain_data"
+
+# The directory to store block-related data.
+#
+# By default, it is stored under the directory configured with `conflux_data_dir` with the directory name `blockchain_db`.
+# If set, the directory path will not be related to `conflux_data_dir` anymore.
+#
+# block_db_dir = "./blockchain_data/blockchain_db"
+
+# Maximum size of cached ledger data (block, receipts, e.t.c.)
+# The unit is MB.
+#
+# ledger_cache_size = 1024
+
+# Rocksdb cache size.
+# Only applies if `block_db_type = "rocksdb"`.
+#
+# rocksdb_cache_size = 128
+
+# Rocksdb compaction file path.
+# Only applies if `block_db_type = "rocksdb"`.
+# If not set, compaction configuration will be set automatically by rocksdb.
+#
+# rocksdb_compaction_profile = "./compact_file.conf"
+
+# State storage parameters.
+# Refer to the documentation for details.
+#
+# storage_delta_mpts_cache_recent_lfu_factor=4.0
+# storage_delta_mpts_cache_size=20000000
+# storage_delta_mpts_cache_start_size=1000000
+# storage_delta_mpts_node_map_vec_size=80000000
+# storage_delta_mpts_slab_idle_size=200000
+
+# Configure the maximal open MPT count. Open MPTs are maintained as an LRU cache, and we will close the database handle
+# for the evicted MPT once its usage finishes. Every MPT contains the data written in 2000 epochs.
+# Accessing a state involves opening both its delta MPT and intermediate MPT,
+# so setting this to 4 allows to access two states at the same time. A full node always needs one latest state to
+# process new epochs, so with the default value we can frequently access one old state (by calling state-related RPCs)
+# efficiently without the overhead of opening/closing databases.
+#
+# Idealy, if the RPC working set involves accessing X state ranges frequently (each range has 2000 epochs),
+# this value should be set to 2+2X to avoid thrashing.
+# However, increasing the value may increase the system memory usage by opening more database instances at the same
+# time, and the memory usage of an MPT is affected by `rocksdb_cache_size`.
+#
+# storage_max_open_mpt_count = 4
+
+# Configure if we strictly check the tx index before garbage collection.
+# Setting it to `false` will improve the performance. But if the value is `false`, it's possible that although the
+# epoch where a tx is executed should not be garbage collected, the tx index of this tx is removed because it's packed
+# in an already garbage collected epoch.
+#
+# strict_tx_index_gc = true
+
+# The epoch number where we want to download the state and start re-executing transactions.
+# For full nodes, if the value is not set, the parameter will not take effects.
+# For archive node, the default value is 0.
+#
+# sync_state_starting_epoch = 0
+
+# The number of epochs needed between our best_epoch and our neighbours' best_epoch that we want to
+# start downloading states during catching up.
+#
+# sync_state_epoch_gap = 20
+
+# ------------------ Light Node Parameters ----------------------
+
+# Header sync parameters.
+# ln_header_request_batch_size = 30
+# ln_header_request_timeout_sec = 2
+# ln_max_headers_in_flight = 1000
+
+# Epoch sync parameters.
+# ln_epoch_request_batch_size = 100
+# ln_epoch_request_timeout_sec = 2
+# ln_max_parallel_epochs_to_request = 10
+# ln_num_epochs_to_request = 200
+# ln_num_waiting_headers_threshold = 1000
+
+# -------------------- Trace Parameters -------------------
+
+# Whether to trace EVM execution and records the result in database.
+#
+# executive_trace = false
+
+
+# -------------------- Others -------------------
+
+# Time (in milliseconds) after which accounts are re-read from disk.
+#
+# account_provider_refresh_time_ms = 1000
+
+# Whether to allow execution without deferring if the execution thread is idle.
+#
+# enable_optimistic_execution = true
+
+# Maximum number of blocks whose timestamp is in the near future is maintained in memory.
+#
+# future_block_buffer_capacity = 32768
+
+# Maximum number of log entries returned from cfx_getLogs.
+# If not set, cfx_getLogs will not limit the number of logs returned.
+#
+# get_logs_filter_max_limit = 10
+
+# Epoch batch size used in log filtering.
+# Larger batch sizes may improve performance but might also prevent consensus from making progress under high RPC load.
+#
+# get_logs_epoch_batch_size = 32
+
+# The maximal allowed number of epochs between `from_epoch` and `to_epoch` in the filter to call `cfx_getLogs`.
+# If not set, there is no limit on the gap.
+# By default it is not set.
+#
+# get_logs_filter_max_epoch_range = 10000
+
+# Maximum number of transactions allowed for peers to send to a catch-up node.
+#
+# max_trans_count_received_in_catch_up = 60_000
+
+# The chain ID of Conflux Network (Conflux space)
+# 1 for testnet
+# 1029 for Tethys
+#
+chain_id = 1
+node_table_promotion_timeout_s = 9600
+
+# The EVM chain ID of Conflux Network (EVM space)
+# 1030 for Tethys
+#
+evm_chain_id = 71
+
+dev_pos_private_key_encryption_password = "your-pos-pwd"
diff --git a/tests/_test_helpers/testnet/log.yaml b/tests/_test_helpers/testnet/log.yaml
new file mode 100644
index 0000000..70fcef3
--- /dev/null
+++ b/tests/_test_helpers/testnet/log.yaml
@@ -0,0 +1,51 @@
+refresh_rate: 30 seconds
+
+appenders:
+  stdout:
+    kind: console
+    encoder:
+        pattern: "{d} {h({l}):5.5} {T:<20.20} {t:12.12} - {m:10.20000}{n}"
+    filters:
+      -
+        kind: "threshold"
+        level: "info"
+
+
+  logfile:
+    kind: rolling_file
+    path: "log/conflux.log"
+    encoder:
+        pattern: "{d} {h({l}):5.5} {T:<20.20} {t:12.12} - {m:.20000}{n}"
+
+    policy:
+      kind: compound
+      trigger:
+        kind: size
+        limit: 2000 mb
+      roller:
+        kind: fixed_window
+        pattern: "log/archive/conflux.{}.gz"
+        count: 50
+
+root:
+    level: info
+    appenders:
+        - stdout
+        - logfile
+
+# We can set log level for crates individually
+loggers:
+    network:
+        level: info
+    cfxcore:
+        level: info
+    rpc:
+        level: info
+    blockgen:
+        level: info
+    client:
+        level: info
+    cfx_storage:
+        level: info
+    cfx_statedb:
+        level: info
diff --git a/tests/_test_helpers/testnet/pos_config/genesis_file b/tests/_test_helpers/testnet/pos_config/genesis_file
new file mode 100644
index 0000000..9151d77
Binary files /dev/null and b/tests/_test_helpers/testnet/pos_config/genesis_file differ
diff --git a/tests/_test_helpers/testnet/pos_config/initial_nodes.json b/tests/_test_helpers/testnet/pos_config/initial_nodes.json
new file mode 100644
index 0000000..3b1c655
--- /dev/null
+++ b/tests/_test_helpers/testnet/pos_config/initial_nodes.json
@@ -0,0 +1,1 @@
+{"initial_nodes":[{"address":"0x0000000000000000000000000000000000000000","bls_key":"88050f7a9e73c48bafa812370ca94f115ae4a708347e5d28d9750b60aa8b1f7a5039f04a6ad544c101f5a517b64bd286","vrf_key":"02f35c24a76883a9ca2ecc2c65192176582cd6715c933916c725d42bcca775bd2e","voting_power":341,"register_tx":{"nonce":"0x0","gas_price":"0x0","gas":"0x0","action":"Create","value":"0x0","storage_limit":0,"epoch_height":0,"chain_id":0,"data":[]}},{"address":"0x0000000000000000000000000000000000000000","bls_key":"87ad70ca07a86523c0e426d893cfd4b681cae65dfacdc01b8cc21d8f518dfb0768d8003a1087a186c9de87abab4765d3","vrf_key":"024ecd0644f085af37f5459355a1b3c28ba34e7aaa175b77000a61f5acc9a4b6c9","voting_power":1214,"register_tx":{"nonce":"0x0","gas_price":"0x0","gas":"0x0","action":"Create","value":"0x0","storage_limit":0,"epoch_height":0,"chain_id":0,"data":[]}},{"address":"0x0000000000000000000000000000000000000000","bls_key":"97b95f70b30048684ecfa3fec834c272fb872ca495eb434fe6bc5ebd183725efbe1b8ee935682f9f23dfd2a02c844e7b","vrf_key":"02b4a62c4b294fc0d0fe0361ac1d2464838f28d93c2b19771b5bd697cafe2203fc","voting_power":2000,"register_tx":{"nonce":"0x0","gas_price":"0x0","gas":"0x0","action":"Create","value":"0x0","storage_limit":0,"epoch_height":0,"chain_id":0,"data":[]}},{"address":"0x0000000000000000000000000000000000000000","bls_key":"a319bc0306f5d025610aada4f295859d162c8f40bc918c05f85ea65d454e98c0416e388de85ea742fa40b47f3e32dded","vrf_key":"024b940667411a387e4cf0f39b8349208aa68415a449febe1f21a6e8c2e28ba3d6","voting_power":10000,"register_tx":{"nonce":"0x0","gas_price":"0x0","gas":"0x0","action":"Create","value":"0x0","storage_limit":0,"epoch_height":0,"chain_id":0,"data":[]}},{"address":"0x0000000000000000000000000000000000000000","bls_key":"afccd4c5f9b9cd02807d7c513b0b8ca75ecc4a4553c1d74e437db45a40db720c5de791f28170eaad0867c3d724ed3a57","vrf_key":"0272d6bf3866fc642b3b2b5b40cc863322e8e035685a6eca32f7a8ee2afcf0e4bd","voting_power":501,"register_tx":{"nonce":"0x0","gas_price":"0x0","gas":"0x0","action":"Create","value":"0x0","storage_limit":0,"epoch_height":0,"chain_id":0,"data":[]}},{"address":"0x0000000000000000000000000000000000000000","bls_key":"b60bf9f209b9c00a503df9c1e04823ebf768b4cf2933c8983495013d40022b0155105b7bc5a4191b3783cb7f32b99505","vrf_key":"025dbda7c685716925a41cae66bb730246c5b960852e5cc2cdb58887aa2290fda3","voting_power":501,"register_tx":{"nonce":"0x0","gas_price":"0x0","gas":"0x0","action":"Create","value":"0x0","storage_limit":0,"epoch_height":0,"chain_id":0,"data":[]}},{"address":"0x0000000000000000000000000000000000000000","bls_key":"947aa54b808ad3a58465c43320894b4409e5e949794d5ce25b2b3570815947c338b631c62482b871ea640f0b8598e5d8","vrf_key":"036d9e75654d87fc5484d11d5265fb4e0910b1dc09d4e76a049a9c43c45891ba86","voting_power":501,"register_tx":{"nonce":"0x0","gas_price":"0x0","gas":"0x0","action":"Create","value":"0x0","storage_limit":0,"epoch_height":0,"chain_id":0,"data":[]}},{"address":"0x0000000000000000000000000000000000000000","bls_key":"893386c1d9fa338162662e5920e095c8b4221dffaab61eb6b754e92221f5e27172a099afce607c3b00df2b72e27c7082","vrf_key":"03df2ba33c52bf625acc8bf3331ce059617180da98d225fc9128258f13e5fffaf2","voting_power":13318,"register_tx":{"nonce":"0x0","gas_price":"0x0","gas":"0x0","action":"Create","value":"0x0","storage_limit":0,"epoch_height":0,"chain_id":0,"data":[]}},{"address":"0x0000000000000000000000000000000000000000","bls_key":"adc3ffbb2ac81ddfc1a56829580b1f31c41e9cc90d82796f69918564c6febb6e266e80421e8f53af6b15cc884190963f","vrf_key":"03285d72aa04a01b7b9f2bc4d2201c6c9d542c01ace24370142b762550a9ce07b6","voting_power":501,"register_tx":{"nonce":"0x0","gas_price":"0x0","gas":"0x0","action":"Create","value":"0x0","storage_limit":0,"epoch_height":0,"chain_id":0,"data":[]}}],"initial_committee":[["3c64d606c0f3d35945572c9bfeb6c070f9b2b51f78ab4ecc32f1a48a63116303",4],["4c5c051b8f72b838e2682302fabde1a3d5372d39476674096110448ea22a6281",15],["8adcc328fbf598dcbe6d7c715da0768fbb098073abfb942e815d7bf95abb0327",2],["9a6fcdd8646a4c56735414187ae6b9f343732c2bb52f678f4fa2f92f3625402a",6],["9b63065c12300b32308ca18022178c1032544e07ffe2821c948c2d31fc5e200a",6],["9f9d23b2c233bbcf127d9bb01065e96a3db6a1ff07349206a859a8ca822f248b",18],["a41ee1a2ddbb7e94236781cbb827fc1d130c8a79ccc7f2eb00e2096f7420c81d",149],["ce257f9b12d67089057d71fe4f20f25569268582e1fa6f6673bb60934582f9f3",2],["e78ffdec03c1fb0bb36427b51b1d92c7a0aac663688eb20ea13162c188482b5c",98]],"initial_seed":"0x36a6a72ff2c1facd7932649f5358730957a52b39920e1ee07e4825f0f2ccbe08"}
\ No newline at end of file
diff --git a/tests/_test_helpers/testnet/pos_config/pos_config.yaml b/tests/_test_helpers/testnet/pos_config/pos_config.yaml
new file mode 100644
index 0000000..982b498
--- /dev/null
+++ b/tests/_test_helpers/testnet/pos_config/pos_config.yaml
@@ -0,0 +1,18 @@
+
+base:
+  #data_dir: ./pos_db
+  role: validator
+  waypoint:
+    from_config: 0:81bf85cc19ca409846c142497552c998812aa96a72cdeab691892674db3e83cd
+consensus:
+  round_initial_timeout_ms: 60000
+  safety_rules:
+    service:
+      type: local
+execution:
+  genesis_file_location: ./genesis_file
+logger:
+  file: ./log/pos.log
+  level: INFO
+#storage:
+  #dir: ./pos_db/db
diff --git a/tests/_test_helpers/testnet/throttling.toml b/tests/_test_helpers/testnet/throttling.toml
new file mode 100644
index 0000000..89e9b28
--- /dev/null
+++ b/tests/_test_helpers/testnet/throttling.toml
@@ -0,0 +1,49 @@
+# Throttling value format:
+#
+# Token bucket: <max_tokens>,<init_tokens>,<recharge_rate>,<default_cost>,<max_throttled_tolerates>
+#   max_tokens: maximum number of tokens in bucket.
+#   init_tokens: number of tokens initialized in bucket.
+#   recharge_rate: number of tokens recharged per second.
+#   default_cost: number of tokens acquired at a time by default.
+#   max_throttled_tolerates: maximum acquires allowed even throttled, otherwise client may be disconnected.
+
+[sync_protocol]
+Status="90,90,1,30,0" # heartbeat interval 30s
+NewBlockHashes="200,200,20,1,100"
+Transactions="50,50,10,1,50"
+GetBlockHeaders="200,200,100,1,100"
+NewBlock="20,20,4,1,20"
+GetBlocks="200,200,100,1,50"
+GetCompactBlocks="200,200,20,1,100"
+GetBlockTxn="200,200,20,1,100"
+DynamicCapabilityChange="20,20,5,1,30"
+TransactionDigests="50,50,10,1,50"
+GetTransactions="50,50,10,1,50"
+GetTransactionsFromTxHashes="50,50,10,1,50"
+GetBlockHashesByEpoch="50,50,10,1,50"
+SnapshotManifestRequest="50,50,10,1,50"
+SnapshotChunkRequest="50,50,10,1,50"
+Throttled="100,100,10,1,50"
+
+# Suggest to limit the IP address to access the RPC as well.
+[rpc]
+cfx_gasPrice="20,20,5,1,5"
+cfx_epochNumber="20,20,5,1,5"
+cfx_getBalance="50,50,10,1,5"
+cfx_getCode="20,20,5,1,5"
+cfx_getBlockByHash="20,20,5,1,5"
+cfx_getBlockByHashWithPivotAssumption="20,20,5,1,5"
+cfx_getBlockByEpochNumber="20,20,5,1,5"
+cfx_getBestBlockHash="50,50,10,1,5"
+cfx_getNextNonce="50,50,10,1,5"
+cfx_sendRawTransaction="50,50,10,1,5"
+cfx_call="20,20,5,1,5"
+cfx_getLogs="20,20,5,1,5"
+cfx_getTransactionByHash="50,50,10,1,5"
+cfx_estimateGas="10,10,2,1,1"
+cfx_getBlocksByEpoch="20,20,5,1,5"
+cfx_getTransactionReceipt="50,50,10,1,5"
+
+[rpc_local]
+
+[light_protocol]
\ No newline at end of file
diff --git a/tests/base_features/test_default_account.py b/tests/base_features/test_default_account.py
index 3340a44..f69d382 100644
--- a/tests/base_features/test_default_account.py
+++ b/tests/base_features/test_default_account.py
@@ -1,28 +1,19 @@
 from conflux_web3 import Web3
 from web3.datastructures import AttributeDict
 
-def test_default_account(w3: Web3, secret_key):
+def test_default_account_set(w3: Web3, secret_key):
     local_account = w3.account.from_key(secret_key)
     w3.cfx.default_account = local_account.address
     assert w3.cfx.default_account == local_account.address
 
-def test_default_account_implicit_cast(w3: Web3, secret_key):
-    """an object with attribute "address" will be accepted 
-    """
-    local_account = w3.account.from_key(secret_key)
     w3.cfx.default_account = local_account
     assert w3.cfx.default_account == local_account.address
-    
-    acct = AttributeDict({
-        "address": local_account.address
-    })
-    w3.cfx.default_account = acct # type: ignore
-    assert w3.cfx.default_account == local_account.address
+
+    assert local_account.address in w3.wallet
     
 def test_web3_connection(w3: Web3):
     assert w3.is_connected()
-    
+
     # a not valid Provider
     w3_ = Web3(Web3.HTTPProvider("http://127.0.0.1:11111"))
     assert not w3_.is_connected()
-    
\ No newline at end of file
diff --git a/tests/conftest.py b/tests/conftest.py
index 3bb7876..c8888ac 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,10 +1,8 @@
-from typing import Iterable, Sequence
+from typing import Iterable, Sequence, Union
 import os
 import pytest
-from tests._test_helpers.node import LocalNode, BaseNode, RemoteTestnetNode
-from tests._test_helpers.ENV_SETTING import (
-    PORT,
-    LOCAL_HOST,
+from tests._test_helpers.node import (
+    LocalNode, BaseNode, RemoteTestnetNode, LocalTestnetNode
 )
 
 from cfx_account.account import LocalAccount
@@ -20,14 +18,16 @@
 )
 
 @pytest.fixture(scope="session")
-def use_remote() -> bool:
-    return bool(os.environ.get("TESTNET_SECRET"))
+def use_testnet() -> bool:
+    return bool(os.environ.get("TESTNET_SECRET")) or bool(os.environ.get("USE_TESTNET"))
 
 @pytest.fixture(scope="session")
-def node(use_remote) -> Iterable[BaseNode]:
-    if use_remote:
-        node = RemoteTestnetNode()
+def node(use_testnet) -> Iterable[BaseNode]:
+    if use_testnet:
+        node = RemoteTestnetNode() # connection error might occur if using public RPC
+        # node = LocalTestnetNode() 
         yield node
+        # node.exit()
     else:
         node = LocalNode()
         yield node
@@ -38,7 +38,7 @@ def node_url(node):
     return node.url
 
 @pytest.fixture(scope="session")
-def secret_key(node: LocalNode) -> str:
+def secret_key(node: LocalNode) -> Union[str, None]:
     """
     Returns:
         str: secret key with enough balance
@@ -57,14 +57,31 @@ def w3(node_url: str, node: LocalNode) -> Web3:
     # assert w3.isConnected()
     return w3
 
+@pytest.fixture(scope="session")
+def account(node_url: str, secret_key) -> LocalAccount:
+    """external_account, not supported by node
+    """
+    provider = Web3.HTTPProvider(node_url)
+    w3 = Web3(provider=provider)
+    acct = w3.account.from_key(secret_key)
+    if w3.cfx.get_balance(acct.address) != 0:
+        return w3.account.from_key(secret_key)
+    elif w3.cfx.chain_id == 1:
+        w3.cfx.default_account = acct
+        faucet = w3.cfx.contract(name="Faucet")
+        faucet.functions.claimCfx().transact().executed()
+        return acct
+    else:
+        raise Exception("Unexpected exception: not local nor testnet node and no environment secret key is set")
+
 @pytest.fixture(scope="module")
-def moduled_w3(node_url: str, node: LocalNode, secret_key) -> Web3:
+def moduled_w3(node_url: str, node: LocalNode, account) -> Web3:
     """
     a web3 instance for the convenience to create module shared objects
     e.g. a transaction or a contract which is required for the module
     NOTE: DON'T CHANGE PROPERTY OF THIS W3
     """
-    account = Account.from_key(secret_key, )
+    # account = Account.from_key(secret_key, )
     provider = Web3.HTTPProvider(node_url)
     w3 = Web3(provider=provider)
     # assert w3.isConnected()
@@ -81,14 +98,7 @@ def address(node_url, secret_key) -> str:
     return addr
 
 @pytest.fixture
-def account(w3: Web3, secret_key) -> LocalAccount:
-    """external_account, not supported by node
-    """
-    return w3.account.from_key(secret_key)
-
-
-@pytest.fixture
-def embedded_accounts(w3: Web3, use_remote: bool) -> Sequence[Base32Address]:
-    if use_remote:
+def embedded_accounts(w3: Web3, use_testnet: bool) -> Sequence[Base32Address]:
+    if use_testnet:
         return []
-    return w3.cfx.accounts
\ No newline at end of file
+    return w3.cfx.accounts
diff --git a/tests/middleware/test_pending.py b/tests/middleware/test_pending.py
index e9b443d..cabfc31 100644
--- a/tests/middleware/test_pending.py
+++ b/tests/middleware/test_pending.py
@@ -1,3 +1,4 @@
+import os
 from typing import TYPE_CHECKING, Sequence, cast
 from cfx_account.account import LocalAccount
 
@@ -9,7 +10,7 @@
 if TYPE_CHECKING:
     from conflux_web3 import Web3
 
-def test_pending(w3: "Web3", account: LocalAccount):
+def test_pending(w3: "Web3", account: LocalAccount, use_testnet):
     # activate by default
     # w3.middleware_onion.add(PendingTransactionMiddleware)
     
@@ -34,4 +35,5 @@ def test_pending(w3: "Web3", account: LocalAccount):
     pending.mined()
     pending.executed()
     pending.confirmed()
-    # TODO: finalized testing
+    if use_testnet and os.environ.get("TEST_FINALIZATION", None):
+        pending.finalized()
diff --git a/tests/pm/test_pm.py b/tests/pm/test_pm.py
new file mode 100644
index 0000000..d7e11f4
--- /dev/null
+++ b/tests/pm/test_pm.py
@@ -0,0 +1,12 @@
+from ethpm import Package, ASSETS_DIR
+
+def test_contract_factory(w3):
+    # ethpm_spec_dir = get_ethpm_spec_dir()
+    escrow_manifest_path = ASSETS_DIR / 'escrow' / 'with_bytecode_v3.json'
+    # erc20_manifest_path = '/Users/conflux-y/Documents/code/web3.py/ethpm/assets/standard-token/with_bytecode_v3.json'
+    escrow_package = Package.from_file(escrow_manifest_path, w3)
+    assert isinstance(escrow_package, Package)
+    
+    factory = escrow_package.get_contract_factory("Escrow") # type: ignore
+    
+    assert factory
diff --git a/tests/rpcs/test_cfx_rpcs.py b/tests/rpcs/test_cfx_rpcs.py
index 296b28b..83024b0 100644
--- a/tests/rpcs/test_cfx_rpcs.py
+++ b/tests/rpcs/test_cfx_rpcs.py
@@ -85,8 +85,8 @@ def test_get_pos_economics(self, w3: Web3):
         TypeValidator.validate_typed_dict(info, "PoSEconomicsInfo")
 
     # TODO: finish this test after pos RPC finished
-    # def test_get_pos_reward_by_epoch(self, w3: Web3, use_remote: bool):
-    #     if use_remote:
+    # def test_get_pos_reward_by_epoch(self, w3: Web3, use_testnet: bool):
+    #     if use_testnet:
     #         info = w3.cfx.get_pos_reward_by_epoch("100")
     #         TypeValidator.validate_typed_dict(info, "PoSEpochRewardInfo")
 
@@ -107,7 +107,10 @@ def test_get_balance(self, w3: Web3, address):
         # the balance is supposed to be non-zero
         assert balance > 0
 
-    def test_get_balance_empty_param(self, w3: Web3):
+    def test_get_balance_empty_param(self, w3: Web3, use_testnet):
+        # TODO: remove use_testnet if statement after testnet node is repaired
+        if use_testnet:
+            return
         with pytest.raises(ValueError):
             w3.cfx.get_balance()
             
@@ -120,15 +123,15 @@ def test_get_code(self, w3: Web3, contract_address):
         # test different cases
         # contract address / user address
         contract_code = w3.cfx.get_code(contract_address, w3.cfx.epoch_number_by_tag("latest_state"))
-        assert contract_code
+        assert isinstance(contract_code, bytes) # reorg might happen, so we only assert the variable type
         
         user_code = w3.cfx.get_code(w3.cfx.account.create().address)
         assert not user_code
         
-    def test_get_storage_at(self, w3: Web3, contract_address, use_remote):
+    def test_get_storage_at(self, w3: Web3, contract_address, use_testnet):
         # TODO: a potential bug in RPC, at present we ignore the testing in local node
-        if use_remote:
-            storage = w3.cfx.get_storage_at(contract_address, 100, w3.cfx.epoch_number)
+        if use_testnet:
+            storage = w3.cfx.get_storage_at(contract_address, 100, w3.cfx.epoch_number_by_tag("latest_state"))
             assert isinstance(storage, bytes)
         else:
             pass
@@ -179,7 +182,10 @@ def test_get_transaction_count(self, w3: Web3, address):
         nonce = w3.cfx.get_transaction_count(address)
         assert nonce >= 0
 
-    def test_get_next_nonce_empty_param(self, w3: Web3):
+    def test_get_next_nonce_empty_param(self, w3: Web3, use_testnet):
+        # TODO: remove use_testnet if statement after testnet node is repaired
+        if use_testnet:
+            return
         with pytest.raises(ValueError):
             w3.cfx.get_next_nonce()
 
@@ -199,8 +205,8 @@ def test_get_tx(moduled_w3: Web3, contract_address):
 
     TypeValidator.validate_typed_dict(transaction_receipt, "TxReceipt")
 
-def test_accounts(w3: Web3, use_remote: bool):
-    if use_remote:
+def test_accounts(w3: Web3, use_testnet: bool):
+    if use_testnet:
         assert True
         return
     
@@ -217,11 +223,11 @@ def test_get_confirmation_risk(w3: Web3, tx_hash):
     risk = w3.cfx.get_confirmation_risk_by_hash(blockHash)
     assert risk < 1
 
-def preprocess_block_data(block_data: BlockData, use_remote: bool) -> BlockData:
+def preprocess_block_data(block_data: BlockData, use_testnet: bool) -> BlockData:
     """
     preprocess block in local testnet
     """    
-    if not use_remote:
+    if not use_testnet:
         # local node may not run pos chain
         block_data = dict(block_data) # type: ignore
         block_data['posReference'] = HexBytes("0x0") # type: ignore
@@ -233,23 +239,23 @@ def block_hash(self, w3: Web3, tx_hash):
         return w3.cfx.wait_for_transaction_receipt(tx_hash)['blockHash']
     
     @pytest.fixture
-    def block_data(self, w3:Web3, block_hash, use_remote):
+    def block_data(self, w3:Web3, block_hash, use_testnet):
         block_data = w3.cfx.get_block_by_hash(block_hash, True)
-        # if not use_remote:
+        # if not use_testnet:
         #     # local node may not run pos chain
         #     block_data = dict(block_data)
         #     block_data['posReference'] = HexBytes("0x0")
-        block_data = preprocess_block_data(block_data, use_remote)
+        block_data = preprocess_block_data(block_data, use_testnet)
         return block_data
     
     @pytest.fixture
-    def no_full_block_data(self, w3:Web3, block_hash, use_remote):
+    def no_full_block_data(self, w3:Web3, block_hash, use_testnet):
         block_data = w3.cfx.get_block_by_hash(block_hash, False)
-        # if not use_remote:
+        # if not use_testnet:
         #     # local node may not run pos chain
         #     block_data = dict(block_data)
         #     block_data['posReference'] = HexBytes("0x0")
-        block_data = preprocess_block_data(block_data, use_remote)
+        block_data = preprocess_block_data(block_data, use_testnet)
         return block_data
     
     def test_get_block_by_hash(self, block_data, no_full_block_data):
@@ -258,16 +264,16 @@ def test_get_block_by_hash(self, block_data, no_full_block_data):
         TypeValidator.validate_typed_dict(no_full_block_data, "BlockData")
         return block_data
 
-    def test_get_block_by_epoch_number(self, w3:Web3, block_data: BlockData, use_remote):
+    def test_get_block_by_epoch_number(self, w3:Web3, block_data: BlockData, use_testnet):
         assert block_data['epochNumber'] is not None
         data_ = w3.cfx.get_block_by_epoch_number(block_data['epochNumber'], True)
-        data_ = preprocess_block_data(data_, use_remote)
+        data_ = preprocess_block_data(data_, use_testnet)
         TypeValidator.validate_typed_dict(data_, "BlockData")
         
-    def test_get_block_by_block_number(self, w3:Web3, block_data: BlockData, use_remote):
+    def test_get_block_by_block_number(self, w3:Web3, block_data: BlockData, use_testnet):
         assert block_data['blockNumber'] is not None
         data_ = w3.cfx.get_block_by_block_number(block_data['blockNumber'], True)
-        data_ = preprocess_block_data(data_, use_remote)
+        data_ = preprocess_block_data(data_, use_testnet)
         assert dict(data_) == dict(block_data)
 
     def test_get_best_block_hash(self, w3:Web3):
@@ -285,7 +291,7 @@ def test_get_skipped_blocks(self, w3: Web3):
         for block_hash in blocks:
             assert isinstance(block_hash, bytes)
             
-    def test_get_blocks_by_hash_with_pivot_assumptions(self, w3: Web3, use_remote):
+    def test_get_blocks_by_hash_with_pivot_assumptions(self, w3: Web3, use_testnet):
         epoch_number = w3.cfx.epoch_number_by_tag("latest_confirmed")
         blocks = w3.cfx.get_blocks_by_epoch(epoch_number)
         block_data = w3.cfx.get_block_by_hash_with_pivot_assumptions(
@@ -293,17 +299,17 @@ def test_get_blocks_by_hash_with_pivot_assumptions(self, w3: Web3, use_remote):
             blocks[-1],
             epoch_number,
         )
-        block_data = preprocess_block_data(block_data, use_remote)
+        block_data = preprocess_block_data(block_data, use_testnet)
         TypeValidator.validate_typed_dict(block_data, "BlockData")
 
-    def test_get_block(self, w3: Web3, block_data: BlockData, use_remote):
+    def test_get_block(self, w3: Web3, block_data: BlockData, use_testnet):
         epoch_number = block_data["epochNumber"]
         assert epoch_number is not None
         hash = block_data["hash"]
         str_hash = hash.hex()
         for block_identifier in [epoch_number, str_hash, hash, "latest_state"]:
             block = w3.cfx.get_block(block_identifier)
-            block = preprocess_block_data(block, use_remote)
+            block = preprocess_block_data(block, use_testnet)
             TypeValidator.validate_typed_dict(block, "BlockData")
 
 class TestPending:
diff --git a/tests/test_node.py b/tests/test_node.py
index 8a13d6c..806263c 100644
--- a/tests/test_node.py
+++ b/tests/test_node.py
@@ -4,9 +4,7 @@
 from conflux_web3 import Web3
 
 
-def test_local_node(node):
+def test_local_node(node, use_testnet):
     secrets = node.secrets
-    assert (len(secrets) > 0)
-
-
-    
\ No newline at end of file
+    if not use_testnet:
+        assert (len(secrets) > 0)
diff --git a/tests/transaction/test_base_tx.py b/tests/transaction/test_base_tx.py
index 186445a..0262da1 100644
--- a/tests/transaction/test_base_tx.py
+++ b/tests/transaction/test_base_tx.py
@@ -39,7 +39,7 @@ def test_basetx_estimate(w3: Web3, address: Base32Address):
     }
     
     estimate = w3.cfx.estimate_gas_and_collateral(tx, w3.cfx.epoch_number-5)
-    assert estimate.gasUsed == 21000
+    assert estimate['gasUsed'] == 21000
     assert estimate["storageCollateralized"] == 0
     TypeValidator.validate_typed_dict(estimate, "EstimateResult")
     
diff --git a/tests/transaction/test_contract.py b/tests/transaction/test_contract.py
index b197773..ec073a9 100644
--- a/tests/transaction/test_contract.py
+++ b/tests/transaction/test_contract.py
@@ -1,10 +1,21 @@
+from typing import TYPE_CHECKING
 import pytest
 from conflux_web3 import Web3
-from conflux_web3.contract import ConfluxContract
+from conflux_web3.contract import (
+    ConfluxContract,
+)
+from conflux_web3.contract.metadata import (
+    get_contract_metadata
+)
+from cfx_utils.exceptions import Base32AddressNotMatch
 from conflux_web3.middleware.wallet import Wallet
+from cfx_account import LocalAccount
 from tests._test_helpers.ENV_SETTING import erc20_metadata
 from tests._test_helpers.type_check import TypeValidator
 
+if TYPE_CHECKING:
+    from conflux_web3 import Web3
+
 class TestERC20Contract:
     contract: ConfluxContract
     
@@ -46,14 +57,14 @@ def test_contract_deploy_and_transfer(self, w3_: Web3):
             
         # test contract event
         processed_log = contract.events.Transfer.process_receipt(transfer_receipt)[0]
-        assert processed_log["args"]["from"] == w3_.cfx.default_account
-        assert processed_log["args"]["to"] == random_account.address
-        assert processed_log["args"]["value"] == 100
-        assert processed_log["blockHash"] == logs[0]["blockHash"]
-        assert processed_log["epochNumber"] == logs[0]["epochNumber"]
-        assert processed_log["transactionHash"] == logs[0]["transactionHash"]
-        assert processed_log["transactionLogIndex"] == logs[0]["transactionLogIndex"]
-        assert processed_log["transactionIndex"] == logs[0]["transactionIndex"]
+        assert processed_log["args"]["from"] == w3_.cfx.default_account, processed_log
+        assert processed_log["args"]["to"] == random_account.address, processed_log
+        assert processed_log["args"]["value"] == 100, processed_log
+        assert processed_log["blockHash"] == logs[0]["blockHash"], processed_log
+        assert processed_log["epochNumber"] == logs[0]["epochNumber"], processed_log
+        assert processed_log["transactionHash"] == logs[0]["transactionHash"], processed_log
+        assert processed_log["transactionLogIndex"] == logs[0]["transactionLogIndex"], processed_log
+        assert processed_log["transactionIndex"] == logs[0]["transactionIndex"], processed_log
 
         # test event filters
         filter_topics = contract.events.Transfer.get_filter_topics(
@@ -73,3 +84,83 @@ def test_contract_deploy_and_transfer(self, w3_: Web3):
             fromEpoch=fromEpoch
         )
         assert new_processed_logs[0]["args"] == processed_log["args"]
+
+    def test_contract_without_wallet(self, w3: Web3, account: LocalAccount):
+        erc20 = w3.cfx.contract(bytecode=erc20_metadata["bytecode"], abi=erc20_metadata["abi"])
+        # test raw
+        prebuilt_tx_params = erc20.constructor(name="ERC20", symbol="C", initialSupply=10**18).build_transaction({
+            'from': account.address,
+            # 'nonce': w3.cfx.get_next_nonce(account.address),
+            # 'value': 0,
+            # 'gas': 21000,
+            # 'gasPrice': 10**9,
+            # 'chainId': w3.cfx.chain_id,
+            # 'epochHeight': w3.cfx.epoch_number
+        })
+        
+        raw_constuct_tx = account.sign_transaction(prebuilt_tx_params).rawTransaction
+        contract_address = w3.cfx.send_raw_transaction(raw_constuct_tx).executed()["contractCreated"]
+        assert contract_address
+        
+        contract_instance = w3.cfx.contract(address=contract_address, abi=erc20_metadata["abi"])
+        prebuilt_transfer = contract_instance.functions.transfer(
+            w3.account.create().address,
+            100
+        ).build_transaction({
+            'from': account.address
+        })
+        raw_tx = account.sign_transaction(prebuilt_transfer).rawTransaction
+        w3.cfx.send_raw_transaction(raw_tx).executed()
+        
+class TestEmbeddedContractMetadata:
+    def test_get_contract_metadata(self):
+        admin_contract_metadata = get_contract_metadata("AdminControl")
+        assert isinstance(admin_contract_metadata["abi"], list)
+        assert isinstance(admin_contract_metadata, dict)
+
+    def test_contract_from_metadata(self, w3: Web3, use_testnet: bool):
+        admin_contract = w3.cfx.contract(**get_contract_metadata("AdminControl"))
+        assert admin_contract.abi
+        assert w3.cfx.address.is_valid_base32(admin_contract.address)
+        
+        if use_testnet:
+            
+            usdt_contract = w3.cfx.contract(**get_contract_metadata("cUSDT", w3.cfx.chain_id))
+            assert usdt_contract.abi
+            assert usdt_contract.bytecode
+            assert w3.cfx.address.is_valid_base32(usdt_contract.address)
+            assert usdt_contract.caller.symbol() == "cUSDT"
+            
+    def test_contract_from_name(self, w3: Web3, use_testnet: bool):
+        admin_contract = w3.cfx.contract(name="AdminControl")
+        assert admin_contract.abi
+        assert w3.cfx.address.is_valid_base32(admin_contract.address)
+        
+        if use_testnet:
+            usdt_contract = w3.cfx.contract(name="cUSDT")
+            assert usdt_contract.abi
+            assert usdt_contract.bytecode
+            assert w3.cfx.address.is_valid_base32(usdt_contract.address)
+            assert usdt_contract.caller.symbol() == "cUSDT"
+            
+        if w3.cfx.chain_id == 1:
+            faucet = w3.cfx.contract(name="Faucet")
+            assert faucet
+    
+    # TODO: test all embedded metadata functionalities
+    def test_faucet_functions(self, w3: Web3):
+        if w3.cfx.chain_id == 1:
+            random_account = w3.account.create()
+            w3.cfx.default_account = random_account
+            faucet = w3.cfx.contract(name="Faucet")
+            faucet.functions.claimCfx().transact().executed()
+            assert w3.cfx.get_balance(w3.cfx.default_account) > 0
+            
+
+def test_contract_initialization(w3: Web3):
+    metadata = get_contract_metadata("AdminControl")
+    chain_id = w3.cfx.chain_id
+    metadata["address"] = w3.cfx.address(metadata["address"], chain_id+1)
+    
+    with pytest.raises(Base32AddressNotMatch):
+        w3.cfx.contract(**metadata)
diff --git a/tests/utils/test_transaction_utils.py b/tests/utils/test_transaction_utils.py
index 6217049..3c0c324 100644
--- a/tests/utils/test_transaction_utils.py
+++ b/tests/utils/test_transaction_utils.py
@@ -1,11 +1,11 @@
 from audioop import add
 import pytest
 from conflux_web3 import Web3
-from conflux_web3._utils.transactions import fill_formal_transaction_defaults
+from conflux_web3._utils.transactions import fill_transaction_defaults
 from tests._test_helpers.type_check import TypeValidator
 
-def test_fill_formal_transaction_defaults(w3: Web3, address):
-    """test inner util fill_formal_transaction_defaults. 
+def test_fill_transaction_defaults(w3: Web3, address):
+    """test inner util fill_transaction_defaults. 
     """
     w3.cfx.default_account = address
     # "from" field is required before using this util
@@ -13,5 +13,5 @@ def test_fill_formal_transaction_defaults(w3: Web3, address):
         "from": address,
         "to": w3.account.create().address
     }
-    filled_tx = fill_formal_transaction_defaults(w3, unfilled_tx)
+    filled_tx = fill_transaction_defaults(w3, unfilled_tx)
     TypeValidator.validate_typed_dict(filled_tx, "TxDict")