diff --git a/.cicd/platforms/ubuntu22.Dockerfile b/.cicd/platforms/ubuntu22.Dockerfile
index bab8c4b..22321c2 100644
--- a/.cicd/platforms/ubuntu22.Dockerfile
+++ b/.cicd/platforms/ubuntu22.Dockerfile
@@ -9,7 +9,8 @@ RUN apt-get update && apt-get upgrade -y && \
                        git                  \
                        jq                   \
                        wget                 \
-                       xxd
+                       xxd                  \
+                       libcurl4-gnutls-dev  
 
 #Install Node.js
 #https://github.com/nodesource/distributions/blob/master/README.md#installation-instructions
diff --git a/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp b/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp
index 776e506..4bf0f5c 100644
--- a/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp
+++ b/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp
@@ -55,6 +55,8 @@ class [[eosio::contract]] erc20 : public contract {
     [[eosio::action]] void setingressfee(eosio::name token_contract, eosio::asset ingress_fee);
     [[eosio::action]] void withdrawfee(eosio::name token_contract, eosio::asset quantity, eosio::name to, std::string memo);
 
+    [[eosio::action]] void unregtoken(eosio::name eos_contract_name, eosio::symbol_code token_symbol_code);
+    
     struct [[eosio::table("implcontract")]] impl_contract_t {
         uint64_t id = 0;
         bytes address;
diff --git a/antelope_contracts/contracts/erc20/src/erc20.cpp b/antelope_contracts/contracts/erc20/src/erc20.cpp
index 897cf61..6cc1eaa 100644
--- a/antelope_contracts/contracts/erc20/src/erc20.cpp
+++ b/antelope_contracts/contracts/erc20/src/erc20.cpp
@@ -424,6 +424,17 @@ void erc20::setegressfee(eosio::name token_contract, eosio::symbol_code token_sy
     call_act.send(receiver_account(), token_table_iter->address, value_zero, call_data, evm_gaslimit);
 }
 
+void erc20::unregtoken(eosio::name token_contract, eosio::symbol_code token_symbol_code) {
+    require_auth(get_self());
+
+    token_table_t token_table(_self, _self.value);
+    auto index_symbol = token_table.get_index<"by.symbol"_n>();
+    auto token_table_iter = index_symbol.find(token_symbol_key(token_contract, token_symbol_code));
+    eosio::check(token_table_iter != index_symbol.end(), "token not registered");
+
+    index_symbol.erase(token_table_iter);
+}
+
 inline eosio::name erc20::receiver_account()const {
     return get_self();
 }
diff --git a/antelope_contracts/tests/erc20/erc20_tester.cpp b/antelope_contracts/tests/erc20/erc20_tester.cpp
index c72a1f8..dcbd7a7 100644
--- a/antelope_contracts/tests/erc20/erc20_tester.cpp
+++ b/antelope_contracts/tests/erc20/erc20_tester.cpp
@@ -270,7 +270,7 @@ void erc20_tester::open(name owner) { push_action(evm_account, "open"_n, owner,
 
 transaction_trace_ptr erc20_tester::exec(const exec_input& input, const std::optional<exec_callback>& callback) {
     auto binary_data = fc::raw::pack<exec_input, std::optional<exec_callback>>(input, callback);
-    return erc20_tester::push_action(evm_account, "exec"_n, evm_account, bytes{binary_data.begin(), binary_data.end()});
+    return erc20_tester::push_action(evm_account, "exec"_n, evm_account, bytes{binary_data.begin(), binary_data.end()}, DEFAULT_EXPIRATION_DELTA + (exec_count++) % 3500);
 }
 
 eosio::chain::action erc20_tester::get_action(account_name code, action_name acttype, std::vector<permission_level> auths,
diff --git a/antelope_contracts/tests/erc20/erc20_tester.hpp b/antelope_contracts/tests/erc20/erc20_tester.hpp
index fb9ee9b..5905ac6 100644
--- a/antelope_contracts/tests/erc20/erc20_tester.hpp
+++ b/antelope_contracts/tests/erc20/erc20_tester.hpp
@@ -63,11 +63,11 @@ struct exec_output {
 
 struct token_t {
         uint64_t id = 0;
-        eosio::chain::name token_contract;
+        eosio::chain::name token_contract{};
         bytes address;  // <-- proxy contract addr
-        eosio::chain::asset ingress_fee;
-        eosio::chain::asset balance;  // <-- total amount in EVM side
-        eosio::chain::asset fee_balance;
+        eosio::chain::asset ingress_fee{};
+        eosio::chain::asset balance{};  // <-- total amount in EVM side
+        eosio::chain::asset fee_balance{};
         uint8_t erc20_precision = 0;
 
     };
@@ -135,6 +135,8 @@ class erc20_tester : public eosio::testing::base_tester {
     const eosio::chain::symbol native_symbol;
     explicit erc20_tester(bool use_real_evm = false, std::string native_symbol_str = "4,EOS");
 
+    unsigned int exec_count = 0; // ensure uniqueness in exec
+
     eosio::chain::asset make_asset(int64_t amount) const { return eosio::chain::asset(amount, native_symbol); }
     eosio::chain::asset make_asset(int64_t amount, const eosio::chain::symbol& target_symbol) const { return eosio::chain::asset(amount, target_symbol); }
     eosio::chain::transaction_trace_ptr transfer_token(eosio::chain::name token_account_name, eosio::chain::name from, eosio::chain::name to, eosio::chain::asset quantity, std::string memo = "");
@@ -187,7 +189,6 @@ class erc20_tester : public eosio::testing::base_tester {
              const std::optional<asset> ingress_bridge_fee = std::nullopt,
              const bool also_prepare_self_balance = true);
 
-   
 };
 
 
diff --git a/antelope_contracts/tests/erc20/integrated_tests.cpp b/antelope_contracts/tests/erc20/integrated_tests.cpp
index f2fc06d..5cafe01 100644
--- a/antelope_contracts/tests/erc20/integrated_tests.cpp
+++ b/antelope_contracts/tests/erc20/integrated_tests.cpp
@@ -195,9 +195,17 @@ struct it_tester : erc20_tester {
         if (!memo.empty()) {
             txn.data += evmc::from_hex(data_str32(str_to_hex(memo))).value();  // memo
         }
+
+        auto old_nonce = from.next_nonce;
         from.sign(txn);
-        auto r = pushtx(txn);
-        // dlog("action trace: ${a}", ("a", r));
+
+        try {
+            auto r = pushtx(txn);
+            // dlog("action trace: ${a}", ("a", r));
+        } catch (...) {
+            from.next_nonce = old_nonce;
+            throw;
+        }
     }
 
     void transferERC20(evm_eoa& from, evmc::address& to, intx::uint256 amount) {
@@ -209,9 +217,16 @@ struct it_tester : erc20_tester {
         txn.data += evmc::from_hex(address_str32(to)).value();      // param1 (to: address)
         txn.data += evmc::from_hex(uint256_str32(amount)).value();  // param2 (amount: uint256)
 
+        auto old_nonce = from.next_nonce;
         from.sign(txn);
-        auto r = pushtx(txn);
-        // dlog("action trace: ${a}", ("a", r));
+
+        try {
+            auto r = pushtx(txn);
+            // dlog("action trace: ${a}", ("a", r));
+        } catch (...) {
+            from.next_nonce = old_nonce;
+            throw;
+        }
     }
 };
 
@@ -258,7 +273,76 @@ try {
 }
 FC_LOG_AND_RETHROW()
 
+BOOST_FIXTURE_TEST_CASE(it_unregtoken, it_tester)
+try {
+    evm_eoa evm1;
+    auto addr_alice = silkworm::make_reserved_address("alice"_n.to_uint64_t());
+
+    // Give evm1 some EOS
+    transfer_token(eos_token_account, "alice"_n, evm_account, make_asset(1000000, eos_token_symbol), evm1.address_0x().c_str());
+    produce_block();
+
+
+    // USDT balance should be zero
+    auto bal = balanceOf(evm1.address_0x().c_str());
+    BOOST_REQUIRE(bal == 0);
+
+    produce_block();
 
+    // alice send 1.0000 USDT to evm1
+    transfer_token(token_account, "alice"_n, erc20_account, make_asset(10000, token_symbol), evm1.address_0x().c_str());
+
+    // evm1 has 0.990000 USDT
+    BOOST_REQUIRE(balanceOf(evm1.address_0x().c_str()) == 990000);
+
+    // alice has 9999.0000 USDT
+    BOOST_REQUIRE(9999'0000 == get_balance("alice"_n, token_account, symbol::from_string("4,USDT")).get_amount());
+
+    // unregtoken
+    push_action(
+        erc20_account, "unregtoken"_n, erc20_account, mvo()("eos_contract_name", token_account)("token_symbol_code", (std::string)(token_symbol.name())));
+
+    // EOS->EVM not allowed after unregtoken
+    BOOST_REQUIRE_EXCEPTION(
+        transfer_token(token_account, "alice"_n, erc20_account, make_asset(20000, token_symbol), evm1.address_0x().c_str()),
+        eosio_assert_message_exception, 
+        eosio_assert_message_is("received unregistered token"));
+
+    // EVM->EOS not allowed after unregtoken
+    auto fee = egressFee();
+    BOOST_REQUIRE_EXCEPTION(
+        bridgeTransferERC20(evm1, addr_alice, 10000, "aaa", fee),
+        eosio_assert_message_exception, 
+        eosio_assert_message_is("ERC-20 token not registerred"));
+
+    // register token again (imply a different ERC-EVM address)
+    push_action(erc20_account, "regtoken"_n, erc20_account, mvo()("eos_contract_name",token_account.to_string())("evm_token_name","EVM USDT V2")("evm_token_symbol","WUSDT")("ingress_fee","0.0100 USDT")("egress_fee","0.0100 EOS")("erc20_precision",6));
+
+    // EOS->EVM: alice transfer 2 USDT to evm1 in EVM (new ERC-EVM address)
+    transfer_token(token_account, "alice"_n, erc20_account, make_asset(20000, token_symbol), evm1.address_0x().c_str());
+
+    // alice has 9997.0000 USDT
+    BOOST_REQUIRE(9997'0000 == get_balance("alice"_n, token_account, symbol::from_string("4,USDT")).get_amount());
+
+    // evm1 has 0.990000 USDT under the original ERC-20 address
+    BOOST_REQUIRE(balanceOf(evm1.address_0x().c_str()) == 990000);
+
+    // refresh evm token address
+    evm_address = getSolidityContractAddress();
+
+    // evm1 has 1.990000 USDT under the new ERC-20 address
+    BOOST_REQUIRE(balanceOf(evm1.address_0x().c_str()) == 1990000);
+
+    // EVM->EOS: evm1 tranfer 0.010000 USDT to alice
+    bridgeTransferERC20(evm1, addr_alice, 10000, "aaa", fee);
+
+    // evm1 has 1.980000 USDT under the new ERC-20 address
+    BOOST_REQUIRE(balanceOf(evm1.address_0x().c_str()) == 1980000);    
+
+    // alice has 9997.0000 USDT
+    BOOST_REQUIRE(9997'0100 == get_balance("alice"_n, token_account, symbol::from_string("4,USDT")).get_amount());
+}
+FC_LOG_AND_RETHROW()
 
 BOOST_FIXTURE_TEST_CASE(it_eos_to_evm, it_tester)
 try {
@@ -351,14 +435,12 @@ try {
                 eosio_assert_message_exception, eosio_assert_message_is("bridge amount must be positive"));
     BOOST_REQUIRE(999999000 == balanceOf(evm1.address_0x().c_str()));
     BOOST_REQUIRE(89999910 == get_balance("alice"_n, token_account, symbol::from_string("4,USDT")).get_amount()); // 
-    evm1.next_nonce --;
     produce_block();
 
     BOOST_REQUIRE_EXCEPTION(bridgeTransferERC20(evm1, addr_alice, 1, "aaa", fee), 
                 eosio_assert_message_exception, eosio_assert_message_is("bridge amount can not have dust"));
     BOOST_REQUIRE(999999000 == balanceOf(evm1.address_0x().c_str()));
     BOOST_REQUIRE(89999910 == get_balance("alice"_n, token_account, symbol::from_string("4,USDT")).get_amount()); // 
-    evm1.next_nonce --;
     produce_block();
 
     bridgeTransferERC20(evm1, addr_alice, 100, "aaa", fee);