diff --git a/.gitignore b/.gitignore
index 25e0aa973..810ed2241 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,9 @@ vendor
 *.swp
 .vscode/*.json
 
+# Test generate tmp files
+app/data
+app/apptest/data
+app_test/data
+plugins/param/data
+plugins/tokens/data
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b76b3d67d..1be5fbd32 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
 # Changelog
 
+## 0.7
+FEATURES
+* [\#725](https://github.com/binance-chain/node/pull/725) [Token] [Dex] BEP8 - Mini-BEP2 token features
+* [\#710](https://github.com/binance-chain/node/pull/710) [DEX] BEP70 - Support busd pair listing and trading
+
+IMPROVEMENTS
+* [\#704](https://github.com/binance-chain/node/pull/704) [DEX] BEP67 Price-based Order Expiration
+* [\#714](https://github.com/binance-chain/node/pull/714) [DEX] Add pendingMatch flag to orderbook query response
+
 ## 0.6.3-hf.1
 
 BUG FIXES
diff --git a/admin/tx.go b/admin/tx.go
index 49633a4de..889554563 100644
--- a/admin/tx.go
+++ b/admin/tx.go
@@ -25,6 +25,8 @@ var transferOnlyModeBlackList = []string{
 	timelock.TimeLockMsg{}.Type(),
 	timelock.TimeUnlockMsg{}.Type(),
 	timelock.TimeRelockMsg{}.Type(),
+	issue.IssueMiniMsg{}.Type(),
+	issue.IssueTinyMsg{}.Type(),
 }
 
 var TxBlackList = map[runtime.Mode][]string{
diff --git a/app/app.go b/app/app.go
index d30578dcf..b47136951 100644
--- a/app/app.go
+++ b/app/app.go
@@ -43,7 +43,8 @@ import (
 	"github.com/binance-chain/node/plugins/param"
 	"github.com/binance-chain/node/plugins/param/paramhub"
 	"github.com/binance-chain/node/plugins/tokens"
-	tkstore "github.com/binance-chain/node/plugins/tokens/store"
+	"github.com/binance-chain/node/plugins/tokens/issue"
+	"github.com/binance-chain/node/plugins/tokens/seturi"
 	"github.com/binance-chain/node/plugins/tokens/swap"
 	"github.com/binance-chain/node/plugins/tokens/timelock"
 	"github.com/binance-chain/node/wire"
@@ -80,7 +81,7 @@ type BinanceChain struct {
 	CoinKeeper     bank.Keeper
 	DexKeeper      *dex.DexKeeper
 	AccountKeeper  auth.AccountKeeper
-	TokenMapper    tkstore.Mapper
+	TokenMapper    tokens.Mapper
 	ValAddrCache   *ValAddrCache
 	stakeKeeper    stake.Keeper
 	govKeeper      gov.Keeper
@@ -95,6 +96,8 @@ type BinanceChain struct {
 	publicationConfig  *config.PublicationConfig
 	publisher          pub.MarketDataPublisher
 
+	dexConfig *config.DexConfig
+
 	// Unlike tendermint, we don't need implement a no-op metrics, usage of this field should
 	// check nil-ness to know whether metrics collection is turn on
 	// TODO(#246): make it an aggregated wrapper of all component metrics (i.e. DexKeeper, StakeKeeper)
@@ -119,6 +122,7 @@ func NewBinanceChain(logger log.Logger, db dbm.DB, traceStore io.Writer, baseApp
 		upgradeConfig:      ServerContext.UpgradeConfig,
 		abciQueryBlackList: getABCIQueryBlackList(ServerContext.QueryConfig),
 		publicationConfig:  ServerContext.PublicationConfig,
+		dexConfig:          ServerContext.DexConfig,
 	}
 	// set upgrade config
 	SetUpgradeConfig(app.upgradeConfig)
@@ -127,7 +131,7 @@ func NewBinanceChain(logger log.Logger, db dbm.DB, traceStore io.Writer, baseApp
 
 	// mappers
 	app.AccountKeeper = auth.NewAccountKeeper(cdc, common.AccountStoreKey, types.ProtoAppAccount)
-	app.TokenMapper = tkstore.NewMapper(cdc, common.TokenStoreKey)
+	app.TokenMapper = tokens.NewMapper(cdc, common.TokenStoreKey)
 	app.CoinKeeper = bank.NewBaseKeeper(app.AccountKeeper)
 	app.ParamHub = paramhub.NewKeeper(cdc, common.ParamsStoreKey, common.TParamsStoreKey)
 	tradingPairMapper := dex.NewTradingPairMapper(app.Codec, common.PairStoreKey)
@@ -264,6 +268,10 @@ func SetUpgradeConfig(upgradeConfig *config.UpgradeConfig) {
 	upgrade.Mgr.AddUpgradeHeight(upgrade.ListingRuleUpgrade, upgradeConfig.ListingRuleUpgradeHeight)
 	upgrade.Mgr.AddUpgradeHeight(upgrade.FixZeroBalance, upgradeConfig.FixZeroBalanceHeight)
 
+	upgrade.Mgr.AddUpgradeHeight(upgrade.BEP8, upgradeConfig.BEP8Height)
+	upgrade.Mgr.AddUpgradeHeight(upgrade.BEP67, upgradeConfig.BEP67Height)
+	upgrade.Mgr.AddUpgradeHeight(upgrade.BEP70, upgradeConfig.BEP70Height)
+
 	// register store keys of upgrade
 	upgrade.Mgr.RegisterStoreKeys(upgrade.BEP9, common.TimeLockStoreKey.Name())
 	upgrade.Mgr.RegisterStoreKeys(upgrade.BEP3, common.AtomicSwapStoreKey.Name())
@@ -283,6 +291,13 @@ func SetUpgradeConfig(upgradeConfig *config.UpgradeConfig) {
 		swap.ClaimHTLTMsg{}.Type(),
 		swap.RefundHTLTMsg{}.Type(),
 	)
+	// register msg types of upgrade
+	upgrade.Mgr.RegisterMsgTypes(upgrade.BEP8,
+		issue.IssueMiniMsg{}.Type(),
+		issue.IssueTinyMsg{}.Type(),
+		seturi.SetURIMsg{}.Type(),
+		list.ListMiniMsg{}.Type(),
+	)
 }
 
 func getABCIQueryBlackList(queryConfig *config.QueryConfig) map[string]bool {
@@ -301,10 +316,10 @@ func (app *BinanceChain) initRunningMode() {
 }
 
 func (app *BinanceChain) initDex(pairMapper dex.TradingPairMapper) {
-	app.DexKeeper = dex.NewOrderKeeper(common.DexStoreKey, app.AccountKeeper, pairMapper,
-		app.RegisterCodespace(dex.DefaultCodespace), app.baseConfig.OrderKeeperConcurrency, app.Codec,
-		app.publicationConfig.ShouldPublishAny())
+
+	app.DexKeeper = dex.NewDexKeeper(common.DexStoreKey, app.AccountKeeper, pairMapper, app.RegisterCodespace(dex.DefaultCodespace), app.baseConfig.OrderKeeperConcurrency, app.Codec, app.publicationConfig.ShouldPublishAny())
 	app.DexKeeper.SubscribeParamChange(app.ParamHub)
+	app.DexKeeper.SetBUSDSymbol(app.dexConfig.BUSDSymbol)
 
 	// do not proceed if we are in a unit test and `CheckState` is unset.
 	if app.CheckState == nil {
@@ -325,11 +340,12 @@ func (app *BinanceChain) initDex(pairMapper dex.TradingPairMapper) {
 		stateDB,
 		app.LastBlockHeight(),
 		app.TxDecoder)
+
 }
 
 func (app *BinanceChain) initPlugins() {
 	tokens.InitPlugin(app, app.TokenMapper, app.AccountKeeper, app.CoinKeeper, app.timeLockKeeper, app.swapKeeper)
-	dex.InitPlugin(app, app.DexKeeper, app.TokenMapper, app.AccountKeeper, app.govKeeper)
+	dex.InitPlugin(app, app.DexKeeper, app.TokenMapper, app.govKeeper)
 	param.InitPlugin(app, app.ParamHub)
 	account.InitPlugin(app, app.AccountKeeper)
 }
@@ -520,12 +536,11 @@ func (app *BinanceChain) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) a
 
 	isBreatheBlock := app.isBreatheBlock(height, lastBlockTime, blockTime)
 	var tradesToPublish []*pub.Trade
-
 	if sdk.IsUpgrade(upgrade.BEP19) || !isBreatheBlock {
 		if app.publicationConfig.ShouldPublishAny() && pub.IsLive {
-			tradesToPublish = pub.MatchAndAllocateAllForPublish(app.DexKeeper, ctx)
+			tradesToPublish = pub.MatchAndAllocateAllForPublish(app.DexKeeper, ctx, isBreatheBlock)
 		} else {
-			app.DexKeeper.MatchAndAllocateAll(ctx, nil)
+			app.DexKeeper.MatchAndAllocateSymbols(ctx, nil, isBreatheBlock)
 		}
 	}
 
@@ -545,6 +560,7 @@ func (app *BinanceChain) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) a
 	}
 
 	app.DexKeeper.StoreTradePrices(ctx)
+
 	blockFee := distributeFee(ctx, app.AccountKeeper, app.ValAddrCache, app.publicationConfig.PublishBlockFee)
 
 	tags, passed, failed := gov.EndBlocker(ctx, app.govKeeper)
@@ -763,10 +779,13 @@ func (app *BinanceChain) publish(tradesToPublish []*pub.Trade, proposalsToPublis
 	var blockToPublish *pub.Block
 	var latestPriceLevels order.ChangedPriceLevelsMap
 
+	orderChanges := app.DexKeeper.GetAllOrderChanges()
+	orderInfoForPublish := app.DexKeeper.GetAllOrderInfosForPub()
+
 	duration := pub.Timer(app.Logger, fmt.Sprintf("collect publish information, height=%d", height), func() {
 		if app.publicationConfig.PublishAccountBalance {
 			txRelatedAccounts := app.Pool.TxRelatedAddrs()
-			tradeRelatedAccounts := pub.GetTradeAndOrdersRelatedAccounts(app.DexKeeper, tradesToPublish)
+			tradeRelatedAccounts := pub.GetTradeAndOrdersRelatedAccounts(tradesToPublish, orderChanges, orderInfoForPublish)
 			accountsToPublish = pub.GetAccountBalances(
 				app.AccountKeeper,
 				ctx,
@@ -795,33 +814,34 @@ func (app *BinanceChain) publish(tradesToPublish []*pub.Trade, proposalsToPublis
 	pub.Logger.Info("start to publish", "height", height,
 		"blockTime", blockTime, "numOfTrades", len(tradesToPublish),
 		"numOfOrders", // the order num we collected here doesn't include trade related orders
-		len(app.DexKeeper.OrderChanges),
+		len(orderChanges),
 		"numOfProposals",
 		proposalsToPublish.NumOfMsgs,
 		"numOfStakeUpdates",
 		stakeUpdates.NumOfMsgs,
 		"numOfAccounts",
 		len(accountsToPublish))
-	pub.ToRemoveOrderIdCh = make(chan string, pub.ToRemoveOrderIdChannelSize)
+	pub.ToRemoveOrderIdCh = make(chan pub.OrderSymbolId, pub.ToRemoveOrderIdChannelSize)
+
 	pub.ToPublishCh <- pub.NewBlockInfoToPublish(
 		height,
 		blockTime,
 		tradesToPublish,
 		proposalsToPublish,
 		stakeUpdates,
-		app.DexKeeper.OrderChanges,     // thread-safety is guarded by the signal from RemoveDoneCh
-		app.DexKeeper.OrderInfosForPub, // thread-safety is guarded by the signal from RemoveDoneCh
+		orderChanges,        // thread-safety is guarded by the signal from RemoveDoneCh
+		orderInfoForPublish, // thread-safety is guarded by the signal from RemoveDoneCh
 		accountsToPublish,
 		latestPriceLevels,
 		blockFee,
-		app.DexKeeper.RoundOrderFees,
+		app.DexKeeper.RoundOrderFees, //only use DexKeeper RoundOrderFees
 		transferToPublish,
 		blockToPublish)
 
 	// remove item from OrderInfoForPublish when we published removed order (cancel, iocnofill, fullyfilled, expired)
-	for id := range pub.ToRemoveOrderIdCh {
-		pub.Logger.Debug("delete order from order changes map", "orderId", id)
-		delete(app.DexKeeper.OrderInfosForPub, id)
+	for o := range pub.ToRemoveOrderIdCh {
+		pub.Logger.Debug("delete order from order changes map", "symbol", o.Symbol, "orderId", o.Id)
+		app.DexKeeper.RemoveOrderInfosForPub(o.Symbol, o.Id)
 	}
 
 	pub.Logger.Debug("finish publish", "height", height)
diff --git a/app/app_pub_test.go b/app/app_pub_test.go
index 8c36807d3..f4853ff70 100644
--- a/app/app_pub_test.go
+++ b/app/app_pub_test.go
@@ -98,7 +98,7 @@ func setupAppTest(t *testing.T) (*assert.Assertions, *require.Assertions, *Binan
 	pub.IsLive = true
 
 	keeper := app.DexKeeper
-	keeper.CollectOrderInfoForPublish = true
+	keeper.EnablePublish()
 	tradingPair := dextypes.NewTradingPair("XYZ-000", "BNB", 102000)
 	keeper.PairMapper.AddTradingPair(app.DeliverState.Ctx, tradingPair)
 	keeper.AddEngine(tradingPair)
@@ -112,8 +112,8 @@ func setupAppTest(t *testing.T) (*assert.Assertions, *require.Assertions, *Binan
 	keeper.FeeManager.FeeConfig.CancelFee = 12
 	keeper.FeeManager.FeeConfig.CancelFeeNative = 6
 
-	_, buyerAcc := testutils.NewAccountForPub(ctx, am, 100000000000, 0, 0) // give user enough coins to pay the fee
-	_, sellerAcc := testutils.NewAccountForPub(ctx, am, 100000000000, 0, 0)
+	_, buyerAcc := testutils.NewAccountForPub(ctx, am, 100000000000, 0, 0, "XYZ-000") // give user enough coins to pay the fee
+	_, sellerAcc := testutils.NewAccountForPub(ctx, am, 100000000000, 0, 0, "XYZ-000")
 	return assert.New(t), require.New(t), app, buyerAcc, sellerAcc
 }
 
@@ -140,7 +140,7 @@ func TestAppPub_MatchOrder(t *testing.T) {
 
 	ctx := app.DeliverState.Ctx
 	msg := orderPkg.NewNewOrderMsg(buyerAcc.GetAddress(), orderPkg.GenerateOrderID(1, buyerAcc.GetAddress()), orderPkg.Side.BUY, "XYZ-000_BNB", 102000, 300000000)
-	handler := orderPkg.NewHandler(app.GetCodec(), app.DexKeeper, app.AccountKeeper)
+	handler := orderPkg.NewHandler(app.DexKeeper)
 	app.DeliverState.Ctx = app.DeliverState.Ctx.WithBlockHeight(41).WithBlockTime(time.Unix(0, 100))
 	buyerAcc.SetSequence(1)
 	app.AccountKeeper.SetAccount(ctx, buyerAcc)
@@ -214,7 +214,7 @@ func TestAppPub_MatchOrder(t *testing.T) {
 
 func TestAppPub_MatchAndCancelFee(t *testing.T) {
 	assert, require, app, buyerAcc, sellerAcc := setupAppTest(t)
-	handler := orderPkg.NewHandler(app.GetCodec(), app.DexKeeper, app.AccountKeeper)
+	handler := orderPkg.NewHandler(app.DexKeeper)
 	ctx := app.DeliverState.Ctx
 
 	// ==== Place a to-be-matched sell order and a to-be-cancelled buy order (in different symbol)
diff --git a/app/apptest/match_allocation_test.go b/app/apptest/match_allocation_test.go
index 2901f2579..5c9dbd936 100644
--- a/app/apptest/match_allocation_test.go
+++ b/app/apptest/match_allocation_test.go
@@ -127,6 +127,8 @@ func SetupTest(initPrices ...int64) (crypto.Address, sdk.Context, []sdk.Account)
 func SetupTest_new(initPrices ...int64) (crypto.Address, sdk.Context, []sdk.Account) {
 	// for new match engine
 	upgrade.Mgr.AddUpgradeHeight(upgrade.BEP19, -1)
+	upgrade.Mgr.AddUpgradeHeight(upgrade.BEP8, -1)
+	upgrade.Mgr.AddUpgradeHeight(upgrade.BEP70, -1)
 	addr := secp256k1.GenPrivKey().PubKey().Address()
 	accAddr := sdk.AccAddress(addr)
 	baseAcc := auth.BaseAccount{Address: accAddr}
@@ -230,7 +232,7 @@ func GetOrderId(add sdk.AccAddress, seq int64, ctx sdk.Context) string {
 func GetOrderBook(pair string) ([]level, []level) {
 	buys := make([]level, 0)
 	sells := make([]level, 0)
-	orderbooks := testApp.DexKeeper.GetOrderBookLevels(pair, 25)
+	orderbooks, _ := testApp.DexKeeper.GetOrderBookLevels(pair, 25)
 	for _, l := range orderbooks {
 		if l.BuyPrice != 0 {
 			buys = append(buys, level{price: l.BuyPrice, qty: l.BuyQty})
diff --git a/app/apptest/ordertx_test.go b/app/apptest/ordertx_test.go
index 50314a450..c84b36752 100644
--- a/app/apptest/ordertx_test.go
+++ b/app/apptest/ordertx_test.go
@@ -22,10 +22,10 @@ type level struct {
 	qty   utils.Fixed8
 }
 
-func getOrderBook(pair string) ([]level, []level) {
+func getOrderBook(pair string) ([]level, []level, bool) {
 	buys := make([]level, 0)
 	sells := make([]level, 0)
-	orderbooks := testApp.DexKeeper.GetOrderBookLevels(pair, 5)
+	orderbooks, pendingMatch := testApp.DexKeeper.GetOrderBookLevels(pair, 5)
 	for _, l := range orderbooks {
 		if l.BuyPrice != 0 {
 			buys = append(buys, level{price: l.BuyPrice, qty: l.BuyQty})
@@ -34,7 +34,7 @@ func getOrderBook(pair string) ([]level, []level) {
 			sells = append(sells, level{price: l.SellPrice, qty: l.SellQty})
 		}
 	}
-	return buys, sells
+	return buys, sells, pendingMatch
 }
 
 func genOrderID(add sdk.AccAddress, seq int64, ctx sdk.Context, am auth.AccountKeeper) string {
@@ -124,6 +124,12 @@ func Test_handleNewOrder_DeliverTx(t *testing.T) {
 	tradingPair := types.NewTradingPair("BTC-000", "BNB", 1e8)
 	testApp.DexKeeper.PairMapper.AddTradingPair(ctx, tradingPair)
 	testApp.DexKeeper.AddEngine(tradingPair)
+	testApp.DexKeeper.GetEngines()["BTC-000_BNB"].LastMatchHeight = -1
+
+	tradingPair2 := types.NewTradingPair("ETH-001", "BNB", 1e8)
+	testApp.DexKeeper.PairMapper.AddTradingPair(ctx, tradingPair2)
+	testApp.DexKeeper.AddEngine(tradingPair2)
+	testApp.DexKeeper.GetEngines()["ETH-001_BNB"].LastMatchHeight = -1
 
 	add := Account(0).GetAddress()
 	oid := fmt.Sprintf("%X-0", add)
@@ -133,11 +139,17 @@ func Test_handleNewOrder_DeliverTx(t *testing.T) {
 	t.Logf("res is %v and error is %v", res, e)
 	assert.Equal(uint32(0), res.Code)
 	assert.Nil(e)
-	buys, sells := getOrderBook("BTC-000_BNB")
+	buys, sells, pendingMatch := getOrderBook("BTC-000_BNB")
 	assert.Equal(1, len(buys))
 	assert.Equal(0, len(sells))
+	assert.Equal(true, pendingMatch)
 	assert.Equal(utils.Fixed8(355e8), buys[0].price)
 	assert.Equal(utils.Fixed8(1e8), buys[0].qty)
+
+	buys, sells, pendingMatch = getOrderBook("ETH-001_BNB")
+	assert.Equal(0, len(buys))
+	assert.Equal(0, len(sells))
+	assert.Equal(false, pendingMatch)
 }
 
 func Test_Match(t *testing.T) {
@@ -149,9 +161,11 @@ func Test_Match(t *testing.T) {
 	ethPair := types.NewTradingPair("ETH-000", "BNB", 97e8)
 	testApp.DexKeeper.PairMapper.AddTradingPair(ctx, ethPair)
 	testApp.DexKeeper.AddEngine(ethPair)
+	testApp.DexKeeper.GetEngines()["ETH-000_BNB"].LastMatchHeight = -1
 	btcPair := types.NewTradingPair("BTC-000", "BNB", 96e8)
 	testApp.DexKeeper.PairMapper.AddTradingPair(ctx, btcPair)
 	testApp.DexKeeper.AddEngine(btcPair)
+	testApp.DexKeeper.GetEngines()["BTC-000_BNB"].LastMatchHeight = -1
 	testApp.DexKeeper.FeeManager.UpdateConfig(newTestFeeConfig())
 
 	// setup accounts
@@ -196,13 +210,17 @@ func Test_Match(t *testing.T) {
 	t.Logf("res is %v and error is %v", res, e)
 	msg = o.NewNewOrderMsg(add, genOrderID(add, 3, ctx, am), 1, "BTC-000_BNB", 98e8, 300e8)
 	res, e = testClient.DeliverTxSync(msg, testApp.Codec)
-	buys, sells := getOrderBook("BTC-000_BNB")
+	buys, sells, pendingMatch := getOrderBook("BTC-000_BNB")
 	assert.Equal(4, len(buys))
 	assert.Equal(3, len(sells))
-	testApp.DexKeeper.MatchAndAllocateAll(ctx, nil)
-	buys, sells = getOrderBook("BTC-000_BNB")
+
+	assert.Equal(true, pendingMatch)
+	testApp.DexKeeper.MatchAndAllocateSymbols(ctx, nil, false)
+	buys, sells, pendingMatch = getOrderBook("BTC-000_BNB")
+
 	assert.Equal(0, len(buys))
 	assert.Equal(3, len(sells))
+	assert.Equal(false, pendingMatch)
 
 	trades, lastPx := testApp.DexKeeper.GetLastTradesForPair("BTC-000_BNB")
 	assert.Equal(int64(96e8), lastPx)
@@ -247,20 +265,21 @@ func Test_Match(t *testing.T) {
 	res, e = testClient.DeliverTxSync(msg, testApp.Codec)
 	t.Logf("res is %v and error is %v", res, e)
 
-	buys, sells = getOrderBook("BTC-000_BNB")
+	buys, sells, _ = getOrderBook("BTC-000_BNB")
 	assert.Equal(0, len(buys))
 	assert.Equal(3, len(sells))
-	buys, sells = getOrderBook("ETH-000_BNB")
+	buys, sells, _ = getOrderBook("ETH-000_BNB")
 	assert.Equal(4, len(buys))
 	assert.Equal(3, len(sells))
 
-	testApp.DexKeeper.MatchAndAllocateAll(ctx, nil)
-	buys, sells = getOrderBook("ETH-000_BNB")
+	testApp.DexKeeper.MatchAndAllocateSymbols(ctx, nil, false)
+	buys, sells, _ = getOrderBook("ETH-000_BNB")
+
 	t.Logf("buys: %v", buys)
 	t.Logf("sells: %v", sells)
 	assert.Equal(1, len(buys))
 	assert.Equal(2, len(sells))
-	buys, sells = getOrderBook("BTC-000_BNB")
+	buys, sells, _ = getOrderBook("BTC-000_BNB")
 	assert.Equal(0, len(buys))
 	assert.Equal(3, len(sells))
 	trades, lastPx = testApp.DexKeeper.GetLastTradesForPair("ETH-000_BNB")
@@ -301,6 +320,7 @@ func Test_handleCancelOrder_CheckTx(t *testing.T) {
 	tradingPair := types.NewTradingPair("BTC-000", "BNB", 1e8)
 	testApp.DexKeeper.PairMapper.AddTradingPair(ctx, tradingPair)
 	testApp.DexKeeper.AddEngine(tradingPair)
+	testApp.DexKeeper.GetEngines()["BTC-000_BNB"].LastMatchHeight = -1
 	testApp.DexKeeper.FeeManager.UpdateConfig(newTestFeeConfig())
 
 	// setup accounts
diff --git a/app/config/config.go b/app/config/config.go
index 581bae02b..12228fb60 100644
--- a/app/config/config.go
+++ b/app/config/config.go
@@ -53,7 +53,7 @@ BEP6Height = {{ .UpgradeConfig.BEP6Height }}
 BEP9Height = {{ .UpgradeConfig.BEP9Height }}
 # Block height of BEP10 upgrade
 BEP10Height = {{ .UpgradeConfig.BEP10Height }}
-# Block height of BEP19Height upgrade
+# Block height of BEP19 upgrade
 BEP19Height = {{ .UpgradeConfig.BEP19Height }}
 # Block height of BEP12 upgrade
 BEP12Height = {{ .UpgradeConfig.BEP12Height }}
@@ -67,6 +67,12 @@ LotSizeUpgradeHeight = {{ .UpgradeConfig.LotSizeUpgradeHeight }}
 ListingRuleUpgradeHeight = {{ .UpgradeConfig.ListingRuleUpgradeHeight }}
 # Block height of FixZeroBalanceHeight upgrade
 FixZeroBalanceHeight = {{ .UpgradeConfig.FixZeroBalanceHeight }}
+# Block height of BEP8 upgrade
+BEP8Height = {{ .UpgradeConfig.BEP8Height }}
+# Block height of BEP67 upgrade
+BEP67Height = {{ .UpgradeConfig.BEP67Height }}
+# Block height of BEP70 upgrade
+BEP70Height = {{ .UpgradeConfig.BEP70Height }}
 
 [query]
 # ABCI query interface black list, suggested value: ["custom/gov/proposals", "custom/timelock/timelocks", "custom/atomicSwap/swapcreator", "custom/atomicSwap/swaprecipient"]
@@ -152,6 +158,10 @@ logFileRoot = "{{ .LogConfig.LogFileRoot }}"
 logFilePath = "{{ .LogConfig.LogFilePath }}"
 # Number of logs keep in memory before writing to file
 logBuffSize = {{ .LogConfig.LogBuffSize }}
+
+[dex]
+# The suffixed symbol of BUSD
+BUSDSymbol = "{{ .DexConfig.BUSDSymbol }}"
 `
 
 type BinanceChainContext struct {
@@ -178,6 +188,7 @@ type BinanceChainConfig struct {
 	*BaseConfig        `mapstructure:"base"`
 	*UpgradeConfig     `mapstructure:"upgrade"`
 	*QueryConfig       `mapstructure:"query"`
+	*DexConfig         `mapstructure:"dex"`
 }
 
 func DefaultBinanceChainConfig() *BinanceChainConfig {
@@ -188,6 +199,7 @@ func DefaultBinanceChainConfig() *BinanceChainConfig {
 		BaseConfig:        defaultBaseConfig(),
 		UpgradeConfig:     defaultUpgradeConfig(),
 		QueryConfig:       defaultQueryConfig(),
+		DexConfig:         defaultGovConfig(),
 	}
 }
 
@@ -350,6 +362,7 @@ func defaultBaseConfig() *BaseConfig {
 }
 
 type UpgradeConfig struct {
+
 	// Galileo Upgrade
 	BEP6Height  int64 `mapstructure:"BEP6Height"`
 	BEP9Height  int64 `mapstructure:"BEP9Height"`
@@ -359,11 +372,17 @@ type UpgradeConfig struct {
 	BEP12Height int64 `mapstructure:"BEP12Height"`
 	// Archimedes Upgrade
 	BEP3Height int64 `mapstructure:"BEP3Height"`
+
 	// TODO: add upgrade name
 	FixSignBytesOverflowHeight int64 `mapstructure:"FixSignBytesOverflowHeight"`
 	LotSizeUpgradeHeight       int64 `mapstructure:"LotSizeUpgradeHeight"`
 	ListingRuleUpgradeHeight   int64 `mapstructure:"ListingRuleUpgradeHeight"`
 	FixZeroBalanceHeight       int64 `mapstructure:"FixZeroBalanceHeight"`
+
+	// TODO: add upgrade name
+	BEP8Height  int64 `mapstructure:"BEP8Height"`
+	BEP67Height int64 `mapstructure:"BEP67Height"`
+	BEP70Height int64 `mapstructure:"BEP70Height"`
 }
 
 func defaultUpgradeConfig() *UpgradeConfig {
@@ -379,6 +398,9 @@ func defaultUpgradeConfig() *UpgradeConfig {
 		LotSizeUpgradeHeight:       math.MaxInt64,
 		ListingRuleUpgradeHeight:   math.MaxInt64,
 		FixZeroBalanceHeight:       math.MaxInt64,
+		BEP8Height:                 math.MaxInt64,
+		BEP67Height:                math.MaxInt64,
+		BEP70Height:                math.MaxInt64,
 	}
 }
 
@@ -392,6 +414,16 @@ func defaultQueryConfig() *QueryConfig {
 	}
 }
 
+type DexConfig struct {
+	BUSDSymbol string `mapstructure:"BUSDSymbol"`
+}
+
+func defaultGovConfig() *DexConfig {
+	return &DexConfig{
+		BUSDSymbol: "",
+	}
+}
+
 func (context *BinanceChainContext) ParseAppConfigInPlace() error {
 	// this piece of code should be consistent with bindFlagsLoadViper
 	// vendor/github.com/tendermint/tendermint/libs/cli/setup.go:125
diff --git a/app/helpers.go b/app/helpers.go
index ee640bc8e..35b71041a 100644
--- a/app/helpers.go
+++ b/app/helpers.go
@@ -144,16 +144,12 @@ func (app *BinanceChain) processErrAbciResponseForPub(txBytes []byte) {
 			case order.NewOrderMsg:
 				app.Logger.Info("failed to process NewOrderMsg", "oid", msg.Id)
 				// The error on deliver should be rare and only impact witness publisher's performance
-				app.DexKeeper.OrderChangesMtx.Lock()
-				app.DexKeeper.OrderChanges = append(app.DexKeeper.OrderChanges, order.OrderChange{msg.Id, order.FailedBlocking, "", msg})
-				app.DexKeeper.OrderChangesMtx.Unlock()
+				app.DexKeeper.UpdateOrderChangeSync(order.OrderChange{msg.Id, order.FailedBlocking, "", msg}, msg.Symbol)
 			case order.CancelOrderMsg:
 				app.Logger.Info("failed to process CancelOrderMsg", "oid", msg.RefId)
 				// The error on deliver should be rare and only impact witness publisher's performance
-				app.DexKeeper.OrderChangesMtx.Lock()
-				// OrderInfo must has been in keeper.OrderInfosForPub
-				app.DexKeeper.OrderChanges = append(app.DexKeeper.OrderChanges, order.OrderChange{msg.RefId, order.FailedBlocking, "", msg})
-				app.DexKeeper.OrderChangesMtx.Unlock()
+				// OrderInfo must has been in keeper.orderInfosForPub
+				app.DexKeeper.UpdateOrderChangeSync(order.OrderChange{msg.RefId, order.FailedBlocking, "", msg}, msg.Symbol)
 			default:
 				// deliberately do nothing for message other than NewOrderMsg
 				// in future, we may publish fail status of send msg
@@ -195,6 +191,7 @@ func (app *BinanceChain) reInitChain() error {
 		snapshot.Manager().GetStateDB(),
 		app.LastBlockHeight(),
 		app.TxDecoder)
+
 	app.initParams()
 
 	// init app cache
diff --git a/app/pub/helpers.go b/app/pub/helpers.go
index dfd7ec216..c01dc541c 100644
--- a/app/pub/helpers.go
+++ b/app/pub/helpers.go
@@ -8,6 +8,8 @@ import (
 	"sync"
 	"time"
 
+	abci "github.com/tendermint/tendermint/abci/types"
+
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	"github.com/cosmos/cosmos-sdk/x/auth"
 	"github.com/cosmos/cosmos-sdk/x/bank"
@@ -20,27 +22,28 @@ import (
 	"github.com/binance-chain/node/plugins/tokens/burn"
 	"github.com/binance-chain/node/plugins/tokens/freeze"
 	"github.com/binance-chain/node/plugins/tokens/issue"
-	abci "github.com/tendermint/tendermint/abci/types"
+	"github.com/binance-chain/node/plugins/tokens/seturi"
 )
 
-func GetTradeAndOrdersRelatedAccounts(kp *orderPkg.Keeper, tradesToPublish []*Trade) []string {
-	res := make([]string, 0, len(tradesToPublish)*2+len(kp.OrderChanges))
+func GetTradeAndOrdersRelatedAccounts(tradesToPublish []*Trade, orderChanges orderPkg.OrderChanges, orderInfosForPublish orderPkg.OrderInfoForPublish) []string {
+	res := make([]string, 0, len(tradesToPublish)*2+len(orderChanges))
 
 	for _, t := range tradesToPublish {
-		if bo, ok := kp.OrderInfosForPub[t.Bid]; ok {
+
+		if bo, ok := orderInfosForPublish[t.Bid]; ok {
 			res = append(res, string(bo.Sender.Bytes()))
 		} else {
 			Logger.Error("failed to locate buy order in OrderChangesMap for trade account resolving", "bid", t.Bid)
 		}
-		if so, ok := kp.OrderInfosForPub[t.Sid]; ok {
+		if so, ok := orderInfosForPublish[t.Sid]; ok {
 			res = append(res, string(so.Sender.Bytes()))
 		} else {
 			Logger.Error("failed to locate sell order in OrderChangesMap for trade account resolving", "sid", t.Sid)
 		}
 	}
 
-	for _, orderChange := range kp.OrderChanges {
-		if orderInfo := kp.OrderInfosForPub[orderChange.Id]; orderInfo != nil {
+	for _, orderChange := range orderChanges {
+		if orderInfo := orderInfosForPublish[orderChange.Id]; orderInfo != nil {
 			res = append(res, string(orderInfo.Sender.Bytes()))
 		} else {
 			Logger.Error("failed to locate order change in OrderChangesMap", "orderChange", orderChange.String())
@@ -128,6 +131,12 @@ func GetBlockPublished(pool *sdk.Pool, header abci.Header, blockHash []byte) *Bl
 		case freeze.UnfreezeMsg:
 			txAsset = msg.Symbol
 			// will not cover timelock, timeUnlock, timeRelock, atomic Swap
+		case issue.IssueMiniMsg:
+			txAsset = msg.Symbol
+		case issue.IssueTinyMsg:
+			txAsset = msg.Symbol
+		case seturi.SetURIMsg:
+			txAsset = msg.Symbol
 		}
 		transactionsToPublish = append(transactionsToPublish, Transaction{
 			TxHash:    txhash,
@@ -259,12 +268,10 @@ func GetAccountBalances(mapper auth.AccountKeeper, ctx sdk.Context, accSlices ..
 	return
 }
 
-func MatchAndAllocateAllForPublish(
-	dexKeeper *orderPkg.Keeper,
-	ctx sdk.Context) []*Trade {
-	// This channels is used for protect not update `dexKeeper.OrderChanges` concurrently
+func MatchAndAllocateAllForPublish(dexKeeper *orderPkg.DexKeeper, ctx sdk.Context, matchAllMiniSymbols bool) []*Trade {
+	// This channels is used for protect not update `dexKeeper.orderChanges` concurrently
 	// matcher would send item to postAlloTransHandler in several goroutine (well-designed)
-	// while dexKeeper.OrderChanges are not separated by concurrent factor (users here)
+	// while dexKeeper.orderChanges are not separated by concurrent factor (users here)
 	iocExpireFeeHolderCh := make(chan orderPkg.ExpireHolder, TransferCollectionChannelSize)
 	wg := sync.WaitGroup{}
 	wg.Add(1)
@@ -274,21 +281,27 @@ func MatchAndAllocateAllForPublish(
 		if tran.IsExpire() {
 			if tran.IsExpiredWithFee() {
 				// we only got expire of Ioc here, gte orders expire is handled in breathe block
-				iocExpireFeeHolderCh <- orderPkg.ExpireHolder{tran.Oid, orderPkg.IocNoFill, tran.Fee.String()}
+				iocExpireFeeHolderCh <- orderPkg.ExpireHolder{tran.Oid, orderPkg.IocNoFill, tran.Fee.String(), tran.Symbol}
 			} else {
-				iocExpireFeeHolderCh <- orderPkg.ExpireHolder{tran.Oid, orderPkg.IocExpire, tran.Fee.String()}
+				iocExpireFeeHolderCh <- orderPkg.ExpireHolder{tran.Oid, orderPkg.IocExpire, tran.Fee.String(), tran.Symbol}
 			}
 		}
 	}
 
-	dexKeeper.MatchAndAllocateAll(ctx, postAlloTransHandler)
+	dexKeeper.MatchAndAllocateSymbols(ctx, postAlloTransHandler, matchAllMiniSymbols)
 	close(iocExpireFeeHolderCh)
 
-	tradeIdx := 0
-	tradesToPublish := make([]*Trade, 0)
 	tradeHeight := ctx.BlockHeight()
-	for _, pair := range dexKeeper.PairMapper.ListAllTradingPairs(ctx) {
-		symbol := pair.GetSymbol()
+	tradesToPublish := extractTradesToPublish(dexKeeper, tradeHeight)
+	wg.Wait()
+	return tradesToPublish
+}
+
+func extractTradesToPublish(dexKeeper *orderPkg.DexKeeper, tradeHeight int64) (tradesToPublish []*Trade) {
+	tradesToPublish = make([]*Trade, 0, 32)
+	tradeIdx := 0
+
+	for symbol := range dexKeeper.GetEngines() {
 		matchEngTrades, _ := dexKeeper.GetLastTrades(tradeHeight, symbol)
 		for _, trade := range matchEngTrades {
 			var ssinglefee string
@@ -317,13 +330,11 @@ func MatchAndAllocateAllForPublish(
 			tradesToPublish = append(tradesToPublish, t)
 		}
 	}
-
-	wg.Wait()
 	return tradesToPublish
 }
 
 func ExpireOrdersForPublish(
-	dexKeeper *orderPkg.Keeper,
+	dexKeeper *orderPkg.DexKeeper,
 	ctx sdk.Context,
 	blockTime time.Time) {
 	expireHolderCh := make(chan orderPkg.ExpireHolder, TransferCollectionChannelSize)
@@ -332,7 +343,7 @@ func ExpireOrdersForPublish(
 	go updateExpireFeeForPublish(dexKeeper, &wg, expireHolderCh)
 	var collectorForExpires = func(tran orderPkg.Transfer) {
 		if tran.IsExpire() {
-			expireHolderCh <- orderPkg.ExpireHolder{tran.Oid, orderPkg.Expired, tran.Fee.String()}
+			expireHolderCh <- orderPkg.ExpireHolder{tran.Oid, orderPkg.Expired, tran.Fee.String(), tran.Symbol}
 		}
 	}
 	dexKeeper.ExpireOrders(ctx, blockTime, collectorForExpires)
@@ -341,14 +352,14 @@ func ExpireOrdersForPublish(
 	return
 }
 
-func DelistTradingPairForPublish(ctx sdk.Context, dexKeeper *orderPkg.Keeper, symbol string) {
+func DelistTradingPairForPublish(ctx sdk.Context, dexKeeper *orderPkg.DexKeeper, symbol string) {
 	expireHolderCh := make(chan orderPkg.ExpireHolder, TransferCollectionChannelSize)
 	wg := sync.WaitGroup{}
 	wg.Add(1)
 	go updateExpireFeeForPublish(dexKeeper, &wg, expireHolderCh)
 	var collectorForExpires = func(tran orderPkg.Transfer) {
 		if tran.IsExpire() {
-			expireHolderCh <- orderPkg.ExpireHolder{tran.Oid, orderPkg.Expired, tran.Fee.String()}
+			expireHolderCh <- orderPkg.ExpireHolder{tran.Oid, orderPkg.Expired, tran.Fee.String(), tran.Symbol}
 		}
 	}
 	dexKeeper.DelistTradingPair(ctx, symbol, collectorForExpires)
@@ -380,14 +391,14 @@ func CollectStakeUpdatesForPublish(unbondingDelegations []stake.UnbondingDelegat
 }
 
 func updateExpireFeeForPublish(
-	dexKeeper *orderPkg.Keeper,
+	dexKeeper *orderPkg.DexKeeper,
 	wg *sync.WaitGroup,
 	expHolderCh <-chan orderPkg.ExpireHolder) {
 	defer wg.Done()
 	for expHolder := range expHolderCh {
 		Logger.Debug("transfer collector for order", "orderId", expHolder.OrderId)
 		change := orderPkg.OrderChange{expHolder.OrderId, expHolder.Reason, expHolder.Fee, nil}
-		dexKeeper.OrderChanges = append(dexKeeper.OrderChanges, change)
+		dexKeeper.UpdateOrderChangeSync(change, expHolder.Symbol)
 	}
 }
 
@@ -507,8 +518,7 @@ func collectOrdersToPublish(
 	orderInfos orderPkg.OrderInfoForPublish,
 	feeHolder orderPkg.FeeHolder,
 	timestamp int64) (opensToPublish []*Order, closedToPublish []*Order, feeToPublish map[string]string) {
-	opensToPublish = make([]*Order, 0)
-	closedToPublish = make([]*Order, 0)
+
 	// serve as a cache to avoid fee's serialization several times for one address
 	feeToPublish = make(map[string]string)
 
@@ -519,43 +529,45 @@ func collectOrdersToPublish(
 	chargedExpires := make(map[string]int)
 
 	// collect orders (new, cancel, ioc-no-fill, expire, failed-blocking and failed-matching) from orderChanges
-	for _, o := range orderChanges {
-		if orderInfo := o.ResolveOrderInfo(orderInfos); orderInfo != nil {
-			orderToPublish := Order{
-				orderInfo.Symbol, o.Tpe, o.Id,
-				"", orderInfo.Sender.String(), orderInfo.Side,
-				orderPkg.OrderType.LIMIT, orderInfo.Price, orderInfo.Quantity,
-				0, 0, orderInfo.CumQty, "",
-				orderInfo.CreatedTimestamp, timestamp, orderInfo.TimeInForce,
-				orderPkg.NEW, orderInfo.TxHash, o.SingleFee,
-			}
+	opensToPublish, closedToPublish = collectOrders(orderChanges, orderInfos, timestamp, chargedCancels, chargedExpires)
 
-			if o.Tpe.IsOpen() {
+	// update C and E fields in serialized fee string
+	updateCancelExpireOrderNumInFees(closedToPublish, orderInfos, feeToPublish, chargedCancels, chargedExpires, feeHolder)
+
+	// update fee and collect orders from trades
+	opensToPublish, closedToPublish = convertTradesToOrders(trades, orderInfos, timestamp, feeHolder, feeToPublish, opensToPublish, closedToPublish)
+
+	return opensToPublish, closedToPublish, feeToPublish
+}
+
+func convertTradesToOrders(trades []*Trade, orderInfos orderPkg.OrderInfoForPublish, timestamp int64, feeHolder orderPkg.FeeHolder, feeToPublish map[string]string, opensToPublish []*Order, closedToPublish []*Order) ([]*Order, []*Order) {
+	for _, t := range trades {
+		if o, exists := orderInfos[t.Bid]; exists {
+			orderToPublish := tradeToOrder(t, o, timestamp, feeHolder, feeToPublish)
+			if orderToPublish.Status.IsOpen() {
 				opensToPublish = append(opensToPublish, &orderToPublish)
 			} else {
 				closedToPublish = append(closedToPublish, &orderToPublish)
 			}
+		} else {
+			Logger.Error("failed to resolve order information from orderInfos", "orderId", t.Bid)
+		}
 
-			// fee field handling
-			if orderToPublish.isChargedCancel() {
-				if _, ok := chargedCancels[string(orderInfo.Sender)]; ok {
-					chargedCancels[string(orderInfo.Sender)] += 1
-				} else {
-					chargedCancels[string(orderInfo.Sender)] = 1
-				}
-			} else if orderToPublish.isChargedExpire() {
-				if _, ok := chargedExpires[string(orderInfo.Sender)]; ok {
-					chargedExpires[string(orderInfo.Sender)] += 1
-				} else {
-					chargedExpires[string(orderInfo.Sender)] = 1
-				}
+		if o, exists := orderInfos[t.Sid]; exists {
+			orderToPublish := tradeToOrder(t, o, timestamp, feeHolder, feeToPublish)
+			if orderToPublish.Status.IsOpen() {
+				opensToPublish = append(opensToPublish, &orderToPublish)
+			} else {
+				closedToPublish = append(closedToPublish, &orderToPublish)
 			}
 		} else {
-			Logger.Error("failed to locate order change in OrderChangesMap", "orderChange", o.String())
+			Logger.Error("failed to resolve order information from orderInfos", "orderId", t.Sid)
 		}
 	}
+	return opensToPublish, closedToPublish
+}
 
-	// update C and E fields in serialized fee string
+func updateCancelExpireOrderNumInFees(closedToPublish []*Order, orderInfos orderPkg.OrderInfoForPublish, feeToPublish map[string]string, chargedCancels map[string]int, chargedExpires map[string]int, feeHolder orderPkg.FeeHolder) {
 	for _, order := range closedToPublish {
 		if orderInfo, ok := orderInfos[order.OrderId]; ok {
 			senderBytesStr := string(orderInfo.Sender)
@@ -574,33 +586,47 @@ func collectOrdersToPublish(
 			Logger.Error("should not to locate order in OrderChangesMap", "oid", order.OrderId)
 		}
 	}
+}
 
-	// update fee and collect orders from trades
-	for _, t := range trades {
-		if o, exists := orderInfos[t.Bid]; exists {
-			orderToPublish := tradeToOrder(t, o, timestamp, feeHolder, feeToPublish)
-			if orderToPublish.Status.IsOpen() {
-				opensToPublish = append(opensToPublish, &orderToPublish)
-			} else {
-				closedToPublish = append(closedToPublish, &orderToPublish)
+func collectOrders(orderChanges orderPkg.OrderChanges, orderInfos orderPkg.OrderInfoForPublish, timestamp int64, chargedCancels map[string]int, chargedExpires map[string]int) ([]*Order, []*Order) {
+	opensToPublish := make([]*Order, 0)
+	closedToPublish := make([]*Order, 0)
+	for _, o := range orderChanges {
+		if orderInfo := o.ResolveOrderInfo(orderInfos); orderInfo != nil {
+			orderToPublish := Order{
+				orderInfo.Symbol, o.Tpe, o.Id,
+				"", orderInfo.Sender.String(), orderInfo.Side,
+				orderPkg.OrderType.LIMIT, orderInfo.Price, orderInfo.Quantity,
+				0, 0, orderInfo.CumQty, "",
+				orderInfo.CreatedTimestamp, timestamp, orderInfo.TimeInForce,
+				orderPkg.NEW, orderInfo.TxHash, o.SingleFee,
 			}
-		} else {
-			Logger.Error("failed to resolve order information from orderInfos", "orderId", t.Bid)
-		}
 
-		if o, exists := orderInfos[t.Sid]; exists {
-			orderToPublish := tradeToOrder(t, o, timestamp, feeHolder, feeToPublish)
-			if orderToPublish.Status.IsOpen() {
+			if o.Tpe.IsOpen() {
 				opensToPublish = append(opensToPublish, &orderToPublish)
 			} else {
 				closedToPublish = append(closedToPublish, &orderToPublish)
 			}
+
+			// fee field handling
+			if orderToPublish.isChargedCancel() {
+				if _, ok := chargedCancels[string(orderInfo.Sender)]; ok {
+					chargedCancels[string(orderInfo.Sender)] += 1
+				} else {
+					chargedCancels[string(orderInfo.Sender)] = 1
+				}
+			} else if orderToPublish.isChargedExpire() {
+				if _, ok := chargedExpires[string(orderInfo.Sender)]; ok {
+					chargedExpires[string(orderInfo.Sender)] += 1
+				} else {
+					chargedExpires[string(orderInfo.Sender)] = 1
+				}
+			}
 		} else {
-			Logger.Error("failed to resolve order information from orderInfos", "orderId", t.Sid)
+			Logger.Error("failed to locate order change in OrderChangesMap", "orderChange", o.String())
 		}
 	}
-
-	return opensToPublish, closedToPublish, feeToPublish
+	return opensToPublish, closedToPublish
 }
 
 func getSerializedFeeForOrder(orderInfo *orderPkg.OrderInfo, status orderPkg.ChangeType, feeHolder orderPkg.FeeHolder, feeToPublish map[string]string) string {
diff --git a/app/pub/keeper_pub_test.go b/app/pub/keeper_pub_test.go
index 12cf498c5..ff9f1ad1b 100644
--- a/app/pub/keeper_pub_test.go
+++ b/app/pub/keeper_pub_test.go
@@ -39,7 +39,7 @@ func newTestFeeConfig() orderPkg.FeeConfig {
 	return feeConfig
 }
 
-var keeper *orderPkg.Keeper
+var keeper *orderPkg.DexKeeper
 var buyer sdk.AccAddress
 var seller sdk.AccAddress
 var am auth.AccountKeeper
@@ -61,7 +61,7 @@ func setupKeeperTest(t *testing.T) (*assert.Assertions, *require.Assertions) {
 	ctx = sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, sdk.RunTxModeDeliver, logger).WithAccountCache(accountCache)
 
 	pairMapper := store.NewTradingPairMapper(cdc, common.PairStoreKey)
-	keeper = orderPkg.NewKeeper(capKey2, am, pairMapper, sdk.NewCodespacer().RegisterNext(dextypes.DefaultCodespace), 2, cdc, true)
+	keeper = orderPkg.NewDexKeeper(capKey2, am, pairMapper, sdk.NewCodespacer().RegisterNext(dextypes.DefaultCodespace), 2, cdc, true)
 	tradingPair := dextypes.NewTradingPair("XYZ-000", types.NativeTokenSymbol, 1e8)
 	keeper.PairMapper.AddTradingPair(ctx, tradingPair)
 	keeper.AddEngine(tradingPair)
@@ -72,10 +72,10 @@ func setupKeeperTest(t *testing.T) (*assert.Assertions, *require.Assertions) {
 	//keeper.FeeConfig.SetFeeRate(ctx, 1000)
 	//keeper.FeeConfig.SetFeeRateNative(ctx, 500)
 
-	_, buyerAcc := testutils.NewAccountForPub(ctx, am, 100000000000, 100000000000, 100000000000) // give user enough coins to pay the fee
+	_, buyerAcc := testutils.NewAccountForPub(ctx, am, 100000000000, 100000000000, 100000000000, "XYZ-000") // give user enough coins to pay the fee
 	buyer = buyerAcc.GetAddress()
 
-	_, sellerAcc := testutils.NewAccountForPub(ctx, am, 100000000000, 100000000000, 100000000000)
+	_, sellerAcc := testutils.NewAccountForPub(ctx, am, 100000000000, 100000000000, 100000000000, "XYZ-000")
 	seller = sellerAcc.GetAddress()
 
 	return assert.New(t), require.New(t)
@@ -89,15 +89,15 @@ func TestKeeper_AddOrder(t *testing.T) {
 	msg = orderPkg.NewNewOrderMsg(buyer, "2", orderPkg.Side.BUY, "XYZ-000_BNB", 101000, 1000000)
 	keeper.AddOrder(orderPkg.OrderInfo{msg, 43, 105, 43, 105, 0, "0D42245EB2BF574A5B9D485404E0E61B1A2397A9", 0}, false)
 
-	require.Len(keeper.OrderChanges, 2)
-	require.Len(keeper.OrderInfosForPub, 2)
+	require.Len(keeper.GetOrderChanges(orderPkg.PairType.BEP2), 2)
+	require.Len(keeper.GetOrderInfosForPub(orderPkg.PairType.BEP2), 2)
 	// verify order0 - and the order in orderchanges slice
-	orderChange0 := keeper.OrderChanges[0]
+	orderChange0 := keeper.GetOrderChanges(orderPkg.PairType.BEP2)[0]
 	assert.Equal("1", orderChange0.Id)
 	assert.Equal(orderPkg.Ack, orderChange0.Tpe)
 
 	// verify order1 - make sure the fields are correct
-	orderInfo1 := keeper.OrderInfosForPub["2"]
+	orderInfo1 := keeper.GetOrderInfosForPub(orderPkg.PairType.BEP2)["2"]
 	assert.Equal(buyer, orderInfo1.Sender)
 	assert.Equal("2", orderInfo1.Id)
 	assert.Equal("XYZ-000_BNB", orderInfo1.Symbol)
@@ -117,17 +117,19 @@ func TestKeeper_IOCExpireWithFee(t *testing.T) {
 	msg := orderPkg.NewOrderMsg{buyer, "1", "XYZ-000_BNB", orderPkg.OrderType.LIMIT, orderPkg.Side.BUY, 102000, 3000000, orderPkg.TimeInForce.IOC}
 	keeper.AddOrder(orderPkg.OrderInfo{msg, 42, 100, 42, 100, 0, "08E19B16880CF70D59DDD996E3D75C66CD0405DE", 0}, false)
 
-	require.Len(keeper.OrderChanges, 1)
-	require.Len(keeper.OrderInfosForPub, 1)
+	require.Len(keeper.GetOrderChanges(orderPkg.PairType.BEP2), 1)
+	require.Len(keeper.GetOrderInfosForPub(orderPkg.PairType.BEP2), 1)
 
-	trades := MatchAndAllocateAllForPublish(keeper, ctx)
+	trades := MatchAndAllocateAllForPublish(keeper, ctx, false)
 
-	require.Len(keeper.OrderChanges, 2)
-	require.Len(keeper.OrderInfosForPub, 1)
+	require.Len(keeper.GetOrderChanges(orderPkg.PairType.BEP2), 2)
+	require.Len(keeper.GetOrderInfosForPub(orderPkg.PairType.BEP2), 1)
 	require.Len(trades, 0)
+	require.Len(keeper.GetAllOrderChanges(), 2)
+	require.Len(keeper.GetAllOrderInfosForPub(), 1)
 
-	orderChange0 := keeper.OrderChanges[0]
-	orderChange1 := keeper.OrderChanges[1]
+	orderChange0 := keeper.GetOrderChanges(orderPkg.PairType.BEP2)[0]
+	orderChange1 := keeper.GetOrderChanges(orderPkg.PairType.BEP2)[1]
 	// verify orderChange0 - Ack
 	assert.Equal("1", orderChange0.Id)
 	assert.Equal(orderPkg.Ack, orderChange0.Tpe)
@@ -143,17 +145,17 @@ func TestKeeper_ExpireWithFee(t *testing.T) {
 	msg := orderPkg.NewOrderMsg{buyer, "1", "XYZ-000_BNB", orderPkg.OrderType.LIMIT, orderPkg.Side.BUY, 102000, 3000000, orderPkg.TimeInForce.GTE}
 	keeper.AddOrder(orderPkg.OrderInfo{msg, 42, 100, 42, 100, 0, "08E19B16880CF70D59DDD996E3D75C66CD0405DE", 0}, false)
 
-	require.Len(keeper.OrderChanges, 1)
-	require.Len(keeper.OrderInfosForPub, 1)
+	require.Len(keeper.GetOrderChanges(orderPkg.PairType.BEP2), 1)
+	require.Len(keeper.GetOrderInfosForPub(orderPkg.PairType.BEP2), 1)
 
 	breathTime := prepareExpire(int64(43))
 	ExpireOrdersForPublish(keeper, ctx, breathTime)
 
-	require.Len(keeper.OrderChanges, 2)
-	require.Len(keeper.OrderInfosForPub, 1)
+	require.Len(keeper.GetOrderChanges(orderPkg.PairType.BEP2), 2)
+	require.Len(keeper.GetOrderInfosForPub(orderPkg.PairType.BEP2), 1)
 
-	orderChange0 := keeper.OrderChanges[0]
-	orderChange1 := keeper.OrderChanges[1]
+	orderChange0 := keeper.GetOrderChanges(orderPkg.PairType.BEP2)[0]
+	orderChange1 := keeper.GetOrderChanges(orderPkg.PairType.BEP2)[1]
 	// verify orderChange0 - Ack
 	assert.Equal("1", orderChange0.Id)
 	assert.Equal(orderPkg.Ack, orderChange0.Tpe)
@@ -168,16 +170,16 @@ func TestKeeper_DelistWithFee(t *testing.T) {
 	msg := orderPkg.NewOrderMsg{buyer, "1", "XYZ-000_BNB", orderPkg.OrderType.LIMIT, orderPkg.Side.BUY, 102000, 3000000, orderPkg.TimeInForce.GTE}
 	keeper.AddOrder(orderPkg.OrderInfo{msg, 42, 100, 42, 100, 0, "08E19B16880CF70D59DDD996E3D75C66CD0405DE", 0}, false)
 
-	require.Len(keeper.OrderChanges, 1)
-	require.Len(keeper.OrderInfosForPub, 1)
+	require.Len(keeper.GetOrderChanges(orderPkg.PairType.BEP2), 1)
+	require.Len(keeper.GetOrderInfosForPub(orderPkg.PairType.BEP2), 1)
 
 	DelistTradingPairForPublish(ctx, keeper, "XYZ-000_BNB")
 
-	require.Len(keeper.OrderChanges, 2)
-	require.Len(keeper.OrderInfosForPub, 1)
+	require.Len(keeper.GetOrderChanges(orderPkg.PairType.BEP2), 2)
+	require.Len(keeper.GetOrderInfosForPub(orderPkg.PairType.BEP2), 1)
 
-	orderChange0 := keeper.OrderChanges[0]
-	orderChange1 := keeper.OrderChanges[1]
+	orderChange0 := keeper.GetOrderChanges(orderPkg.PairType.BEP2)[0]
+	orderChange1 := keeper.GetOrderChanges(orderPkg.PairType.BEP2)[1]
 
 	// verify orderChange0 - Ack
 	assert.Equal("1", orderChange0.Id)
@@ -195,10 +197,10 @@ func Test_IOCPartialExpire(t *testing.T) {
 	msg2 := orderPkg.NewOrderMsg{seller, "s-1", "XYZ-000_BNB", orderPkg.OrderType.LIMIT, orderPkg.Side.SELL, 100000000, 100000000, orderPkg.TimeInForce.GTE}
 	keeper.AddOrder(orderPkg.OrderInfo{msg2, 42, 100, 42, 100, 0, "", 0}, false)
 
-	require.Len(keeper.OrderChanges, 2)
-	require.Len(keeper.OrderInfosForPub, 2)
-	orderChange0 := keeper.OrderChanges[0]
-	orderChange1 := keeper.OrderChanges[1]
+	require.Len(keeper.GetOrderChanges(orderPkg.PairType.BEP2), 2)
+	require.Len(keeper.GetOrderInfosForPub(orderPkg.PairType.BEP2), 2)
+	orderChange0 := keeper.GetOrderChanges(orderPkg.PairType.BEP2)[0]
+	orderChange1 := keeper.GetOrderChanges(orderPkg.PairType.BEP2)[1]
 	// verify orderChange0 - Ack
 	assert.Equal("b-1", orderChange0.Id)
 	assert.Equal(orderPkg.Ack, orderChange0.Tpe)
@@ -206,10 +208,10 @@ func Test_IOCPartialExpire(t *testing.T) {
 	assert.Equal("s-1", orderChange1.Id)
 	assert.Equal(orderPkg.Ack, orderChange1.Tpe)
 
-	trades := MatchAndAllocateAllForPublish(keeper, ctx)
+	trades := MatchAndAllocateAllForPublish(keeper, ctx, false)
 
-	require.Len(keeper.OrderChanges, 3)
-	require.Len(keeper.OrderInfosForPub, 2)
+	require.Len(keeper.GetOrderChanges(orderPkg.PairType.BEP2), 3)
+	require.Len(keeper.GetOrderInfosForPub(orderPkg.PairType.BEP2), 2)
 	require.Len(trades, 1)
 	trade0 := trades[0]
 	assert.Equal("0-0", trade0.Id)
@@ -219,7 +221,7 @@ func Test_IOCPartialExpire(t *testing.T) {
 	assert.Equal("s-1", trade0.Sid)
 	assert.Equal("b-1", trade0.Bid)
 
-	orderChange2 := keeper.OrderChanges[2]
+	orderChange2 := keeper.GetOrderChanges(orderPkg.PairType.BEP2)[2]
 	assert.Equal("b-1", orderChange2.Id)
 	assert.Equal(orderPkg.IocExpire, orderChange2.Tpe)
 
@@ -235,10 +237,10 @@ func Test_GTEPartialExpire(t *testing.T) {
 	msg2 := orderPkg.NewOrderMsg{seller, "s-1", "XYZ-000_BNB", orderPkg.OrderType.LIMIT, orderPkg.Side.SELL, 100000000, 300000000, orderPkg.TimeInForce.GTE}
 	keeper.AddOrder(orderPkg.OrderInfo{msg2, 42, 100, 42, 100, 0, "", 0}, false)
 
-	require.Len(keeper.OrderChanges, 2)
-	require.Len(keeper.OrderInfosForPub, 2)
-	orderChange0 := keeper.OrderChanges[0]
-	orderChange1 := keeper.OrderChanges[1]
+	require.Len(keeper.GetOrderChanges(orderPkg.PairType.BEP2), 2)
+	require.Len(keeper.GetOrderInfosForPub(orderPkg.PairType.BEP2), 2)
+	orderChange0 := keeper.GetOrderChanges(orderPkg.PairType.BEP2)[0]
+	orderChange1 := keeper.GetOrderChanges(orderPkg.PairType.BEP2)[1]
 	// verify orderChange0 - Ack
 	assert.Equal("b-1", orderChange0.Id)
 	assert.Equal(orderPkg.Ack, orderChange0.Tpe)
@@ -246,7 +248,7 @@ func Test_GTEPartialExpire(t *testing.T) {
 	assert.Equal("s-1", orderChange1.Id)
 	assert.Equal(orderPkg.Ack, orderChange1.Tpe)
 
-	trades := MatchAndAllocateAllForPublish(keeper, ctx)
+	trades := MatchAndAllocateAllForPublish(keeper, ctx, false)
 	require.Len(trades, 1)
 	trade0 := trades[0]
 	assert.Equal("0-0", trade0.Id)
@@ -259,15 +261,15 @@ func Test_GTEPartialExpire(t *testing.T) {
 	assert.Equal("BNB:50000", keeper.RoundOrderFees[string(buyer.Bytes())].String())
 	assert.Equal("BNB:50000", keeper.RoundOrderFees[string(seller.Bytes())].String())
 
-	require.Len(keeper.OrderChanges, 2) // for GTE order, fully fill is not derived from transfer (will be generated by trade)
-	require.Len(keeper.OrderInfosForPub, 2)
+	require.Len(keeper.GetOrderChanges(orderPkg.PairType.BEP2), 2) // for GTE order, fully fill is not derived from transfer (will be generated by trade)
+	require.Len(keeper.GetOrderInfosForPub(orderPkg.PairType.BEP2), 2)
 
 	// let the sell order expire
 	breathTime := prepareExpire(int64(43))
 	ExpireOrdersForPublish(keeper, ctx, breathTime)
 
-	require.Len(keeper.OrderChanges, 3)
-	orderChange2 := keeper.OrderChanges[2]
+	require.Len(keeper.GetOrderChanges(orderPkg.PairType.BEP2), 3)
+	orderChange2 := keeper.GetOrderChanges(orderPkg.PairType.BEP2)[2]
 	assert.Equal("s-1", orderChange2.Id)
 	assert.Equal(orderPkg.Expired, orderChange2.Tpe)
 }
@@ -282,11 +284,11 @@ func Test_OneBuyVsTwoSell(t *testing.T) {
 	msg3 := orderPkg.NewOrderMsg{seller, "s-2", "XYZ-000_BNB", orderPkg.OrderType.LIMIT, orderPkg.Side.SELL, 100000000, 200000000, orderPkg.TimeInForce.GTE}
 	keeper.AddOrder(orderPkg.OrderInfo{msg3, 42, 100, 42, 100, 0, "", 0}, false)
 
-	require.Len(keeper.OrderChanges, 3)
-	require.Len(keeper.OrderInfosForPub, 3)
-	orderChange0 := keeper.OrderChanges[0]
-	orderChange1 := keeper.OrderChanges[1]
-	orderChange2 := keeper.OrderChanges[2]
+	require.Len(keeper.GetOrderChanges(orderPkg.PairType.BEP2), 3)
+	require.Len(keeper.GetOrderInfosForPub(orderPkg.PairType.BEP2), 3)
+	orderChange0 := keeper.GetOrderChanges(orderPkg.PairType.BEP2)[0]
+	orderChange1 := keeper.GetOrderChanges(orderPkg.PairType.BEP2)[1]
+	orderChange2 := keeper.GetOrderChanges(orderPkg.PairType.BEP2)[2]
 	// verify orderChange0 - Ack
 	assert.Equal("b-1", orderChange0.Id)
 	assert.Equal(orderPkg.Ack, orderChange0.Tpe)
@@ -297,7 +299,7 @@ func Test_OneBuyVsTwoSell(t *testing.T) {
 	assert.Equal("s-2", orderChange2.Id)
 	assert.Equal(orderPkg.Ack, orderChange2.Tpe)
 
-	trades := MatchAndAllocateAllForPublish(keeper, ctx)
+	trades := MatchAndAllocateAllForPublish(keeper, ctx, false)
 	require.Len(trades, 2)
 	trade0 := trades[0]
 	assert.Equal("0-0", trade0.Id)
diff --git a/app/pub/msgs.go b/app/pub/msgs.go
index bf55031fb..b97ca0897 100644
--- a/app/pub/msgs.go
+++ b/app/pub/msgs.go
@@ -121,12 +121,14 @@ func (msg *ExecutionResults) ToNativeMap() map[string]interface{} {
 	if msg.StakeUpdates.NumOfMsgs > 0 {
 		native["stakeUpdates"] = map[string]interface{}{"org.binance.dex.model.avro.StakeUpdates": msg.StakeUpdates.ToNativeMap()}
 	}
+
 	return native
 }
 
 func (msg *ExecutionResults) EssentialMsg() string {
 	// mainly used to recover for large breathe block expiring message, there should be no trade on breathe block
 	orders := msg.Orders.EssentialMsg()
+	//TODO output other fields: trades, stakeUpdate etc.
 	return fmt.Sprintf("height:%d\ntime:%d\norders:\n%s\n", msg.Height, msg.Timestamp, orders)
 }
 
diff --git a/app/pub/publisher.go b/app/pub/publisher.go
index 33c6d0e6a..eb17b44c4 100644
--- a/app/pub/publisher.go
+++ b/app/pub/publisher.go
@@ -18,11 +18,16 @@ const (
 	MaxOrderBookLevel             = 100
 )
 
+type OrderSymbolId struct {
+	Symbol string
+	Id     string
+}
+
 var (
 	Logger            tmlog.Logger
 	Cfg               *config.PublicationConfig
 	ToPublishCh       chan BlockInfoToPublish
-	ToRemoveOrderIdCh chan string // order ids to remove from keeper.OrderInfoForPublish
+	ToRemoveOrderIdCh chan OrderSymbolId // order symbol and ids to remove from keeper.OrderInfoForPublish
 	IsLive            bool
 )
 
@@ -58,14 +63,7 @@ func Publish(
 					marketData.orderInfos,
 					marketData.feeHolder,
 					marketData.timestamp)
-				for _, o := range closedToPublish {
-					if ToRemoveOrderIdCh != nil {
-						Logger.Debug(
-							"going to delete order from order changes map",
-							"orderId", o.OrderId, "status", o.Status)
-						ToRemoveOrderIdCh <- o.OrderId
-					}
-				}
+				addClosedOrder(closedToPublish, ToRemoveOrderIdCh)
 			}
 
 			// ToRemoveOrderIdCh would be only used in production code
@@ -75,6 +73,7 @@ func Publish(
 			}
 
 			ordersToPublish := append(opensToPublish, closedToPublish...)
+
 			if cfg.PublishOrderUpdates {
 				duration := Timer(Logger, "publish all orders", func() {
 					publishExecutionResult(
@@ -106,7 +105,7 @@ func Publish(
 			}
 
 			if cfg.PublishOrderBook {
-				var changedPrices orderPkg.ChangedPriceLevelsMap
+				var changedPrices = make(orderPkg.ChangedPriceLevelsMap)
 				duration := Timer(Logger, "prepare order books to publish", func() {
 					changedPrices = filterChangedOrderBooksByOrders(ordersToPublish, marketData.latestPricesLevels)
 				})
@@ -172,6 +171,17 @@ func Publish(
 	}
 }
 
+func addClosedOrder(closedToPublish []*Order, toRemoveOrderIdCh chan OrderSymbolId) {
+	if toRemoveOrderIdCh != nil {
+		for _, o := range closedToPublish {
+			Logger.Debug(
+				"going to delete order from order changes map",
+				"orderId", o.OrderId, "status", o.Status)
+			toRemoveOrderIdCh <- OrderSymbolId{o.Symbol, o.OrderId}
+		}
+	}
+}
+
 func Stop(publisher MarketDataPublisher) {
 	if IsLive == false {
 		Logger.Error("publication module has already been stopped")
diff --git a/app/pub/types.go b/app/pub/types.go
index cec82935e..562162836 100644
--- a/app/pub/types.go
+++ b/app/pub/types.go
@@ -46,5 +46,6 @@ func NewBlockInfoToPublish(
 		blockFee,
 		feeHolder,
 		transfers,
-		block}
+		block,
+	}
 }
diff --git a/app_test/abci_open_orders_test.go b/app_test/abci_open_orders_test.go
index 1b94ad65b..2c5ef262e 100644
--- a/app_test/abci_open_orders_test.go
+++ b/app_test/abci_open_orders_test.go
@@ -18,8 +18,21 @@ import (
 )
 
 func Test_Success(t *testing.T) {
-	assert, require, pair := setup(t)
+	baseSymbol := "XYZ-000"
+	runTestSuccess(t, baseSymbol, true)
+}
+
+func Test_Success_Before_Upgrade(t *testing.T) {
+	baseSymbol := "XYZ-000"
+	runTestSuccess(t, baseSymbol, false)
+}
 
+func Test_Success_Mini(t *testing.T) {
+	baseSymbol := "XYZ-000M"
+	runTestSuccess(t, baseSymbol, true)
+}
+func runTestSuccess(t *testing.T, symbol string, upgrade bool) {
+	assert, require, pair := setup(t, symbol, true)
 	msg := orderPkg.NewNewOrderMsg(buyer, "b-1", orderPkg.Side.BUY, pair, 102000, 3000000)
 	keeper.AddOrder(orderPkg.OrderInfo{msg, 100, 0, 100, 0, 0, "", 0}, false)
 
@@ -38,7 +51,7 @@ func Test_Success(t *testing.T) {
 
 	ctx = ctx.WithBlockHeader(abci.Header{Height: 101, Time: time.Unix(1, 0)})
 	ctx = ctx.WithBlockHeight(101)
-	keeper.MatchAndAllocateAll(ctx, nil)
+	keeper.MatchAndAllocateSymbols(ctx, nil, false)
 
 	openOrders = issueMustSuccessQuery(pair, buyer, assert)
 	require.Len(openOrders, 1)
@@ -59,15 +72,52 @@ func Test_Success(t *testing.T) {
 }
 
 func Test_InvalidPair(t *testing.T) {
-	assert, _, _ := setup(t)
+	setChainVersion()
+	defer resetChainVersion()
+	baseSymbol := "XYZ-000"
+	runInvalidPair(t, baseSymbol)
+}
+
+func Test_InvalidPair_Before_Upgrade(t *testing.T) {
+	baseSymbol := "XYZ-000"
+	runInvalidPair(t, baseSymbol)
+}
 
+func Test_InvalidPair_Mini(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	baseSymbol := "XYZ-000M"
+	runInvalidPair(t, baseSymbol)
+}
+
+func runInvalidPair(t *testing.T, symbol string) {
+	assert, _, _ := setup(t, symbol, true)
 	res := issueQuery("%afuiewf%@^&2blf", buyer.String())
 	assert.Equal(uint32(sdk.CodeInternal), res.Code)
 	assert.Equal("pair is not valid", res.Log)
 }
 
 func Test_NonListedPair(t *testing.T) {
-	assert, _, _ := setup(t)
+	setChainVersion()
+	defer resetChainVersion()
+	baseSymbol := "XYZ-000"
+	runNonListedPair(t, baseSymbol)
+}
+
+func Test_NonListedPair_Before_Upgrade(t *testing.T) {
+	baseSymbol := "XYZ-000"
+	runNonListedPair(t, baseSymbol)
+}
+
+func Test_NonListedPair_Mini(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	baseSymbol := "XYZ-000M"
+	runNonListedPair(t, baseSymbol)
+}
+
+func runNonListedPair(t *testing.T, symbol string) {
+	assert, _, _ := setup(t, symbol, true)
 
 	res := issueQuery("NNB-000_BNB", buyer.String())
 	assert.Equal(uint32(sdk.CodeInternal), res.Code)
@@ -75,7 +125,26 @@ func Test_NonListedPair(t *testing.T) {
 }
 
 func Test_InvalidAddr(t *testing.T) {
-	assert, _, pair := setup(t)
+	setChainVersion()
+	defer resetChainVersion()
+	baseSymbol := "XYZ-000"
+	runInvalidAddr(t, baseSymbol)
+}
+
+func Test_InvalidAddr_Before_Upgrade(t *testing.T) {
+	baseSymbol := "XYZ-000"
+	runInvalidAddr(t, baseSymbol)
+}
+
+func Test_InvalidAddr_Mini(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	baseSymbol := "XYZ-000M"
+	runInvalidAddr(t, baseSymbol)
+}
+
+func runInvalidAddr(t *testing.T, symbol string) {
+	assert, _, pair := setup(t, symbol, true)
 
 	res := issueQuery(pair, "%afuiewf%@^&2blf")
 	assert.Equal(uint32(sdk.CodeInternal), res.Code)
@@ -83,7 +152,26 @@ func Test_InvalidAddr(t *testing.T) {
 }
 
 func Test_NonExistAddr(t *testing.T) {
-	assert, _, pair := setup(t)
+	setChainVersion()
+	defer resetChainVersion()
+	baseSymbol := "XYZ-000"
+	runNonExistAddr(t, baseSymbol)
+}
+
+func Test_NonExistAddr_Before_Upgrade(t *testing.T) {
+	baseSymbol := "XYZ-000"
+	runNonExistAddr(t, baseSymbol)
+}
+
+func Test_NonExistAddr_Mini(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	baseSymbol := "XYZ-000M"
+	runNonExistAddr(t, baseSymbol)
+}
+
+func runNonExistAddr(t *testing.T, symbol string) {
+	assert, _, pair := setup(t, symbol, true)
 
 	msg := orderPkg.NewNewOrderMsg(seller, "s-1", orderPkg.Side.SELL, pair, 102000, 3000000)
 	keeper.AddOrder(orderPkg.OrderInfo{msg, 100, 0, 100, 0, 0, "", 0}, false)
@@ -101,7 +189,7 @@ func issueMustSuccessQuery(pair string, address sdk.AccAddress, assert *assert.A
 }
 
 func issueQuery(pair string, address string) abci.ResponseQuery {
-	path := fmt.Sprintf("/%s/openorders/%s/%s", dex.AbciQueryPrefix, pair, address)
+	path := fmt.Sprintf("/%s/openorders/%s/%s", dex.DexAbciQueryPrefix, pair, address)
 	query := abci.RequestQuery{
 		Path: path,
 		Data: []byte(""),
diff --git a/app_test/utils_test.go b/app_test/utils_test.go
index 2af12b401..980b81d34 100644
--- a/app_test/utils_test.go
+++ b/app_test/utils_test.go
@@ -17,6 +17,7 @@ import (
 	appPkg "github.com/binance-chain/node/app"
 	"github.com/binance-chain/node/common/testutils"
 	"github.com/binance-chain/node/common/types"
+	"github.com/binance-chain/node/common/upgrade"
 	orderPkg "github.com/binance-chain/node/plugins/dex/order"
 	dextypes "github.com/binance-chain/node/plugins/dex/types"
 	"github.com/binance-chain/node/wire"
@@ -24,7 +25,7 @@ import (
 
 // this file has to named with suffix _test, this is a golang bug: https://github.com/golang/go/issues/24895
 var (
-	keeper *orderPkg.Keeper
+	keeper *orderPkg.DexKeeper
 	buyer  sdk.AccAddress
 	seller sdk.AccAddress
 	am     auth.AccountKeeper
@@ -33,8 +34,8 @@ var (
 	cdc    *wire.Codec
 )
 
-func setup(t *testing.T) (ass *assert.Assertions, req *require.Assertions, pair string) {
-	baseAssetSymbol := "XYZ-000"
+func setup(t *testing.T, symbol string, upgrade bool) (ass *assert.Assertions, req *require.Assertions, pair string) {
+	baseAssetSymbol := symbol
 	logger := log.NewTMLogger(os.Stdout)
 
 	db := dbm.NewMemDB()
@@ -43,19 +44,32 @@ func setup(t *testing.T) (ass *assert.Assertions, req *require.Assertions, pair
 	ctx = app.GetContextForCheckState()
 	cdc = app.GetCodec()
 
+	if upgrade {
+		setChainVersion()
+	}
+
 	keeper = app.(*appPkg.BinanceChain).DexKeeper
 	tradingPair := dextypes.NewTradingPair(baseAssetSymbol, types.NativeTokenSymbol, 1e8)
 	keeper.PairMapper.AddTradingPair(ctx, tradingPair)
 	keeper.AddEngine(tradingPair)
 
 	am = app.(*appPkg.BinanceChain).AccountKeeper
-	_, buyerAcc := testutils.NewAccountForPub(ctx, am, 100000000000, 100000000000, 100000000000) // give user enough coins to pay the fee
+	_, buyerAcc := testutils.NewAccountForPub(ctx, am, 100000000000, 100000000000, 100000000000, symbol) // give user enough coins to pay the fee
 	buyer = buyerAcc.GetAddress()
 
-	_, sellerAcc := testutils.NewAccountForPub(ctx, am, 100000000000, 100000000000, 100000000000)
+	_, sellerAcc := testutils.NewAccountForPub(ctx, am, 100000000000, 100000000000, 100000000000, symbol)
 	seller = sellerAcc.GetAddress()
 
 	pair = fmt.Sprintf("%s_%s", baseAssetSymbol, types.NativeTokenSymbol)
 
 	return assert.New(t), require.New(t), pair
 }
+
+func setChainVersion() {
+	upgrade.Mgr.AddUpgradeHeight(upgrade.BEP8, -1)
+	upgrade.Mgr.AddUpgradeHeight(upgrade.BEP70, -1)
+}
+
+func resetChainVersion() {
+	upgrade.Mgr.Config.HeightMap = nil
+}
diff --git a/common/testutils/testutils.go b/common/testutils/testutils.go
index 75587ecc5..c85255808 100644
--- a/common/testutils/testutils.go
+++ b/common/testutils/testutils.go
@@ -13,20 +13,27 @@ import (
 )
 
 func SetupMultiStoreForUnitTest() (sdk.MultiStore, *sdk.KVStoreKey, *sdk.KVStoreKey) {
-	_, ms, capKey, capKey2 := SetupMultiStoreWithDBForUnitTest()
+	_, ms, capKey, capKey2, _ := SetupMultiStoreWithDBForUnitTest()
 	return ms, capKey, capKey2
 }
 
-func SetupMultiStoreWithDBForUnitTest() (dbm.DB, sdk.MultiStore, *sdk.KVStoreKey, *sdk.KVStoreKey) {
+func SetupThreeMultiStoreForUnitTest() (sdk.MultiStore, *sdk.KVStoreKey, *sdk.KVStoreKey, *sdk.KVStoreKey) {
+	_, ms, capKey, capKey2, capKey3 := SetupMultiStoreWithDBForUnitTest()
+	return ms, capKey, capKey2, capKey3
+}
+
+func SetupMultiStoreWithDBForUnitTest() (dbm.DB, sdk.MultiStore, *sdk.KVStoreKey, *sdk.KVStoreKey, *sdk.KVStoreKey) {
 	db := dbm.NewMemDB()
 	capKey := sdk.NewKVStoreKey("capkey")
 	capKey2 := sdk.NewKVStoreKey("capkey2")
+	capKey3 := sdk.NewKVStoreKey("capkey3")
 	ms := store.NewCommitMultiStore(db)
 	ms.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db)
 	ms.MountStoreWithDB(capKey2, sdk.StoreTypeIAVL, db)
+	ms.MountStoreWithDB(capKey3, sdk.StoreTypeIAVL, db)
 	ms.MountStoreWithDB(common.PairStoreKey, sdk.StoreTypeIAVL, db)
 	ms.LoadLatestVersion()
-	return db, ms, capKey, capKey2
+	return db, ms, capKey, capKey2, capKey3
 }
 
 // coins to more than cover the fee
@@ -72,19 +79,19 @@ func NewNamedAccount(ctx sdk.Context, am auth.AccountKeeper, free int64) (crypto
 	return privKey, appAcc
 }
 
-func NewAccountForPub(ctx sdk.Context, am auth.AccountKeeper, free, locked, freeze int64) (crypto.PrivKey, sdk.Account) {
+func NewAccountForPub(ctx sdk.Context, am auth.AccountKeeper, free, locked, freeze int64, symbol string) (crypto.PrivKey, sdk.Account) {
 	privKey, addr := PrivAndAddr()
 	acc := am.NewAccountWithAddress(ctx, addr)
 	coins := NewNativeTokens(free)
-	coins = append(coins, sdk.NewCoin("XYZ-000", free))
+	coins = append(coins, sdk.NewCoin(symbol, free))
 	acc.SetCoins(coins)
 
 	appAcc := acc.(*types.AppAccount)
 	lockedCoins := NewNativeTokens(locked)
-	lockedCoins = append(lockedCoins, sdk.NewCoin("XYZ-000", locked))
+	lockedCoins = append(lockedCoins, sdk.NewCoin(symbol, locked))
 	appAcc.SetLockedCoins(lockedCoins)
 	freezeCoins := NewNativeTokens(freeze)
-	freezeCoins = append(freezeCoins, sdk.NewCoin("XYZ-000", freeze))
+	freezeCoins = append(freezeCoins, sdk.NewCoin(symbol, freeze))
 	appAcc.SetFrozenCoins(freezeCoins)
 	am.SetAccount(ctx, acc)
 	return privKey, acc
diff --git a/common/types/mini_token.go b/common/types/mini_token.go
new file mode 100644
index 000000000..0ffba0569
--- /dev/null
+++ b/common/types/mini_token.go
@@ -0,0 +1,212 @@
+package types
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"regexp"
+	"strings"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+
+	"github.com/binance-chain/node/common/utils"
+)
+
+const (
+	MiniTokenSymbolMaxLen          = 8
+	MiniTokenSymbolMinLen          = 3
+	MiniTokenSymbolSuffixLen       = 4 // probably enough. if it collides (unlikely) the issuer can just use another tx.
+	MiniTokenSymbolTxHashSuffixLen = 3 // probably enough. if it collides (unlikely) the issuer can just use another tx.
+	MiniTokenSymbolMSuffix         = "M"
+
+	MiniTokenMinExecutionAmount int64 = 1e8       // 1 with 8 decimal digits
+	MiniTokenSupplyUpperBound   int64 = 1000000e8 // 1m with 8 decimal digits
+	TinyTokenSupplyUpperBound   int64 = 10000e8  // 10k with 8 decimal digits
+	MaxTokenURILength                 = 2048
+
+	TinyRangeType SupplyRangeType = 1
+	MiniRangeType SupplyRangeType = 2
+)
+
+type SupplyRangeType int8
+
+func (t SupplyRangeType) UpperBound() int64 {
+	switch t {
+	case TinyRangeType:
+		return TinyTokenSupplyUpperBound
+	case MiniRangeType:
+		return MiniTokenSupplyUpperBound
+	default:
+		return -1
+	}
+}
+
+func (t SupplyRangeType) String() string {
+	switch t {
+	case TinyRangeType:
+		return "Tiny"
+	case MiniRangeType:
+		return "Mini"
+	default:
+		return "Unknown"
+	}
+}
+
+var SupplyRange = struct {
+	TINY SupplyRangeType
+	MINI SupplyRangeType
+}{TinyRangeType, MiniRangeType}
+
+type MiniToken struct {
+	Name        string          `json:"name"`
+	Symbol      string          `json:"symbol"`
+	OrigSymbol  string          `json:"original_symbol"`
+	TotalSupply utils.Fixed8    `json:"total_supply"`
+	Owner       sdk.AccAddress  `json:"owner"`
+	Mintable    bool            `json:"mintable"`
+	TokenType   SupplyRangeType `json:"token_type"`
+	TokenURI    string          `json:"token_uri"` //TODO set max length
+}
+
+var _ IToken = &MiniToken{}
+
+func NewMiniToken(name, origSymbol, symbol string, supplyRangeType SupplyRangeType, totalSupply int64, owner sdk.AccAddress, mintable bool, tokenURI string) *MiniToken {
+	return &MiniToken{
+		Name:        name,
+		Symbol:      symbol,
+		OrigSymbol:  origSymbol,
+		TotalSupply: utils.Fixed8(totalSupply),
+		Owner:       owner,
+		Mintable:    mintable,
+		TokenType:   supplyRangeType,
+		TokenURI:    tokenURI,
+	}
+}
+
+func (token MiniToken) GetName() string {
+	return token.Name
+}
+
+func (token MiniToken) GetSymbol() string {
+	return token.Symbol
+}
+
+func (token MiniToken) GetOrigSymbol() string {
+	return token.OrigSymbol
+}
+
+func (token MiniToken) GetTotalSupply() utils.Fixed8 {
+	return token.TotalSupply
+}
+
+func (token *MiniToken) SetTotalSupply(totalSupply utils.Fixed8) {
+	token.TotalSupply = totalSupply
+}
+
+func (token MiniToken) GetOwner() sdk.AccAddress {
+	return token.Owner
+}
+
+func (token MiniToken) IsMintable() bool {
+	return token.Mintable
+}
+
+func (token *MiniToken) IsOwner(addr sdk.AccAddress) bool {
+	return bytes.Equal(token.Owner, addr)
+}
+
+func (token MiniToken) String() string {
+	return fmt.Sprintf("{Name: %v, Symbol: %v, TokenType: %v, TotalSupply: %v, Owner: %X, Mintable: %v, TokenURI: %v}",
+		token.Name, token.Symbol, token.TokenType, token.TotalSupply, token.Owner, token.Mintable, token.TokenURI)
+}
+
+//check if it's mini token by last letter without validation
+func IsMiniTokenSymbol(symbol string) bool {
+	if symbol == NativeTokenSymbol ||
+		symbol == NativeTokenSymbolDotBSuffixed {
+		return false
+	}
+	parts, err := splitSuffixedTokenSymbol(symbol)
+	if err != nil {
+		return false
+	}
+	suffixPart := parts[1]
+
+	return len(suffixPart) == MiniTokenSymbolSuffixLen && strings.HasSuffix(suffixPart, MiniTokenSymbolMSuffix)
+}
+
+//Validate and check if it's mini token
+func IsValidMiniTokenSymbol(symbol string) bool {
+	return ValidateMiniTokenSymbol(symbol) == nil
+}
+
+func ValidateIssueMiniSymbol(symbol string) error {
+	if len(symbol) == 0 {
+		return errors.New("token symbol cannot be empty")
+	}
+
+	if symbol == NativeTokenSymbol ||
+		symbol == NativeTokenSymbolDotBSuffixed {
+		return errors.New("symbol cannot be the same as native token")
+	}
+
+	// check len without suffix
+	if symbolLen := len(symbol); symbolLen > MiniTokenSymbolMaxLen || symbolLen < MiniTokenSymbolMinLen {
+		return errors.New("length of token symbol is limited to 3~8")
+	}
+
+	if !utils.IsAlphaNum(symbol) {
+		return errors.New("token symbol should be alphanumeric")
+	}
+
+	return nil
+}
+
+func ValidateMiniTokenSymbol(symbol string) error {
+	if len(symbol) == 0 {
+		return errors.New("suffixed token symbol cannot be empty")
+	}
+
+	if symbol == NativeTokenSymbol ||
+		symbol == NativeTokenSymbolDotBSuffixed {
+		return errors.New("symbol cannot be the same as native token")
+	}
+
+	parts, err := splitSuffixedTokenSymbol(symbol)
+	if err != nil {
+		return err
+	}
+
+	symbolPart := parts[0]
+	// check len without suffix
+	if len(symbolPart) < MiniTokenSymbolMinLen {
+		return fmt.Errorf("mini-token symbol part is too short, got %d chars", len(symbolPart))
+	}
+	if len(symbolPart) > MiniTokenSymbolMaxLen {
+		return fmt.Errorf("mini-token symbol part is too long, got %d chars", len(symbolPart))
+	}
+
+	if !utils.IsAlphaNum(symbolPart) {
+		return errors.New("mini-token symbol part should be alphanumeric")
+	}
+
+	suffixPart := parts[1]
+	if len(suffixPart) != MiniTokenSymbolSuffixLen {
+		return fmt.Errorf("mini-token symbol suffix must be %d chars in length, got %d", MiniTokenSymbolSuffixLen, len(suffixPart))
+	}
+
+	if suffixPart[len(suffixPart)-1:] != MiniTokenSymbolMSuffix {
+		return fmt.Errorf("mini-token symbol suffix must end with M")
+	}
+
+	// prohibit non-hexadecimal chars in the suffix part
+	isHex, err := regexp.MatchString(fmt.Sprintf("[0-9A-F]{%d}M", MiniTokenSymbolTxHashSuffixLen), suffixPart)
+	if err != nil {
+		return err
+	}
+	if !isHex {
+		return fmt.Errorf("mini-token symbol tx hash suffix must be hex with a length of %d", MiniTokenSymbolTxHashSuffixLen)
+	}
+
+	return nil
+}
diff --git a/common/types/token.go b/common/types/token.go
index fb7ab19e9..a3c1ae974 100644
--- a/common/types/token.go
+++ b/common/types/token.go
@@ -26,6 +26,20 @@ const (
 	NativeTokenTotalSupply        = 2e16
 )
 
+type IToken interface {
+	GetName() string
+	GetSymbol() string
+	GetOrigSymbol() string
+	GetTotalSupply() utils.Fixed8
+	SetTotalSupply(totalSupply utils.Fixed8)
+	GetOwner() sdk.AccAddress
+	IsMintable() bool
+	IsOwner(addr sdk.AccAddress) bool
+	String() string
+}
+
+var _ IToken = &Token{}
+
 type Token struct {
 	Name        string         `json:"name"`
 	Symbol      string         `json:"symbol"`
@@ -35,9 +49,37 @@ type Token struct {
 	Mintable    bool           `json:"mintable"`
 }
 
+func (token Token) GetName() string {
+	return token.Name
+}
+
+func (token Token) GetSymbol() string {
+	return token.Symbol
+}
+
+func (token Token) GetOrigSymbol() string {
+	return token.OrigSymbol
+}
+
+func (token Token) GetTotalSupply() utils.Fixed8 {
+	return token.TotalSupply
+}
+
+func (token *Token) SetTotalSupply(totalSupply utils.Fixed8) {
+	token.TotalSupply = totalSupply
+}
+
+func (token Token) GetOwner() sdk.AccAddress {
+	return token.Owner
+}
+
+func (token Token) IsMintable() bool {
+	return token.Mintable
+}
+
 func NewToken(name, symbol string, totalSupply int64, owner sdk.AccAddress, mintable bool) (*Token, error) {
 	// double check that the symbol is suffixed
-	if err := ValidateMapperTokenSymbol(symbol); err != nil {
+	if err := ValidateTokenSymbol(symbol); err != nil {
 		return nil, err
 	}
 	parts, err := splitSuffixedTokenSymbol(symbol)
@@ -60,19 +102,7 @@ func (token Token) String() string {
 		token.Name, token.Symbol, token.TotalSupply, token.Owner, token.Mintable)
 }
 
-// Token Validation
-
-func ValidateToken(token Token) error {
-	if err := ValidateMapperTokenSymbol(token.Symbol); err != nil {
-		return err
-	}
-	if err := ValidateIssueMsgTokenSymbol(token.OrigSymbol); err != nil {
-		return err
-	}
-	return nil
-}
-
-func ValidateIssueMsgTokenSymbol(symbol string) error {
+func ValidateIssueSymbol(symbol string) error {
 	if len(symbol) == 0 {
 		return errors.New("token symbol cannot be empty")
 	}
@@ -93,7 +123,17 @@ func ValidateIssueMsgTokenSymbol(symbol string) error {
 	return nil
 }
 
-func ValidateMapperTokenSymbol(symbol string) error {
+func ValidateTokenSymbols(coins sdk.Coins) error {
+	for _, coin := range coins {
+		err := ValidateTokenSymbol(coin.Denom)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func ValidateTokenSymbol(symbol string) error {
 	if len(symbol) == 0 {
 		return errors.New("suffixed token symbol cannot be empty")
 	}
diff --git a/common/types/token_test.go b/common/types/token_test.go
index b5fb83353..f92ceaca6 100644
--- a/common/types/token_test.go
+++ b/common/types/token_test.go
@@ -79,30 +79,30 @@ func TestNewToken(t *testing.T) {
 	}
 }
 
-func TestValidateIssueMsgTokenSymbol(t *testing.T) {
+func TestValidateIssueSymbol(t *testing.T) {
 	for _, tt := range issueMsgSymbolTestCases {
 		t.Run(tt.symbol, func(t *testing.T) {
-			if err := types.ValidateIssueMsgTokenSymbol(tt.symbol); (err == nil) != tt.correct {
-				t.Errorf("ValidateIssueMsgTokenSymbol() error = %v, correct %v", err, tt.correct)
+			if err := types.ValidateIssueSymbol(tt.symbol); (err == nil) != tt.correct {
+				t.Errorf("ValidateIssueSymbol() error = %v, correct %v", err, tt.correct)
 			}
 		})
 	}
-	// extra test. an issued symbol that is valid in NewToken and ValidateMapperTokenSymbol but not here
-	if err := types.ValidateIssueMsgTokenSymbol("XYZ-000"); err == nil {
-		t.Errorf("ValidateIssueMsgTokenSymbol() error = %v, expected XYZ-000 to be invalid", err)
+	// extra test. an issued symbol that is valid in NewToken and ValidateTokenSymbol but not here
+	if err := types.ValidateIssueSymbol("XYZ-000"); err == nil {
+		t.Errorf("ValidateIssueSymbol() error = %v, expected XYZ-000 to be invalid", err)
 	}
 }
 
-func TestValidateMapperTokenSymbol(t *testing.T) {
+func TestValidateTokenSymbol(t *testing.T) {
 	for _, tt := range tokenMapperSymbolTestCases {
 		t.Run(tt.symbol, func(t *testing.T) {
-			if err := types.ValidateMapperTokenSymbol(tt.symbol); (err == nil) != tt.correct {
-				t.Errorf("ValidateMapperTokenSymbol() error = %v, correct %v", err, tt.correct)
+			if err := types.ValidateTokenSymbol(tt.symbol); (err == nil) != tt.correct {
+				t.Errorf("ValidateTokenSymbol() error = %v, correct %v", err, tt.correct)
 			}
 		})
 	}
-	// extra test. an issued symbol that is valid in ValidateIssueMsgTokenSymbol but not here
-	if err := types.ValidateMapperTokenSymbol("XYZ"); err == nil {
-		t.Errorf("ValidateIssueMsgTokenSymbol() error = %v, expected XYZ to be invalid", err)
+	// extra test. an issued symbol that is valid in ValidateIssueSymbol but not here
+	if err := types.ValidateTokenSymbol("XYZ"); err == nil {
+		t.Errorf("ValidateIssueSymbol() error = %v, expected XYZ to be invalid", err)
 	}
 }
diff --git a/common/types/wire.go b/common/types/wire.go
index 90e3161ae..7f5b56a25 100644
--- a/common/types/wire.go
+++ b/common/types/wire.go
@@ -10,7 +10,10 @@ func RegisterWire(cdc *wire.Codec) {
 	// Register AppAccount
 	cdc.RegisterInterface((*sdk.Account)(nil), nil)
 	cdc.RegisterInterface((*NamedAccount)(nil), nil)
+	cdc.RegisterInterface((*IToken)(nil), nil)
+
 	cdc.RegisterConcrete(&AppAccount{}, "bnbchain/Account", nil)
 
-	cdc.RegisterConcrete(Token{}, "bnbchain/Token", nil)
+	cdc.RegisterConcrete(&Token{}, "bnbchain/Token", nil)
+	cdc.RegisterConcrete(&MiniToken{}, "bnbchain/MiniToken", nil)
 }
diff --git a/common/upgrade/upgrade.go b/common/upgrade/upgrade.go
index c1ecba7de..41ac806bd 100644
--- a/common/upgrade/upgrade.go
+++ b/common/upgrade/upgrade.go
@@ -17,11 +17,17 @@ const (
 	BEP12 = "BEP12" // https://github.com/binance-chain/BEPs/pull/17
 	// Archimedes Upgrade
 	BEP3 = "BEP3" // https://github.com/binance-chain/BEPs/pull/30
+
 	// TODO: add upgrade name
 	FixSignBytesOverflow = sdk.FixSignBytesOverflow
 	LotSizeOptimization  = "LotSizeOptimization"
 	ListingRuleUpgrade   = "ListingRuleUpgrade" // Remove restriction that only the owner of base asset can list trading pair
 	FixZeroBalance       = "FixZeroBalance"
+
+	//Nightingale upgrade
+	BEP8  = sdk.BEP8 // https://github.com/binance-chain/BEPs/pull/69 Mini token upgrade
+	BEP67 = "BEP67"  // https://github.com/binance-chain/BEPs/pull/67 Expiry time upgrade
+	BEP70 = "BEP70"  // https://github.com/binance-chain/BEPs/pull/70 BUSD Pair Upgrade
 )
 
 func UpgradeBEP10(before func(), after func()) {
diff --git a/go.mod b/go.mod
index d35971827..4d256f838 100644
--- a/go.mod
+++ b/go.mod
@@ -29,9 +29,9 @@ require (
 )
 
 replace (
-	github.com/cosmos/cosmos-sdk => github.com/binance-chain/bnc-cosmos-sdk v0.25.0-binance.21
+	github.com/cosmos/cosmos-sdk => github.com/binance-chain/bnc-cosmos-sdk v0.25.0-binance.22
 	github.com/tendermint/go-amino => github.com/binance-chain/bnc-go-amino v0.14.1-binance.2
-	github.com/tendermint/iavl => github.com/binance-chain/bnc-tendermint-iavl v0.12.0-binance.3
+	github.com/tendermint/iavl => github.com/binance-chain/bnc-tendermint-iavl v0.12.0-binance.4
 	github.com/tendermint/tendermint => github.com/binance-chain/bnc-tendermint v0.32.3-binance.1
 	github.com/zondax/ledger-cosmos-go => github.com/binance-chain/ledger-cosmos-go v0.9.9-binance.3
 	golang.org/x/crypto => github.com/tendermint/crypto v0.0.0-20190823143015-45b1026d81ae
diff --git a/go.sum b/go.sum
index ad02d1a4a..bfdcf35ae 100644
--- a/go.sum
+++ b/go.sum
@@ -26,14 +26,14 @@ github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
-github.com/binance-chain/bnc-cosmos-sdk v0.25.0-binance.21 h1:I/AMgDlXPWeZsCaCh0fqgvwkOI0YTpQEsRhC5+VECCU=
-github.com/binance-chain/bnc-cosmos-sdk v0.25.0-binance.21/go.mod h1:Qd+w7Vm2WGxOqPquawShZ8KsHtA8PVDnx6XvZobloYg=
+github.com/binance-chain/bnc-cosmos-sdk v0.25.0-binance.22 h1:AYJZDs9H3DN/tETOMtRQOpMv2JVxCpxhciSttofQJY0=
+github.com/binance-chain/bnc-cosmos-sdk v0.25.0-binance.22/go.mod h1:lE+RVRYNWqO8xUa/SjiqyDGiII5BtjMSwovI/lcbSuM=
 github.com/binance-chain/bnc-go-amino v0.14.1-binance.2 h1:XcbcfisVItk92UKoGbtNT8nbcfadj3H3ayuM2srAfVs=
 github.com/binance-chain/bnc-go-amino v0.14.1-binance.2/go.mod h1:yaElUUxWtv/TC/ldGtlKAvS1vKwokxgJ1d97I+6is80=
 github.com/binance-chain/bnc-tendermint v0.32.3-binance.1 h1:LDGvORYLSwsTEQM0W7yrbdgjrAZxQDe18WUTLNuFOEc=
 github.com/binance-chain/bnc-tendermint v0.32.3-binance.1/go.mod h1:kN5dNxE8voFtDqx2HjbM8sBIH5cUuMtLg0XEHjqzUiY=
-github.com/binance-chain/bnc-tendermint-iavl v0.12.0-binance.3 h1:OyTJet9aGz+c2WKpTf5cAvJNiQeqVFYP4AV9cPpro2M=
-github.com/binance-chain/bnc-tendermint-iavl v0.12.0-binance.3/go.mod h1:Zmh8GRdNJB8DULIOBar3JCZp6tSpcvM1NGKfE9U2EzA=
+github.com/binance-chain/bnc-tendermint-iavl v0.12.0-binance.4 h1:BhaV2iiGWfRC6iB8HHOYJeUDwtQMB2pUA4ah+KCbBhI=
+github.com/binance-chain/bnc-tendermint-iavl v0.12.0-binance.4/go.mod h1:Zmh8GRdNJB8DULIOBar3JCZp6tSpcvM1NGKfE9U2EzA=
 github.com/binance-chain/ledger-cosmos-go v0.9.9-binance.3 h1:FFpFbkzlP2HUyxQCm0eoU6mkfgMNynfqZRbeWqlaLdQ=
 github.com/binance-chain/ledger-cosmos-go v0.9.9-binance.3/go.mod h1:TULULYTvPuWBxFIZFy6KjJaxJzbHeUderYNB1YhD6N0=
 github.com/binance-chain/tss v0.1.2 h1:AyTedSG5HG/WAvM9PDPWjTXQ+dvNdHg3x1c+1a584PQ=
diff --git a/integration_test.sh b/integration_test.sh
index d147954a2..84d49dc0b 100755
--- a/integration_test.sh
+++ b/integration_test.sh
@@ -75,7 +75,6 @@ bob_addr=$(./bnbcli keys list --home ${cli_home} | grep bob | grep -o "bnb1[0-9a
 # wait for the chain
 sleep 10s
 
-
 ## ROUND 1 ##
 
 # send
@@ -320,4 +319,63 @@ sleep 1s
 result=$(expect ./deposit.exp ${swapID} "10000:${eth_symbol}" bob ${chain_id}  ${cli_home})
 check_operation "Deposit to a deposited single chain atomic swap" "${result}" "ERROR"
 
+
+## ROUND 5 ##
+
+sleep 1s
+# issue token
+result=$(expect ./issue_mini.exp MBC MiniBitcoin 900000000000 true alice ${chain_id} ${cli_home} 1 sample)
+mbc_symbol=$(echo "${result}" | tail -n 1 | grep -o "MBC-[0-9A-Z]*M")
+check_operation "Issue Mini Token" "${result}" "${chain_operation_words}"
+
+
+sleep 1s
+# send
+result=$(expect ./send.exp ${cli_home} alice ${chain_id} "100000000000:${mbc_symbol}" ${bob_addr} 1)
+check_operation "Send Token" "${result}" "${chain_operation_words}"
+
+sleep 1s
+# multi send
+echo ${bob_addr}
+result=$(expect ./multi_send.exp ${cli_home} alice ${chain_id} "[{\"to\":\"${bob_addr}\",\"amount\":\"10000000000:${mbc_symbol}\"},{\"to\":\"${alice_addr}\",\"amount\":\"1000000000:${mbc_symbol}\"}]" 1)
+check_operation "Multi Send Token" "${result}" "${chain_operation_words}"
+
+sleep 1s
+# mint token
+result=$(expect ./mint.exp ${mbc_symbol} 10000000000 alice ${chain_id} ${cli_home})
+check_operation "Mint Token" "${result}" "${chain_operation_words}"
+
+sleep 3s
+# list trading pair
+result=$(expect ./list_mini.exp ${mbc_symbol} BNB 100000000 alice ${chain_id} ${cli_home})
+check_operation "List Trading Pair" "${result}" "${chain_operation_words}"
+
+sleep 1s
+# place buy order
+result=$(expect ./order.exp ${mbc_symbol}_BNB 1 100000000 1000000000 bob ${chain_id} gte ${cli_home})
+check_operation "Place Order" "${result}" "${chain_operation_words}"
+order_id=$(echo "${result}" | tail -n 1 | grep -o "[0-9A-Z]\{4,\}-[0-9]*") # capture order id, not symbol
+printf "Order ID: $order_id\n"
+
+sleep 2s
+# cancel order
+result=$(expect ./cancel.exp "${mbc_symbol}_BNB" "${order_id}" bob ${chain_id} ${cli_home})
+check_operation "Cancel Order" "${result}" "${chain_operation_words}"
+
+sleep 1s
+# place buy order
+result=$(expect ./order.exp ${mbc_symbol}_BNB 1 100000000 1000000000 bob ${chain_id} gte ${cli_home})
+check_operation "Place Order" "${result}" "${chain_operation_words}"
+
+echo ""
+./bnbcli dex show -l ${mbc_symbol}_BNB  --trust-node true
+
+sleep 1s
+# place Sell order
+result=$(expect ./order.exp ${mbc_symbol}_BNB 2 100000000 2000000000 alice ${chain_id} gte ${cli_home})
+check_operation "Place Order" "${result}" "${chain_operation_words}"
+
+result=$(./bnbcli dex show -l ${mbc_symbol}_BNB  --trust-node true)
+check_operation "Order Book" "${result}" "${order_book_words}"
+
 exit_test 0
diff --git a/networks/demo/issue_mini.exp b/networks/demo/issue_mini.exp
new file mode 100755
index 000000000..39a86f5b3
--- /dev/null
+++ b/networks/demo/issue_mini.exp
@@ -0,0 +1,21 @@
+#!/usr/bin/expect
+
+set symbol [lindex $argv 0]
+set token_name [lindex $argv 1]
+set supply [lindex $argv 2]
+set mintable [lindex $argv 3]
+set from [lindex $argv 4]
+set chain_id [lindex $argv 5]
+set home [lindex $argv 6]
+set type [lindex $argv 7]
+set uri [lindex $argv 8]
+
+set timeout 30
+	if {"${home}" == ""} {
+		spawn ./bnbcli token issue-mini -s $symbol --token-name $token_name -n $supply --mintable $mintable --from $from --token-uri $uri --chain-id $chain_id
+	} else {
+		spawn ./bnbcli token issue-mini --home $home -s $symbol --token-name $token_name -n $supply --mintable $mintable --from $from --token-uri $uri --chain-id $chain_id
+	}
+	expect "Password*"
+	send "12345678\r"
+interact
diff --git a/networks/demo/list_mini.exp b/networks/demo/list_mini.exp
new file mode 100755
index 000000000..58cf6f515
--- /dev/null
+++ b/networks/demo/list_mini.exp
@@ -0,0 +1,19 @@
+#!/usr/bin/expect
+
+set symbol [lindex $argv 0]
+set quote_symbol [lindex $argv 1]
+set init_price [lindex $argv 2]
+set from [lindex $argv 3]
+set chain_id [lindex $argv 4]
+set home [lindex $argv 5]
+set proposal_id [lindex $argv 6]
+
+set timeout 30
+	if {"${home}" == ""} {
+		spawn ./bnbcli dex list-mini -s $symbol --quote-asset-symbol $quote_symbol --from $from --init-price $init_price --chain-id $chain_id
+	} else {
+		spawn ./bnbcli dex list-mini --home $home -s $symbol --quote-asset-symbol $quote_symbol --from $from --init-price $init_price --chain-id $chain_id
+	}
+	expect "Password*"
+	send "12345678\r"
+interact
diff --git a/networks/demo/multi_send.exp b/networks/demo/multi_send.exp
index 3f634f171..4e62bf174 100755
--- a/networks/demo/multi_send.exp
+++ b/networks/demo/multi_send.exp
@@ -4,9 +4,10 @@ set home [lindex $argv 0]
 set from [lindex $argv 1]
 set chain_id [lindex $argv 2]
 set tx [lindex $argv 3]
+set memo [lindex $argv 4]
 
 set timeout 30
-	spawn ./bnbcli token multi-send --home $home --from $from --chain-id=$chain_id --transfers $tx
+	spawn ./bnbcli token multi-send --home $home --from $from --chain-id=$chain_id --transfers $tx --memo $memo
 	expect "Password*"
 	send "12345678\r"
 interact
diff --git a/networks/publisher/list_mini.sh b/networks/publisher/list_mini.sh
new file mode 100755
index 000000000..c918511a0
--- /dev/null
+++ b/networks/publisher/list_mini.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+
+# start a validator and witness on local machine
+# later on db indexer and publisher can be setup and started by newIndexer.sh and newPublisher.sh
+# later on order generation can be kicked off by ordergen.sh
+# TODO: support two validator without docker in same machine
+
+########################### SETUP #########################
+home=$HOME
+src="${home}/go/src/github.com/binance-chain/node"
+deamonhome="${home}/.bnbchaind"
+witnesshome="${home}/.bnbchaind_witness"
+clihome="${home}/.bnbcli"
+chain_id='test-chain-n4b735'
+echo $src
+echo $deamonhome
+echo $witnesshome
+echo $clihome
+
+key_seed_path="${home}"
+executable="${src}/build/bnbchaind"
+clipath="${src}/build/bnbcli"
+cli="${clipath} --home ${clihome}"
+scripthome="${src}/networks/publisher"
+############################ END ##########################
+
+#X1Mini_symbol="X1Mini-ED3"
+result=$(${cli} token issue-tiny --from=zc --token-name="X1M Coin" --symbol=X1M --total-supply=800000000000 --chain-id ${chain_id})
+X1Mini_symbol=$(echo "${result}" | tail -n 1 | grep -o "X1M-[0-9A-Z]*")
+echo ${X1Mini_symbol}
+sleep 2
+echo 1234qwerasdf|${cli} dex list-mini -s=${X1Mini_symbol} --quote-asset-symbol=BNB --init-price=100000000 --from=zc --chain-id ${chain_id}
+sleep 1
+zz_addr=$(${cli} keys list | grep "zz.*local" | grep -o "bnb[0-9a-zA-Z]*" | grep -v "bnbp")
+echo 1234qwerasdf|${cli} send --from=zc --to=${zz_addr} --amount=200000000000:${X1Mini_symbol} --chain-id ${chain_id}
+sleep 5
diff --git a/networks/publisher/newIndexer.sh b/networks/publisher/newIndexer.sh
index 043af5ece..307279218 100755
--- a/networks/publisher/newIndexer.sh
+++ b/networks/publisher/newIndexer.sh
@@ -1,12 +1,16 @@
 #!/usr/bin/env bash
 
 ########################### SETUP #########################
-src='/Users/zhaocong/go/src/github.com/binance-chain/node'
-home='/Users/zhaocong'
-deamonhome='/Users/zhaocong/.bnbchaind'
-witnesshome='/Users/zhaocong/.bnbchaind_indexer'
-clihome='/Users/zhaocong/.bnbcli'
+home=$HOME
+src="${home}/go/src/github.com/binance-chain/node"
+deamonhome="${home}/.bnbchaind"
+witnesshome="${home}/.bnbchaind_witness"
+clihome="${home}/.bnbcli"
 chain_id='test-chain-n4b735'
+echo $src
+echo $deamonhome
+echo $witnesshome
+echo $clihome
 
 key_seed_path="${home}"
 executable="${src}/build/bnbchaind"
diff --git a/networks/publisher/newPublisher.sh b/networks/publisher/newPublisher.sh
index 951bfa248..f70da490b 100755
--- a/networks/publisher/newPublisher.sh
+++ b/networks/publisher/newPublisher.sh
@@ -1,12 +1,16 @@
 #!/usr/bin/env bash
 
 ########################### SETUP #########################
-src='/Users/zhaocong/go/src/github.com/binance-chain/node'
-home='/Users/zhaocong'
-deamonhome='/Users/zhaocong/.bnbchaind'
-witnesshome='/Users/zhaocong/.bnbchaind_publisher'
-clihome='/Users/zhaocong/.bnbcli'
+home=$HOME
+src="${home}/go/src/github.com/binance-chain/node"
+deamonhome="${home}/.bnbchaind"
+witnesshome="${home}/.bnbchaind_publisher"
+clihome="${home}/.bnbcli"
 chain_id='test-chain-n4b735'
+echo $src
+echo $deamonhome
+echo $witnesshome
+echo $clihome
 
 key_seed_path="${home}"
 executable="${src}/build/bnbchaind"
diff --git a/networks/publisher/newValidator.sh b/networks/publisher/newValidator.sh
index 8bdcca4b4..a4915fdd5 100755
--- a/networks/publisher/newValidator.sh
+++ b/networks/publisher/newValidator.sh
@@ -1,9 +1,9 @@
 #!/usr/bin/env bash
 
-src='/Users/zhaocong/go/src/github.com/binance-chain/node'
-executable='/Users/zhaocong/go/src/github.com/binance-chain/node/build/bnbchaind'
-cli='/Users/zhaocong/go/src/github.com/binance-chain/node/build/bnbcli'
-home='/Users/zhaocong'
+home=$HOME
+src="${home}/go/src/github.com/binance-chain/node"
+executable="${home}/go/src/github.com/binance-chain/node/build/bnbchaind"
+cli="${home}/go/src/github.com/binance-chain/node/build/bnbcli"
 
 ## clean history data
 #rm -r ${home}/.bnbchaind_val2
diff --git a/networks/publisher/ordergen.sh b/networks/publisher/ordergen.sh
index ea9ffb8bc..d755ff4f0 100755
--- a/networks/publisher/ordergen.sh
+++ b/networks/publisher/ordergen.sh
@@ -1,11 +1,12 @@
 #!/bin/bash
 
 ########################### SETUP #########################
-clipath='/Users/zhaocong/go/src/github.com/binance-chain/node/build/bnbcli'
-home='/Users/zhaocong'
-clihome='/Users/zhaocong/.bnbcli'
+home=$HOME
+src="${home}/go/src/github.com/binance-chain/node"
+clipath="${home}/go/src/github.com/binance-chain/node/build/bnbcli"
+clihome="${home}/.bnbcli"
 chainId='test-chain-n4b735' # should be same with publisher/setup.sh or testnet/deploy.sh
-src='/Users/zhaocong/go/src/github.com/binance-chain/node'
+
 
 cli="${clipath} --home ${clihome}"
 scripthome="${src}/networks/publisher"
@@ -38,17 +39,21 @@ function random()
 while :
 do
     side=$(random 1 2)
-    price=$(random 1 20)
-    qty=$(random 10 20)
+    price=$(random 1 4)
+    qty=$(random 1 2)
     pause=$(random 5 7)
     symbolNum=$(random 1 10)
 
-    symbol="NNB-FCA_BNB"
+    symbol="ZCK-980_BNB"
     if [ $symbolNum -lt 4 ]
     then
-        symbol="NNB-FCA_BNB"
+        symbol="Y2B-822M_BNB"
+    elif [ $symbolNum -lt 6 ]
+    then
+        symbol="X1M-42FM_BNB"
+    else [ $symbolNum -lt 8 ]
+        symbol="ZCK-CD6_BNB"
     fi
-
     from="zc"
     if [ $side == 1 ]
     then
@@ -57,11 +62,11 @@ do
 
     printf "\n${cli} dex order --symbol=${symbol} --side=${side} --price=${price}00000000 --qty=${qty}00000000 --tif="GTE" --from=${from} --chain-id=${chainId}\n"
 
-    ${cli} dex order --symbol=${symbol} --side=${side} --price=${price}00000000 --qty=${qty}00000000 --tif="GTE" --from=${from} --chain-id=${chainId}
+    echo 1234qwerasdf|${cli} dex order --symbol=${symbol} --side=${side} --price=${price}00000000 --qty=${qty}00000000 --tif="GTE" --from=${from} --chain-id=${chainId}
 
     # -d is used for get response of expect script. TODO: better log redirection
-#    result=$(expect -d ${scripthome}/ordergen.exp "${clipath}" "${clihome}" "${symbol}" "${side}" "${price}00000000" "${qty}00000000" "${from}" "${chainId}")
+    #result=$(expect -d ${scripthome}/ordergen.exp "${clipath}" "${clihome}" "${symbol}" "${side}" "${price}00000000" "${qty}00000000" "${from}" "${chainId}")
 
-#    printf "\nsleep ${pause} seconds...\n"
-#    sleep ${pause}
+    #printf "\nsleep ${pause} seconds...\n"
+    sleep ${pause}
 done
\ No newline at end of file
diff --git a/networks/publisher/setup.sh b/networks/publisher/setup.sh
index 12fe03445..a859b98c7 100755
--- a/networks/publisher/setup.sh
+++ b/networks/publisher/setup.sh
@@ -6,12 +6,16 @@
 # TODO: support two validator without docker in same machine
 
 ########################### SETUP #########################
-src='/Users/zhaocong/go/src/github.com/binance-chain/node'
-home='/Users/zhaocong'
-deamonhome='/Users/zhaocong/.bnbchaind'
-witnesshome='/Users/zhaocong/.bnbchaind_witness'
-clihome='/Users/zhaocong/.bnbcli'
+home=$HOME
+src="${home}/go/src/github.com/binance-chain/node"
+deamonhome="${home}/.bnbchaind"
+witnesshome="${home}/.bnbchaind_witness"
+clihome="${home}/.bnbcli"
 chain_id='test-chain-n4b735'
+echo $src
+echo $deamonhome
+echo $witnesshome
+echo $clihome
 
 key_seed_path="${home}"
 executable="${src}/build/bnbchaind"
@@ -40,6 +44,8 @@ mkdir -p ${home}/.bnbchaind_witness/config
 sed -i -e "s/log_level = \"main:info,state:info,\*:error\"/log_level = \"debug\"/g" ${deamonhome}/config/config.toml
 sed -i -e "s/allow_duplicate_ip = false/allow_duplicate_ip = true/g" ${deamonhome}/config/config.toml
 sed -i -e "s/addr_book_strict = true/addr_book_strict = false/g" ${deamonhome}/config/config.toml
+# for http-ap testing
+sed -i -e "s/index_tags = \"\"/index_tags = \"tx.height\"/g" ${deamonhome}/config/config.toml
 
 sed -i -e 's/logToConsole = true/logToConsole = false/g' ${deamonhome}/config/app.toml
 sed -i -e 's/breatheBlockInterval = 0/breatheBlockInterval = 100/g' ${deamonhome}/config/app.toml
diff --git a/networks/publisher/setup_mini.sh b/networks/publisher/setup_mini.sh
new file mode 100755
index 000000000..ba56770b4
--- /dev/null
+++ b/networks/publisher/setup_mini.sh
@@ -0,0 +1,114 @@
+#!/usr/bin/env bash
+
+# start a validator and witness on local machine
+# later on db indexer and publisher can be setup and started by newIndexer.sh and newPublisher.sh
+# later on order generation can be kicked off by ordergen.sh
+# TODO: support two validator without docker in same machine
+
+########################### SETUP #########################
+home=$HOME
+src="${home}/go/src/github.com/binance-chain/node"
+deamonhome="${home}/.bnbchaind"
+witnesshome="${home}/.bnbchaind_witness"
+clihome="${home}/.bnbcli"
+chain_id='test-chain-n4b735'
+echo $src
+echo $deamonhome
+echo $witnesshome
+echo $clihome
+
+key_seed_path="${home}"
+executable="${src}/build/bnbchaind"
+clipath="${src}/build/bnbcli"
+cli="${clipath} --home ${clihome}"
+scripthome="${src}/networks/publisher"
+############################ END ##########################
+
+# clean history data
+rm -r ${deamonhome}
+rm -r ${clihome}
+rm -r ${home}/.bnbchaind_witness
+
+# build
+cd ${src}
+make build
+
+# init a validator and witness node
+${executable} init --moniker xxx --chain-id ${chain_id} > ${key_seed_path}/key_seed.json # cannot save into ${deamonhome}/init.json
+secret=$(cat ${key_seed_path}/key_seed.json | grep secret | grep -o ":.*" | grep -o "\".*"  | sed "s/\"//g")
+#echo ${secret}
+
+mkdir -p ${home}/.bnbchaind_witness/config
+
+#sed -i -e "s/skip_timeout_commit = false/skip_timeout_commit = true/g" ${deamonhome}/config/config.toml
+sed -i -e "s/log_level = \"main:info,state:info,\*:error\"/log_level = \"debug\"/g" ${deamonhome}/config/config.toml
+sed -i -e "s/allow_duplicate_ip = false/allow_duplicate_ip = true/g" ${deamonhome}/config/config.toml
+sed -i -e "s/addr_book_strict = true/addr_book_strict = false/g" ${deamonhome}/config/config.toml
+# for http-ap testing
+sed -i -e "s/index_tags = \"\"/index_tags = \"tx.height\"/g" ${deamonhome}/config/config.toml
+
+sed -i -e 's/logToConsole = true/logToConsole = false/g' ${deamonhome}/config/app.toml
+sed -i -e 's/breatheBlockInterval = 0/breatheBlockInterval = 100/g' ${deamonhome}/config/app.toml
+sed -i -e "s/publishOrderUpdates = false/publishOrderUpdates = true/g" ${deamonhome}/config/app.toml
+sed -i -e "s/publishAccountBalance = false/publishAccountBalance = true/g" ${deamonhome}/config/app.toml
+sed -i -e "s/publishOrderBook = false/publishOrderBook = true/g" ${deamonhome}/config/app.toml
+sed -i -e "s/publishBlockFee = false/publishBlockFee = true/g" ${deamonhome}/config/app.toml
+sed -i -e "s/publishTransfer = false/publishTransfer = true/g" ${deamonhome}/config/app.toml
+sed -i -e "s/publishLocal = false/publishLocal = true/g" ${deamonhome}/config/app.toml
+
+# config witness node
+cp ${deamonhome}/config/genesis.json ${witnesshome}/config/
+cp ${deamonhome}/config/app.toml ${witnesshome}/config/
+cp ${deamonhome}/config/config.toml ${witnesshome}/config/
+
+sed -i -e "s/26/27/g" ${witnesshome}/config/config.toml
+sed -i -e "s/6060/7060/g" ${witnesshome}/config/config.toml
+#sed -i -e "s/fastest_sync_height = -1/fastest_sync_height = 10/g" ${witnesshome}/config/config.toml
+
+# start validator
+${executable} start --pruning breathe > ${deamonhome}/log.txt 2>&1 &
+validator_pid=$!
+echo ${validator_pid}
+sleep 20 # sleep in case cli status call failed to get node id
+validatorStatus=$(${cli} status)
+validator_id=$(echo ${validatorStatus} | grep -o "\"id\":\"[a-zA-Z0-9]*\"" | sed "s/\"//g" | sed "s/id://g")
+#echo ${validator_id}
+
+# set witness peer to validator and start witness
+sed -i -e "s/persistent_peers = \"\"/persistent_peers = \"${validator_id}@127.0.0.1:26656\"/g" ${witnesshome}/config/config.toml
+sed -i -e "s/state_sync_height = -1/state_sync_height = 0/g" ${witnesshome}/config/config.toml
+${executable} start --pruning breathe --home ${witnesshome} > ${witnesshome}/log.txt 2>&1 &
+witness_pid=$!
+echo ${witness_pid}
+
+# init accounts
+result=$(expect ${scripthome}/recover.exp "${secret}" "zc" "${clipath}" "${clihome}")
+result=$(expect ${scripthome}/add_key.exp "zz" "${clipath}" "${clihome}")
+zz_addr=$(${cli} keys list | grep "zz.*local" | grep -o "bnb[0-9a-zA-Z]*" | grep -v "bnbp")
+
+sleep 5
+## issue&list NNB and ZCB for ordergen
+#result=$(${cli} token issue --from=zc --token-name="New BNB Coin" --symbol=NNB --total-supply=2000000000000000 --chain-id ${chain_id})
+#nnb_symbol=$(echo "${result}" | tail -n 1 | grep -o "NNB-[0-9A-Z]*")
+#echo ${nnb_symbol}
+#sleep 5
+#((expire_time=$(date '+%s')+1000))
+#${cli} gov submit-list-proposal --chain-id ${chain_id} --from zc --deposit 200000000000:BNB --base-asset-symbol ${nnb_symbol} --quote-asset-symbol BNB --init-price 1000000000 --title "list NNB/BNB" --description "list NNB/BNB" --expire-time ${expire_time} --voting-period 8 --json
+#sleep 2
+#${cli} gov vote --from zc --chain-id ${chain_id} --proposal-id 1 --option Yes --json
+#sleep 7
+#${cli} dex list -s=${nnb_symbol} --quote-asset-symbol=BNB --init-price=1000000000 --from=zc --chain-id ${chain_id} --proposal-id 1
+#sleep 1
+#result=$(${cli} token issue --from=zc --token-name="ZC Coin" --symbol=ZCB --total-supply=2000000000000000 --chain-id ${chain_id})
+#zcb_symbol=$(echo "${result}" | tail -n 1 | grep -o "ZCB-[0-9A-Z]*")
+#echo ${zcb_symbol}
+#sleep 5
+#((expire_time=$(date '+%s')+1000))
+#${cli} gov submit-list-proposal --chain-id ${chain_id} --from zc --deposit 200000000000:BNB --base-asset-symbol ${zcb_symbol} --quote-asset-symbol BNB --init-price 1000000000 --title "list NNB/BNB" --description "list NNB/BNB" --expire-time ${expire_time} --voting-period 5 --json
+#sleep 2
+#${cli} gov vote --from zc --chain-id ${chain_id} --proposal-id 2 --option Yes --json
+#sleep 6
+#${cli} dex list -s=${zcb_symbol} --quote-asset-symbol=BNB --init-price=1000000000 --from=zc --chain-id ${chain_id} --proposal-id 2
+#sleep 1
+${cli} send --from=zc --to=${zz_addr} --amount=1000000000000000:BNB --chain-id ${chain_id}
+#sleep 5
diff --git a/networks/tools/snapshot_viewer/snapshot.go b/networks/tools/snapshot_viewer/snapshot.go
index a79df3eb8..9b7a098c5 100644
--- a/networks/tools/snapshot_viewer/snapshot.go
+++ b/networks/tools/snapshot_viewer/snapshot.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/cosmos/cosmos-sdk/store"
 	sdk "github.com/cosmos/cosmos-sdk/types"
+
 	"github.com/tendermint/go-amino"
 	cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino"
 	"github.com/tendermint/tendermint/libs/db"
diff --git a/plugins/api/handlers.go b/plugins/api/handlers.go
index 7757c2403..45c8ad6b3 100644
--- a/plugins/api/handlers.go
+++ b/plugins/api/handlers.go
@@ -7,6 +7,7 @@ import (
 	"github.com/cosmos/cosmos-sdk/client/context"
 
 	hnd "github.com/binance-chain/node/plugins/api/handlers"
+	"github.com/binance-chain/node/plugins/dex"
 	dexapi "github.com/binance-chain/node/plugins/dex/client/rest"
 	paramapi "github.com/binance-chain/node/plugins/param/client/rest"
 	tksapi "github.com/binance-chain/node/plugins/tokens/client/rest"
@@ -94,8 +95,12 @@ func (s *server) handleSimulateReq(cdc *wire.Codec, ctx context.CLIContext) http
 	return s.withTextPlainForm(s.limitReqSize(h))
 }
 
-func (s *server) handlePairsReq(cdc *wire.Codec, ctx context.CLIContext) http.HandlerFunc {
-	return dexapi.GetPairsReqHandler(cdc, ctx)
+func (s *server) handleBEP2PairsReq(cdc *wire.Codec, ctx context.CLIContext) http.HandlerFunc {
+	return dexapi.GetPairsReqHandler(cdc, ctx, dex.DexAbciQueryPrefix)
+}
+
+func (s *server) handleMiniPairsReq(cdc *wire.Codec, ctx context.CLIContext) http.HandlerFunc {
+	return dexapi.GetPairsReqHandler(cdc, ctx, dex.DexMiniAbciQueryPrefix)
 }
 
 func (s *server) handleDexDepthReq(cdc *wire.Codec, ctx context.CLIContext) http.HandlerFunc {
@@ -112,11 +117,19 @@ func (s *server) handleDexOpenOrdersReq(cdc *wire.Codec, ctx context.CLIContext)
 }
 
 func (s *server) handleTokenReq(cdc *wire.Codec, ctx context.CLIContext) http.HandlerFunc {
-	return tksapi.GetTokenReqHandler(cdc, ctx)
+	return tksapi.GetTokenReqHandler(cdc, ctx, false)
 }
 
 func (s *server) handleTokensReq(cdc *wire.Codec, ctx context.CLIContext) http.HandlerFunc {
-	return tksapi.GetTokensReqHandler(cdc, ctx)
+	return tksapi.GetTokensReqHandler(cdc, ctx, false)
+}
+
+func (s *server) handleMiniTokenReq(cdc *wire.Codec, ctx context.CLIContext) http.HandlerFunc {
+	return tksapi.GetTokenReqHandler(cdc, ctx, true)
+}
+
+func (s *server) handleMiniTokensReq(cdc *wire.Codec, ctx context.CLIContext) http.HandlerFunc {
+	return tksapi.GetTokensReqHandler(cdc, ctx, true)
 }
 
 func (s *server) handleBalancesReq(cdc *wire.Codec, ctx context.CLIContext, tokens tkstore.Mapper) http.HandlerFunc {
diff --git a/plugins/api/routes.go b/plugins/api/routes.go
index 70b0c8874..8e259ab6e 100644
--- a/plugins/api/routes.go
+++ b/plugins/api/routes.go
@@ -29,7 +29,7 @@ func (s *server) bindRoutes() *server {
 		Methods("POST")
 
 	// dex routes
-	r.HandleFunc(prefix+"/markets", s.handlePairsReq(s.cdc, s.ctx)).
+	r.HandleFunc(prefix+"/markets", s.handleBEP2PairsReq(s.cdc, s.ctx)).
 		Methods("GET")
 	r.HandleFunc(prefix+"/depth", s.handleDexDepthReq(s.cdc, s.ctx)).
 		Queries("symbol", "{symbol}", "limit", "{limit:[0-9]+}").
@@ -44,6 +44,9 @@ func (s *server) bindRoutes() *server {
 		Queries("address", "{address}", "symbol", "{symbol}").
 		Methods("GET")
 
+	r.HandleFunc(prefix+"/mini/markets", s.handleMiniPairsReq(s.cdc, s.ctx)).
+		Methods("GET")
+
 	// tokens routes
 	r.HandleFunc(prefix+"/tokens", s.handleTokensReq(s.cdc, s.ctx)).
 		Methods("GET")
@@ -54,6 +57,12 @@ func (s *server) bindRoutes() *server {
 	r.HandleFunc(prefix+"/balances/{address}/{symbol}", s.handleBalanceReq(s.cdc, s.ctx, s.tokens)).
 		Methods("GET")
 
+	// mini tokens routes
+	r.HandleFunc(prefix+"/mini/tokens", s.handleMiniTokensReq(s.cdc, s.ctx)).
+		Methods("GET")
+	r.HandleFunc(prefix+"/mini/tokens/{symbol}", s.handleMiniTokenReq(s.cdc, s.ctx)).
+		Methods("GET")
+
 	// fee params
 	r.HandleFunc(prefix+"/fees", s.handleFeesParamReq(s.cdc, s.ctx)).
 		Methods("GET")
diff --git a/plugins/api/server.go b/plugins/api/server.go
index a9f59876f..64c6a0810 100644
--- a/plugins/api/server.go
+++ b/plugins/api/server.go
@@ -5,10 +5,10 @@ import (
 
 	"github.com/cosmos/cosmos-sdk/client/context"
 	keyscli "github.com/cosmos/cosmos-sdk/client/keys"
-	keys "github.com/cosmos/cosmos-sdk/crypto/keys"
+	"github.com/cosmos/cosmos-sdk/crypto/keys"
 
 	"github.com/binance-chain/node/common"
-	tkstore "github.com/binance-chain/node/plugins/tokens/store"
+	"github.com/binance-chain/node/plugins/tokens"
 	"github.com/binance-chain/node/wire"
 )
 
@@ -27,7 +27,7 @@ type server struct {
 
 	// stores for handlers
 	keyBase keys.Keybase
-	tokens  tkstore.Mapper
+	tokens  tokens.Mapper
 
 	accStoreName string
 }
@@ -45,7 +45,7 @@ func newServer(ctx context.CLIContext, cdc *wire.Codec) *server {
 		ctx:          ctx,
 		cdc:          cdc,
 		keyBase:      kb,
-		tokens:       tkstore.NewMapper(cdc, common.TokenStoreKey),
+		tokens:       tokens.NewMapper(cdc, common.TokenStoreKey),
 		accStoreName: common.AccountStoreName,
 	}
 }
diff --git a/plugins/dex/abci.go b/plugins/dex/abci.go
index 00616c057..76bbc9173 100644
--- a/plugins/dex/abci.go
+++ b/plugins/dex/abci.go
@@ -9,6 +9,7 @@ import (
 	sdk "github.com/cosmos/cosmos-sdk/types"
 
 	app "github.com/binance-chain/node/common/types"
+	"github.com/binance-chain/node/plugins/dex/order"
 	"github.com/binance-chain/node/plugins/dex/store"
 	"github.com/binance-chain/node/plugins/dex/types"
 	"github.com/binance-chain/node/plugins/dex/utils"
@@ -18,24 +19,25 @@ import (
 const MaxDepthLevels = 1000    // matches UI requirement
 const DefaultDepthLevels = 100 // matches UI requirement
 
-func createAbciQueryHandler(keeper *DexKeeper) app.AbciQueryHandler {
+func createAbciQueryHandler(keeper *DexKeeper, abciQueryPrefix string) app.AbciQueryHandler {
+	queryPrefix := abciQueryPrefix
 	return func(app app.ChainApp, req abci.RequestQuery, path []string) (res *abci.ResponseQuery) {
 		// expects at least two query path segments.
-		if path[0] != AbciQueryPrefix || len(path) < 2 {
+		if path[0] != queryPrefix || len(path) < 2 {
 			return nil
 		}
 		switch path[1] {
-		case "pairs": // args: ["dex", "pairs", <offset>, <limit>]
+		case "pairs": // args: ["dex" or "dex-mini", "pairs", <offset>, <limit>]
 			if len(path) < 4 {
 				return &abci.ResponseQuery{
 					Code: uint32(sdk.CodeUnknownRequest),
 					Log: fmt.Sprintf(
 						"%s %s query requires offset and limit in the path",
-						AbciQueryPrefix, path[1]),
+						queryPrefix, path[1]),
 				}
 			}
 			ctx := app.GetContextForCheckState()
-			pairs := keeper.PairMapper.ListAllTradingPairs(ctx)
+			pairs := listPairs(keeper, ctx, queryPrefix)
 			var offset, limit, end int
 			var err error
 			if pairs == nil || len(pairs) == 0 {
@@ -81,6 +83,14 @@ func createAbciQueryHandler(keeper *DexKeeper) app.AbciQueryHandler {
 				Value: bz,
 			}
 		case "orderbook": // args: ["dex", "orderbook"]
+			if queryPrefix == DexMiniAbciQueryPrefix {
+				return &abci.ResponseQuery{
+					Code: uint32(sdk.ABCICodeOK),
+					Info: fmt.Sprintf(
+						"Unknown `%s` query path: %v",
+						queryPrefix, path),
+				}
+			}
 			//TODO: sync lock, validate pair
 			if len(path) < 3 {
 				return &abci.ResponseQuery{
@@ -106,10 +116,11 @@ func createAbciQueryHandler(keeper *DexKeeper) app.AbciQueryHandler {
 					levelLimit = l
 				}
 			}
-			levels := keeper.GetOrderBookLevels(pair, levelLimit)
+			levels, pendingMatch := keeper.GetOrderBookLevels(pair, levelLimit)
 			book := store.OrderBook{
-				Height: height,
-				Levels: levels,
+				Height:       height,
+				Levels:       levels,
+				PendingMatch: pendingMatch,
 			}
 			bz, err := app.GetCodec().MarshalBinaryLengthPrefixed(book)
 			if err != nil {
@@ -123,6 +134,14 @@ func createAbciQueryHandler(keeper *DexKeeper) app.AbciQueryHandler {
 				Value: bz,
 			}
 		case "openorders": // args: ["dex", "openorders", <pair>, <bech32Str>]
+			if queryPrefix == DexMiniAbciQueryPrefix {
+				return &abci.ResponseQuery{
+					Code: uint32(sdk.ABCICodeOK),
+					Info: fmt.Sprintf(
+						"Unknown `%s` query path: %v",
+						queryPrefix, path),
+				}
+			}
 			if len(path) < 4 {
 				return &abci.ResponseQuery{
 					Code: uint32(sdk.CodeUnknownRequest),
@@ -140,8 +159,7 @@ func createAbciQueryHandler(keeper *DexKeeper) app.AbciQueryHandler {
 				}
 			}
 			ctx := app.GetContextForCheckState()
-			existingPair, err := keeper.PairMapper.GetTradingPair(ctx, baseAsset, quoteAsset)
-			if pair != existingPair.GetSymbol() || err != nil {
+			if !keeper.PairMapper.Exists(ctx, baseAsset, quoteAsset) {
 				return &abci.ResponseQuery{
 					Code: uint32(sdk.CodeInternal),
 					Log:  "pair is not listed",
@@ -173,8 +191,25 @@ func createAbciQueryHandler(keeper *DexKeeper) app.AbciQueryHandler {
 				Code: uint32(sdk.ABCICodeOK),
 				Info: fmt.Sprintf(
 					"Unknown `%s` query path: %v",
-					AbciQueryPrefix, path),
+					queryPrefix, path),
 			}
 		}
 	}
 }
+
+func listPairs(keeper *DexKeeper, ctx sdk.Context, abciPrefix string) []types.TradingPair {
+	pairs := keeper.PairMapper.ListAllTradingPairs(ctx)
+	rs := make([]types.TradingPair, 0, len(pairs))
+	for _, pair := range pairs {
+		if keeper.GetPairType(pair.GetSymbol()) == order.PairType.MINI {
+			if abciPrefix == DexMiniAbciQueryPrefix {
+				rs = append(rs, pair)
+			}
+		} else {
+			if abciPrefix == DexAbciQueryPrefix {
+				rs = append(rs, pair)
+			}
+		}
+	}
+	return rs
+}
\ No newline at end of file
diff --git a/plugins/dex/aliases.go b/plugins/dex/aliases.go
index 1c0bff943..9e7d34657 100644
--- a/plugins/dex/aliases.go
+++ b/plugins/dex/aliases.go
@@ -10,9 +10,9 @@ import (
 // type TradingPair = types.TradingPair
 
 type TradingPairMapper = store.TradingPairMapper
-type DexKeeper = order.Keeper
+type DexKeeper = order.DexKeeper
 
 var NewTradingPairMapper = store.NewTradingPairMapper
-var NewOrderKeeper = order.NewKeeper
+var NewDexKeeper = order.NewDexKeeper
 
 const DefaultCodespace = types.DefaultCodespace
diff --git a/plugins/dex/client/cli/commands.go b/plugins/dex/client/cli/commands.go
index 30c3b1f3c..3ff94a2bf 100644
--- a/plugins/dex/client/cli/commands.go
+++ b/plugins/dex/client/cli/commands.go
@@ -22,6 +22,7 @@ func AddCommands(cmd *cobra.Command, cdc *wire.Codec) {
 	dexCmd.AddCommand(
 		client.PostCommands(
 			listTradingPairCmd(cdc),
+			listMiniTradingPairCmd(cdc),
 			client.LineBreak,
 			newOrderCmd(cdc),
 			cancelOrderCmd(cdc))...)
diff --git a/plugins/dex/client/cli/list.go b/plugins/dex/client/cli/list.go
index 864ebd668..9c515a8cd 100644
--- a/plugins/dex/client/cli/list.go
+++ b/plugins/dex/client/cli/list.go
@@ -32,13 +32,13 @@ func listTradingPairCmd(cdc *wire.Codec) *cobra.Command {
 			}
 
 			baseAsset := viper.GetString(flagBaseAsset)
-			err = types.ValidateMapperTokenSymbol(baseAsset)
+			err = types.ValidateTokenSymbol(baseAsset)
 			if err != nil {
 				return err
 			}
 
 			quoteAsset := viper.GetString(flagQuoteAsset)
-			err = types.ValidateMapperTokenSymbol(quoteAsset)
+			err = types.ValidateTokenSymbol(quoteAsset)
 			if err != nil {
 				return err
 			}
@@ -74,3 +74,52 @@ func listTradingPairCmd(cdc *wire.Codec) *cobra.Command {
 
 	return cmd
 }
+
+func listMiniTradingPairCmd(cdc *wire.Codec) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "list-mini",
+		Short: "",
+		RunE: func(cmd *cobra.Command, args []string) error {
+			cliCtx, txbldr := client.PrepareCtx(cdc)
+
+			from, err := cliCtx.GetFromAddress()
+			if err != nil {
+				return err
+			}
+
+			baseAsset := viper.GetString(flagBaseAsset)
+			err = types.ValidateMiniTokenSymbol(baseAsset)
+			if err != nil {
+				return err
+			}
+
+			quoteAsset := viper.GetString(flagQuoteAsset)
+			if quoteAsset != types.NativeTokenSymbol && !strings.HasPrefix(quoteAsset, "BUSD") {
+				return errors.New("invalid quote asset")
+			}
+
+			baseAsset = strings.ToUpper(baseAsset)
+			quoteAsset = strings.ToUpper(quoteAsset)
+
+			initPriceStr := viper.GetString(flagInitPrice)
+			initPrice, err := utils.ParsePrice(initPriceStr)
+			if err != nil {
+				return err
+			}
+
+			msg := list.NewListMiniMsg(from, baseAsset, quoteAsset, initPrice)
+			err = client.SendOrPrintTx(cliCtx, txbldr, msg)
+			if err != nil {
+				return err
+			}
+
+			return nil
+		},
+	}
+
+	cmd.Flags().StringP(flagBaseAsset, "s", "", "symbol of the base asset")
+	cmd.Flags().String(flagQuoteAsset, "", "symbol of the quote currency")
+	cmd.Flags().String(flagInitPrice, "", "init price for this pair")
+
+	return cmd
+}
diff --git a/plugins/dex/client/rest/getpairs.go b/plugins/dex/client/rest/getpairs.go
index cb755a9c9..200551229 100644
--- a/plugins/dex/client/rest/getpairs.go
+++ b/plugins/dex/client/rest/getpairs.go
@@ -16,8 +16,8 @@ const maxPairsLimit = 1000
 const defaultPairsLimit = 100
 const defaultPairsOffset = 0
 
-func listAllTradingPairs(ctx context.CLIContext, cdc *wire.Codec, offset int, limit int) ([]types.TradingPair, error) {
-	bz, err := ctx.Query(fmt.Sprintf("dex/pairs/%d/%d", offset, limit), nil)
+func listAllTradingPairs(ctx context.CLIContext, cdc *wire.Codec, prefix string, offset int, limit int) ([]types.TradingPair, error) {
+	bz, err := ctx.Query(fmt.Sprintf("%s/pairs/%d/%d", prefix, offset, limit), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -27,7 +27,7 @@ func listAllTradingPairs(ctx context.CLIContext, cdc *wire.Codec, offset int, li
 }
 
 // GetPairsReqHandler creates an http request handler to list
-func GetPairsReqHandler(cdc *wire.Codec, ctx context.CLIContext) http.HandlerFunc {
+func GetPairsReqHandler(cdc *wire.Codec, ctx context.CLIContext, abciQueryPrefix string) http.HandlerFunc {
 	type params struct {
 		limit  int
 		offset int
@@ -79,7 +79,7 @@ func GetPairsReqHandler(cdc *wire.Codec, ctx context.CLIContext) http.HandlerFun
 			params.limit = maxPairsLimit
 		}
 
-		pairs, err := listAllTradingPairs(ctx, cdc, params.offset, params.limit)
+		pairs, err := listAllTradingPairs(ctx, cdc, abciQueryPrefix, params.offset, params.limit)
 		if err != nil {
 			throw(w, http.StatusInternalServerError, err)
 			return
diff --git a/plugins/dex/client/rest/utils/streamer.go b/plugins/dex/client/rest/utils/streamer.go
index 0a5c15d5f..015dbb40d 100644
--- a/plugins/dex/client/rest/utils/streamer.go
+++ b/plugins/dex/client/rest/utils/streamer.go
@@ -78,6 +78,10 @@ func StreamDepthResponse(w io.Writer, ob *store.OrderBook, limit int) error {
 		i++
 	}
 
-	// end streamed json with height
-	return write(w, fmt.Sprintf("],\"height\":%d}", ob.Height))
+	// pass 3 - height
+	if err := write(w, fmt.Sprintf("],\"height\":%d", ob.Height)); err != nil {
+		return err
+	}
+	// end streamed json with pendingMatch flag
+	return write(w, fmt.Sprintf(",\"pendingMatch\":%t}", ob.PendingMatch))
 }
diff --git a/plugins/dex/client/rest/utils/streamer_test.go b/plugins/dex/client/rest/utils/streamer_test.go
index cb42003c9..21ce123fe 100644
--- a/plugins/dex/client/rest/utils/streamer_test.go
+++ b/plugins/dex/client/rest/utils/streamer_test.go
@@ -19,9 +19,10 @@ func TestStreamDepthResponse(t *testing.T) {
 		limit int
 	}
 	type response struct {
-		Height int64      `json:"height"`
-		Asks   [][]string `json:"asks"`
-		Bids   [][]string `json:"bids"`
+		Height       int64      `json:"height"`
+		Asks         [][]string `json:"asks"`
+		Bids         [][]string `json:"bids"`
+		PendingMatch bool       `json:"pendingMatch"`
 	}
 	tests := []struct {
 		name    string
@@ -41,9 +42,10 @@ func TestStreamDepthResponse(t *testing.T) {
 			},
 			wantW: (func() string {
 				want, _ := json.Marshal(response{
-					Height: 1,
-					Asks:   [][]string{},
-					Bids:   [][]string{},
+					Height:       1,
+					Asks:         [][]string{},
+					Bids:         [][]string{},
+					PendingMatch: false,
 				})
 				sorted := types.MustSortJSON(want)
 				return string(sorted)
@@ -82,6 +84,7 @@ func TestStreamDepthResponse(t *testing.T) {
 					Bids: [][]string{
 						{"100.00000000", "1.00000000"},
 					},
+					PendingMatch: false,
 				})
 				sorted := types.MustSortJSON(want)
 				return string(sorted)
@@ -126,6 +129,7 @@ func TestStreamDepthResponse(t *testing.T) {
 						{"150.00000000", "1.00000000"},
 						{"100.00000000", "1.00000000"},
 					},
+					PendingMatch: false,
 				})
 				sorted := types.MustSortJSON(want)
 				return string(sorted)
@@ -165,6 +169,7 @@ func TestStreamDepthResponse(t *testing.T) {
 						// highest buy first
 						{"100.00000000", "1.00000000"},
 					},
+					PendingMatch: false,
 				})
 				sorted := types.MustSortJSON(want)
 				return string(sorted)
diff --git a/plugins/dex/list/handler.go b/plugins/dex/list/handler.go
index 88e3bf896..7aced6b44 100644
--- a/plugins/dex/list/handler.go
+++ b/plugins/dex/list/handler.go
@@ -18,11 +18,13 @@ import (
 )
 
 // NewHandler initialises dex message handlers
-func NewHandler(keeper *order.Keeper, tokenMapper tokens.Mapper, govKeeper gov.Keeper) sdk.Handler {
+func NewHandler(keeper *order.DexKeeper, tokenMapper tokens.Mapper, govKeeper gov.Keeper) sdk.Handler {
 	return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
 		switch msg := msg.(type) {
 		case ListMsg:
 			return handleList(ctx, keeper, tokenMapper, govKeeper, msg)
+		case ListMiniMsg:
+			return handleListMini(ctx, keeper, tokenMapper, msg)
 		default:
 			errMsg := fmt.Sprintf("Unrecognized dex msg type: %v", reflect.TypeOf(msg).Name())
 			return sdk.ErrUnknownRequest(errMsg).Result()
@@ -74,7 +76,7 @@ func checkListProposal(ctx sdk.Context, govKeeper gov.Keeper, msg ListMsg) error
 	return nil
 }
 
-func handleList(ctx sdk.Context, keeper *order.Keeper, tokenMapper tokens.Mapper, govKeeper gov.Keeper,
+func handleList(ctx sdk.Context, keeper *order.DexKeeper, tokenMapper tokens.Mapper, govKeeper gov.Keeper,
 	msg ListMsg) sdk.Result {
 	if err := checkListProposal(ctx, govKeeper, msg); err != nil {
 		return types.ErrInvalidProposal(err.Error()).Result()
@@ -103,12 +105,12 @@ func handleList(ctx sdk.Context, keeper *order.Keeper, tokenMapper tokens.Mapper
 			return sdk.ErrUnauthorized("only the owner of the token can list the token").Result()
 		}
 
-		if !tokenMapper.Exists(ctx, msg.QuoteAssetSymbol) {
+		if !tokenMapper.ExistsBEP2(ctx, msg.QuoteAssetSymbol) {
 			return sdk.ErrInvalidCoins("quote token does not exist").Result()
 		}
 	}
 
-	if !tokenMapper.Exists(ctx, msg.QuoteAssetSymbol) {
+	if !tokenMapper.ExistsBEP2(ctx, msg.QuoteAssetSymbol) {
 		return sdk.ErrInvalidCoins("quote token does not exist").Result()
 	}
 
diff --git a/plugins/dex/list/handler_mini.go b/plugins/dex/list/handler_mini.go
new file mode 100644
index 000000000..317cf6063
--- /dev/null
+++ b/plugins/dex/list/handler_mini.go
@@ -0,0 +1,46 @@
+package list
+
+import (
+	"github.com/binance-chain/node/common/log"
+	"github.com/binance-chain/node/plugins/dex/order"
+	"github.com/binance-chain/node/plugins/dex/types"
+	"github.com/binance-chain/node/plugins/tokens"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+func handleListMini(ctx sdk.Context, dexKeeper *order.DexKeeper, tokenMapper tokens.Mapper,
+	msg ListMiniMsg) sdk.Result {
+
+	if err := dexKeeper.CanListTradingPair(ctx, msg.BaseAssetSymbol, msg.QuoteAssetSymbol); err != nil {
+		return sdk.ErrInvalidCoins(err.Error()).Result()
+	}
+
+	baseToken, err := tokenMapper.GetToken(ctx, msg.BaseAssetSymbol)
+	if err != nil {
+		return sdk.ErrInvalidCoins(err.Error()).Result()
+	}
+
+	quoteToken, err := tokenMapper.GetToken(ctx, msg.QuoteAssetSymbol)
+	if err != nil {
+		return sdk.ErrInvalidCoins(err.Error()).Result()
+	}
+
+	if !baseToken.IsOwner(msg.From) && !quoteToken.IsOwner(msg.From) {
+		return sdk.ErrUnauthorized("only the owner of the base asset or quote asset can list the trading pair").Result()
+	}
+
+	lotSize := dexKeeper.DetermineLotSize(msg.BaseAssetSymbol, msg.QuoteAssetSymbol, msg.InitPrice)
+	pair := types.NewTradingPairWithLotSize(msg.BaseAssetSymbol, msg.QuoteAssetSymbol, msg.InitPrice, lotSize)
+	err = dexKeeper.PairMapper.AddTradingPair(ctx, pair)
+	if err != nil {
+		return sdk.ErrInternal(err.Error()).Result()
+	}
+
+	// this is done in memory! we must not run this block in checktx or simulate!
+	if ctx.IsDeliverTx() { // only add engine during DeliverTx
+		dexKeeper.AddEngine(pair)
+		log.With("module", "dex").Info("List new mini-token Pair and created new match engine", "pair", pair)
+	}
+
+	return sdk.Result{}
+}
diff --git a/plugins/dex/list/handler_mini_test.go b/plugins/dex/list/handler_mini_test.go
new file mode 100644
index 000000000..99b83d253
--- /dev/null
+++ b/plugins/dex/list/handler_mini_test.go
@@ -0,0 +1,118 @@
+package list
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+
+	abci "github.com/tendermint/tendermint/abci/types"
+	"github.com/tendermint/tendermint/libs/log"
+
+	"github.com/binance-chain/node/common/types"
+	"github.com/binance-chain/node/common/upgrade"
+	"github.com/binance-chain/node/plugins/tokens"
+)
+
+func setChainVersion() {
+	upgrade.Mgr.AddUpgradeHeight(upgrade.BEP8, -1)
+	upgrade.Mgr.AddUpgradeHeight(upgrade.BEP70, -1)
+}
+
+func resetChainVersion() {
+	upgrade.Mgr.Config.HeightMap = nil
+}
+
+func setupForMini(ctx sdk.Context, tokenMapper tokens.Mapper, t *testing.T) {
+	err := tokenMapper.NewToken(ctx, &types.Token{
+		Name:        "Bitcoin",
+		Symbol:      "BTC-000",
+		OrigSymbol:  "BTC",
+		TotalSupply: 10000,
+		Owner:       sdk.AccAddress("testacc"),
+	})
+	require.Nil(t, err, "new token error")
+
+	err = tokenMapper.NewToken(ctx, &types.Token{
+		Name:        "Native Token",
+		Symbol:      types.NativeTokenSymbol,
+		OrigSymbol:  types.NativeTokenSymbol,
+		TotalSupply: 10000,
+		Owner:       sdk.AccAddress("testacc"),
+	})
+	require.Nil(t, err, "new token error")
+
+	miniToken := types.NewMiniToken("Bitcoin Mini", "BTC", "BTC-000M", types.MiniRangeType, 100000e8, sdk.AccAddress("testacc"), false, "")
+	err = tokenMapper.NewToken(ctx, miniToken)
+	require.Nil(t, err, "new token error")
+
+	tinyToken := types.NewMiniToken("Bitcoin Mini", "ETH", "ETH-000M", types.TinyRangeType, 10000e8, sdk.AccAddress("testacc"), true, "abc")
+	err = tokenMapper.NewToken(ctx, tinyToken)
+	require.Nil(t, err, "new token error")
+}
+
+func TestHandleListMiniIdenticalSymbols(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	cdc := MakeCodec()
+	ms, orderKeeper, tokenMapper, _ := MakeKeepers(cdc)
+	ctx := sdk.NewContext(ms, abci.Header{}, sdk.RunTxModeDeliver, log.NewNopLogger())
+	setupForMini(ctx, tokenMapper, t)
+	result := handleListMini(ctx, orderKeeper, tokenMapper, ListMiniMsg{
+		From:             sdk.AccAddress("testacc"),
+		BaseAssetSymbol:  "BTC-000M",
+		QuoteAssetSymbol: "BTC-000M",
+		InitPrice:        1000,
+	})
+	require.Contains(t, result.Log, "base asset symbol should not be identical to quote asset symbol")
+}
+
+func TestHandleListMiniWrongBaseSymbol(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	cdc := MakeCodec()
+	ms, orderKeeper, tokenMapper, _ := MakeKeepers(cdc)
+	ctx := sdk.NewContext(ms, abci.Header{}, sdk.RunTxModeDeliver, log.NewNopLogger())
+	setupForMini(ctx, tokenMapper, t)
+	result := handleListMini(ctx, orderKeeper, tokenMapper, ListMiniMsg{
+		From:             sdk.AccAddress("testacc"),
+		BaseAssetSymbol:  "BTC",
+		QuoteAssetSymbol: "BNB",
+		InitPrice:        1000,
+	})
+	//require.Equal(t, result.Code, sdk.ABCICodeOK)
+	require.Contains(t, result.Log, "token(BTC) not found")
+}
+
+func TestHandleListMiniRight(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	cdc := MakeCodec()
+	ms, orderKeeper, tokenMapper, _ := MakeKeepers(cdc)
+	ctx := sdk.NewContext(ms, abci.Header{}, sdk.RunTxModeDeliver, log.NewNopLogger())
+	setupForMini(ctx, tokenMapper, t)
+	result := handleListMini(ctx, orderKeeper, tokenMapper, ListMiniMsg{
+		From:             sdk.AccAddress("testacc"),
+		BaseAssetSymbol:  "BTC-000M",
+		QuoteAssetSymbol: "BNB",
+		InitPrice:        1000,
+	})
+	require.Equal(t, result.Code, sdk.ABCICodeOK)
+}
+
+func TestHandleListTinyRight(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	cdc := MakeCodec()
+	ms, orderKeeper, tokenMapper, _ := MakeKeepers(cdc)
+	ctx := sdk.NewContext(ms, abci.Header{}, sdk.RunTxModeDeliver, log.NewNopLogger())
+	setupForMini(ctx, tokenMapper, t)
+	result := handleListMini(ctx, orderKeeper, tokenMapper, ListMiniMsg{
+		From:             sdk.AccAddress("testacc"),
+		BaseAssetSymbol:  "ETH-000M",
+		QuoteAssetSymbol: "BNB",
+		InitPrice:        1000,
+	})
+	require.Equal(t, result.Code, sdk.ABCICodeOK)
+}
diff --git a/plugins/dex/list/handler_test.go b/plugins/dex/list/handler_test.go
index e161fa56d..aec312a94 100644
--- a/plugins/dex/list/handler_test.go
+++ b/plugins/dex/list/handler_test.go
@@ -27,7 +27,6 @@ import (
 	"github.com/binance-chain/node/plugins/dex/store"
 	dexTypes "github.com/binance-chain/node/plugins/dex/types"
 	"github.com/binance-chain/node/plugins/tokens"
-	tokenStore "github.com/binance-chain/node/plugins/tokens/store"
 )
 
 func MakeCodec() *codec.Codec {
@@ -42,7 +41,7 @@ func MakeCodec() *codec.Codec {
 	return cdc
 }
 
-func MakeKeepers(cdc *codec.Codec) (ms sdkStore.CommitMultiStore, orderKeeper *order.Keeper, tokenMapper tokenStore.Mapper, govKeeper gov.Keeper) {
+func MakeKeepers(cdc *codec.Codec) (ms sdkStore.CommitMultiStore, dexKeeper *order.DexKeeper, tokenMapper tokens.Mapper, govKeeper gov.Keeper) {
 	accKey := sdk.NewKVStoreKey("acc")
 	pairKey := sdk.NewKVStoreKey("pair")
 	tokenKey := sdk.NewKVStoreKey("token")
@@ -65,10 +64,9 @@ func MakeKeepers(cdc *codec.Codec) (ms sdkStore.CommitMultiStore, orderKeeper *o
 	accKeeper := auth.NewAccountKeeper(cdc, accKey, types.ProtoAppAccount)
 	codespacer := sdk.NewCodespacer()
 	pairMapper := store.NewTradingPairMapper(cdc, pairKey)
-	orderKeeper = order.NewKeeper(common.DexStoreKey, accKeeper, pairMapper,
-		codespacer.RegisterNext(dexTypes.DefaultCodespace), 2, cdc, false)
+	dexKeeper = order.NewDexKeeper(common.DexStoreKey, accKeeper, pairMapper, codespacer.RegisterNext(dexTypes.DefaultCodespace), 2, cdc, false)
 
-	tokenMapper = tokenStore.NewMapper(cdc, tokenKey)
+	tokenMapper = tokens.NewMapper(cdc, tokenKey)
 
 	paramsKeeper := params.NewKeeper(cdc, paramKey, paramTKey)
 	bankKeeper := bank.NewBaseKeeper(accKeeper)
@@ -85,7 +83,7 @@ func MakeKeepers(cdc *codec.Codec) (ms sdkStore.CommitMultiStore, orderKeeper *o
 		gov.DefaultCodespace,
 		new(sdk.Pool))
 
-	return ms, orderKeeper, tokenMapper, govKeeper
+	return ms, dexKeeper, tokenMapper, govKeeper
 }
 
 func getProposal(lowerCase bool, baseAssetSymbol string, quoteAssetSymbol string) gov.Proposal {
@@ -207,7 +205,7 @@ func TestListHandler(t *testing.T) {
 	})
 	require.Contains(t, result.Log, "token(BTC-000) not found")
 
-	err := tokenMapper.NewToken(ctx, types.Token{
+	err := tokenMapper.NewToken(ctx, &types.Token{
 		Name:        "Bitcoin",
 		Symbol:      "BTC-000",
 		OrigSymbol:  "BTC",
@@ -234,7 +232,7 @@ func TestListHandler(t *testing.T) {
 	})
 	require.Contains(t, result.Log, "quote token does not exist")
 
-	err = tokenMapper.NewToken(ctx, types.Token{
+	err = tokenMapper.NewToken(ctx, &types.Token{
 		Name:        "Native Token",
 		Symbol:      types.NativeTokenSymbol,
 		OrigSymbol:  types.NativeTokenSymbol,
@@ -258,7 +256,7 @@ func TestListHandler_LowerCase(t *testing.T) {
 	cdc := MakeCodec()
 	ms, orderKeeper, tokenMapper, govKeeper := MakeKeepers(cdc)
 	ctx := sdk.NewContext(ms, abci.Header{}, sdk.RunTxModeDeliver, log.NewNopLogger())
-	err := tokenMapper.NewToken(ctx, types.Token{
+	err := tokenMapper.NewToken(ctx, &types.Token{
 		Name:        "Bitcoin",
 		Symbol:      "BTC-000",
 		OrigSymbol:  "BTC",
@@ -267,7 +265,7 @@ func TestListHandler_LowerCase(t *testing.T) {
 	})
 	require.Nil(t, err, "new token error")
 
-	err = tokenMapper.NewToken(ctx, types.Token{
+	err = tokenMapper.NewToken(ctx, &types.Token{
 		Name:        "Native Token",
 		Symbol:      types.NativeTokenSymbol,
 		OrigSymbol:  types.NativeTokenSymbol,
@@ -325,7 +323,7 @@ func TestListHandler_AfterUpgrade(t *testing.T) {
 	cdc := MakeCodec()
 	ms, orderKeeper, tokenMapper, govKeeper := MakeKeepers(cdc)
 	ctx := sdk.NewContext(ms, abci.Header{}, sdk.RunTxModeDeliver, log.NewNopLogger())
-	err := tokenMapper.NewToken(ctx, types.Token{
+	err := tokenMapper.NewToken(ctx, &types.Token{
 		Name:        "Bitcoin",
 		Symbol:      "BTC-000",
 		OrigSymbol:  "BTC",
@@ -334,7 +332,7 @@ func TestListHandler_AfterUpgrade(t *testing.T) {
 	})
 	require.Nil(t, err, "new token error")
 
-	err = tokenMapper.NewToken(ctx, types.Token{
+	err = tokenMapper.NewToken(ctx, &types.Token{
 		Name:        "Native Token",
 		Symbol:      types.NativeTokenSymbol,
 		OrigSymbol:  types.NativeTokenSymbol,
diff --git a/plugins/dex/list/hooks.go b/plugins/dex/list/hooks.go
index 89fd5302e..bce51f7f7 100644
--- a/plugins/dex/list/hooks.go
+++ b/plugins/dex/list/hooks.go
@@ -14,11 +14,11 @@ import (
 )
 
 type ListHooks struct {
-	orderKeeper *order.Keeper
+	orderKeeper *order.DexKeeper
 	tokenMapper tokens.Mapper
 }
 
-func NewListHooks(orderKeeper *order.Keeper, tokenMapper tokens.Mapper) ListHooks {
+func NewListHooks(orderKeeper *order.DexKeeper, tokenMapper tokens.Mapper) ListHooks {
 	return ListHooks{
 		orderKeeper: orderKeeper,
 		tokenMapper: tokenMapper,
@@ -58,11 +58,11 @@ func (hooks ListHooks) OnProposalSubmitted(ctx sdk.Context, proposal gov.Proposa
 		return errors.New("expire time should after now")
 	}
 
-	if !hooks.tokenMapper.Exists(ctx, listParams.BaseAssetSymbol) {
+	if !hooks.tokenMapper.ExistsBEP2(ctx, listParams.BaseAssetSymbol) {
 		return errors.New("base token does not exist")
 	}
 
-	if !hooks.tokenMapper.Exists(ctx, listParams.QuoteAssetSymbol) {
+	if !hooks.tokenMapper.ExistsBEP2(ctx, listParams.QuoteAssetSymbol) {
 		return errors.New("quote token does not exist")
 	}
 
@@ -74,10 +74,10 @@ func (hooks ListHooks) OnProposalSubmitted(ctx sdk.Context, proposal gov.Proposa
 }
 
 type DelistHooks struct {
-	orderKeeper *order.Keeper
+	orderKeeper *order.DexKeeper
 }
 
-func NewDelistHooks(orderKeeper *order.Keeper) DelistHooks {
+func NewDelistHooks(orderKeeper *order.DexKeeper) DelistHooks {
 	return DelistHooks{
 		orderKeeper: orderKeeper,
 	}
diff --git a/plugins/dex/list/hooks_test.go b/plugins/dex/list/hooks_test.go
index 2c86f610f..996a0f946 100644
--- a/plugins/dex/list/hooks_test.go
+++ b/plugins/dex/list/hooks_test.go
@@ -177,7 +177,7 @@ func TestTradingPairExists(t *testing.T) {
 
 	ctx := sdk.NewContext(ms, abci.Header{}, sdk.RunTxModeDeliver, log.NewNopLogger())
 
-	err = tokenMapper.NewToken(ctx, types.Token{
+	err = tokenMapper.NewToken(ctx, &types.Token{
 		Name:        "Native Token",
 		Symbol:      listParams.BaseAssetSymbol,
 		OrigSymbol:  listParams.BaseAssetSymbol,
@@ -186,7 +186,7 @@ func TestTradingPairExists(t *testing.T) {
 	})
 	require.Nil(t, err, "new token error")
 
-	err = tokenMapper.NewToken(ctx, types.Token{
+	err = tokenMapper.NewToken(ctx, &types.Token{
 		Name:        "Native Token",
 		Symbol:      listParams.QuoteAssetSymbol,
 		OrigSymbol:  "BTC",
@@ -226,7 +226,7 @@ func TestPrerequisiteTradingPair(t *testing.T) {
 
 	ctx := sdk.NewContext(ms, abci.Header{}, sdk.RunTxModeDeliver, log.NewNopLogger())
 
-	err = tokenMapper.NewToken(ctx, types.Token{
+	err = tokenMapper.NewToken(ctx, &types.Token{
 		Name:        "Native Token",
 		Symbol:      listParams.BaseAssetSymbol,
 		OrigSymbol:  "BTC",
@@ -235,7 +235,7 @@ func TestPrerequisiteTradingPair(t *testing.T) {
 	})
 	require.Nil(t, err, "new token error")
 
-	err = tokenMapper.NewToken(ctx, types.Token{
+	err = tokenMapper.NewToken(ctx, &types.Token{
 		Name:        "Native Token",
 		Symbol:      listParams.QuoteAssetSymbol,
 		OrigSymbol:  "ETH",
@@ -260,7 +260,7 @@ func TestPrerequisiteTradingPair(t *testing.T) {
 	err = orderKeeper.PairMapper.AddTradingPair(ctx, pair)
 	require.Nil(t, err, "add trading pair error")
 
-	err = tokenMapper.NewToken(ctx, types.Token{
+	err = tokenMapper.NewToken(ctx, &types.Token{
 		Name:        "Native Token",
 		Symbol:      listParams.BaseAssetSymbol,
 		OrigSymbol:  "BTC",
@@ -269,7 +269,7 @@ func TestPrerequisiteTradingPair(t *testing.T) {
 	})
 	require.Nil(t, err, "new token error")
 
-	err = tokenMapper.NewToken(ctx, types.Token{
+	err = tokenMapper.NewToken(ctx, &types.Token{
 		Name:        "Native Token",
 		Symbol:      listParams.QuoteAssetSymbol,
 		OrigSymbol:  "ETH",
@@ -330,7 +330,7 @@ func TestQuoteTokenDoesNotExist(t *testing.T) {
 
 	ctx := sdk.NewContext(ms, abci.Header{}, sdk.RunTxModeDeliver, log.NewNopLogger())
 
-	err = tokenMapper.NewToken(ctx, types.Token{
+	err = tokenMapper.NewToken(ctx, &types.Token{
 		Name:        "Native Token",
 		Symbol:      listParams.BaseAssetSymbol,
 		OrigSymbol:  "BNB",
@@ -366,7 +366,7 @@ func TestRightProposal(t *testing.T) {
 
 	ctx := sdk.NewContext(ms, abci.Header{}, sdk.RunTxModeDeliver, log.NewNopLogger())
 
-	err = tokenMapper.NewToken(ctx, types.Token{
+	err = tokenMapper.NewToken(ctx, &types.Token{
 		Name:        "Native Token",
 		Symbol:      listParams.BaseAssetSymbol,
 		OrigSymbol:  listParams.BaseAssetSymbol,
@@ -375,7 +375,7 @@ func TestRightProposal(t *testing.T) {
 	})
 	require.Nil(t, err, "new token error")
 
-	err = tokenMapper.NewToken(ctx, types.Token{
+	err = tokenMapper.NewToken(ctx, &types.Token{
 		Name:        "Native Token",
 		Symbol:      listParams.QuoteAssetSymbol,
 		OrigSymbol:  "BTC",
diff --git a/plugins/dex/list/msg.go b/plugins/dex/list/msg.go
index 286687846..9845366c7 100644
--- a/plugins/dex/list/msg.go
+++ b/plugins/dex/list/msg.go
@@ -40,11 +40,11 @@ func (msg ListMsg) ValidateBasic() sdk.Error {
 	if msg.ProposalId <= 0 {
 		return sdk.ErrInvalidCoins("proposal id should be positive")
 	}
-	err := types.ValidateMapperTokenSymbol(msg.BaseAssetSymbol)
+	err := types.ValidateTokenSymbol(msg.BaseAssetSymbol)
 	if err != nil {
 		return sdk.ErrInvalidCoins("base token: " + err.Error())
 	}
-	err = types.ValidateMapperTokenSymbol(msg.QuoteAssetSymbol)
+	err = types.ValidateTokenSymbol(msg.QuoteAssetSymbol)
 	if err != nil {
 		return sdk.ErrInvalidCoins("quote token: " + err.Error())
 	}
diff --git a/plugins/dex/list/msg_mini.go b/plugins/dex/list/msg_mini.go
new file mode 100644
index 000000000..09c0111ba
--- /dev/null
+++ b/plugins/dex/list/msg_mini.go
@@ -0,0 +1,75 @@
+package list
+
+import (
+	"encoding/json"
+	"fmt"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+
+	"github.com/binance-chain/node/common/types"
+	"github.com/binance-chain/node/common/upgrade"
+	"github.com/binance-chain/node/plugins/dex/order"
+)
+
+const MiniMsg = "dexListMini"
+
+var _ sdk.Msg = ListMiniMsg{}
+
+type ListMiniMsg struct {
+	From             sdk.AccAddress `json:"from"`
+	BaseAssetSymbol  string         `json:"base_asset_symbol"`
+	QuoteAssetSymbol string         `json:"quote_asset_symbol"`
+	InitPrice        int64          `json:"init_price"`
+}
+
+func NewListMiniMsg(from sdk.AccAddress, baseAssetSymbol string, quoteAssetSymbol string, initPrice int64) ListMiniMsg {
+	return ListMiniMsg{
+		From:             from,
+		BaseAssetSymbol:  baseAssetSymbol,
+		QuoteAssetSymbol: quoteAssetSymbol,
+		InitPrice:        initPrice,
+	}
+}
+
+func (msg ListMiniMsg) Route() string                { return Route }
+func (msg ListMiniMsg) Type() string                 { return MiniMsg }
+func (msg ListMiniMsg) String() string               { return fmt.Sprintf("MsgListMini{%#v}", msg) }
+func (msg ListMiniMsg) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.From} }
+
+func (msg ListMiniMsg) ValidateBasic() sdk.Error {
+	err := types.ValidateMiniTokenSymbol(msg.BaseAssetSymbol)
+	if err != nil {
+		return sdk.ErrInvalidCoins("base token: " + err.Error())
+	}
+	if len(msg.QuoteAssetSymbol) == 0 {
+		return sdk.ErrInvalidCoins("quote token is empty ")
+	}
+
+	// before BEP70 upgraded, we only support listing mini token against NativeToken
+	if sdk.IsUpgrade(upgrade.BEP70) {
+		if types.NativeTokenSymbol != msg.QuoteAssetSymbol && order.BUSDSymbol != msg.QuoteAssetSymbol {
+			return sdk.ErrInvalidCoins("quote token is not valid ")
+		}
+	} else {
+		if types.NativeTokenSymbol != msg.QuoteAssetSymbol {
+			return sdk.ErrInvalidCoins("quote token is not valid ")
+		}
+	}
+
+	if msg.InitPrice <= 0 {
+		return sdk.ErrInvalidCoins("price should be positive")
+	}
+	return nil
+}
+
+func (msg ListMiniMsg) GetSignBytes() []byte {
+	b, err := json.Marshal(msg)
+	if err != nil {
+		panic(err)
+	}
+	return b
+}
+
+func (msg ListMiniMsg) GetInvolvedAddresses() []sdk.AccAddress {
+	return msg.GetSigners()
+}
diff --git a/plugins/dex/list/msg_mini_test.go b/plugins/dex/list/msg_mini_test.go
new file mode 100644
index 000000000..b399247c3
--- /dev/null
+++ b/plugins/dex/list/msg_mini_test.go
@@ -0,0 +1,67 @@
+package list
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+
+	"github.com/binance-chain/node/plugins/dex/order"
+)
+
+func TestMiniIdenticalBaseAssetAndQuoteAsset(t *testing.T) {
+	msg := NewListMiniMsg(sdk.AccAddress{}, "BTC-000M", "BTC-000M", 1000)
+	err := msg.ValidateBasic()
+	require.NotNil(t, err, "msg should be error")
+	require.Contains(t, err.Error(), "quote token is not valid")
+}
+
+func TestMiniWrongBaseAssetSymbol(t *testing.T) {
+	msg := NewListMiniMsg(sdk.AccAddress{}, "BTC", "BTC-000", 1000)
+	err := msg.ValidateBasic()
+	require.NotNil(t, err, "msg should be error")
+	require.Contains(t, err.Error(), "base token: suffixed token symbol must contain a hyphen ('-')")
+}
+
+func TestMiniWrongBaseAssetSymbolNotMiniToken(t *testing.T) {
+	msg := NewListMiniMsg(sdk.AccAddress{}, "BTC-000", "BTC-000", 1000)
+	err := msg.ValidateBasic()
+	require.NotNil(t, err, "msg should be error")
+	require.Contains(t, err.Error(), "base token: mini-token symbol suffix must be 4 chars in length, got 3")
+}
+
+func TestMiniWrongQuoteAssetSymbol(t *testing.T) {
+	msg := NewListMiniMsg(sdk.AccAddress{}, "BTC-000M", "ETH-123", 1000)
+	err := msg.ValidateBasic()
+	require.NotNil(t, err, "msg should be error")
+	require.Contains(t, err.Error(), "quote token is not valid")
+}
+
+func TestMiniWrongInitPrice(t *testing.T) {
+	msg := NewListMiniMsg(sdk.AccAddress{}, "BTC-000M", "BNB", -1000)
+	err := msg.ValidateBasic()
+	require.NotNil(t, err, "msg should be error")
+	require.Contains(t, err.Error(), "price should be positive")
+}
+
+func TestMiniRightMsg(t *testing.T) {
+	msg := NewListMiniMsg(sdk.AccAddress{}, "BTC-000M", "BNB", 1000)
+	err := msg.ValidateBasic()
+	require.Nil(t, err, "msg should not be error")
+}
+
+func TestMiniBUSDQuote(t *testing.T) {
+	msg := NewListMiniMsg(sdk.AccAddress{}, "BTC-000M", "BUSD-000", 1000)
+	err := msg.ValidateBasic()
+	require.NotNil(t, err, "msg should be error")
+	require.Contains(t, err.Error(), "quote token is not valid")
+
+	setChainVersion()
+	defer resetChainVersion()
+	order.BUSDSymbol = "BUSD-000"
+	msg = NewListMiniMsg(sdk.AccAddress{}, "BTC-000M", "BUSD-000", 1000)
+	err = msg.ValidateBasic()
+	require.Nil(t, err, "msg should not be error")
+
+}
diff --git a/plugins/dex/matcheng/engine_new.go b/plugins/dex/matcheng/engine_new.go
index 8070382b7..937511a78 100644
--- a/plugins/dex/matcheng/engine_new.go
+++ b/plugins/dex/matcheng/engine_new.go
@@ -13,6 +13,13 @@ import (
 )
 
 func (me *MatchEng) Match(height int64) bool {
+	success := me.runMatch(height)
+	if sdk.IsUpgrade(upgrade.BEP19) {
+		me.LastMatchHeight = height
+	}
+	return success
+}
+func (me *MatchEng) runMatch(height int64) bool {
 	if !sdk.IsUpgrade(upgrade.BEP19) {
 		return me.MatchBeforeGalileo(height)
 	}
@@ -32,7 +39,8 @@ func (me *MatchEng) Match(height int64) bool {
 		me.logger.Error("dropRedundantQty failed", "error", err)
 		return false
 	}
-	takerSide, err := me.determineTakerSide(height, index)
+	//If order height > the last Match height, then it's maker.
+	takerSide, err := me.determineTakerSide(index)
 	if err != nil {
 		me.logger.Error("determineTakerSide failed", "error", err)
 		return false
@@ -41,7 +49,6 @@ func (me *MatchEng) Match(height int64) bool {
 	surplus := me.overLappedLevel[index].BuySellSurplus
 	me.fillOrdersNew(takerSide, takerSideOrders, index, tradePrice, surplus)
 	me.LastTradePrice = tradePrice
-	me.LastMatchHeight = height
 	return true
 }
 
@@ -115,23 +122,23 @@ func dropRedundantQty(orders []OrderPart, toDropQty, lotSize int64) error {
 	return nil
 }
 
-func findTakerStartIdx(height int64, orders []OrderPart) (idx int, makerTotal int64) {
+func findTakerStartIdx(lastMatchHeight int64, orders []OrderPart) (idx int, makerTotal int64) {
 	i, k := 0, len(orders)
 	for ; i < k; i++ {
-		if orders[i].Time >= height {
-			return i, makerTotal
-		} else {
+		if orders[i].Time <= lastMatchHeight {
 			makerTotal += orders[i].nxtTrade
+		} else {
+			return i, makerTotal
 		}
 	}
 	return i, makerTotal
 }
 
-func (me *MatchEng) determineTakerSide(height int64, tradePriceIdx int) (int8, error) {
+func (me *MatchEng) determineTakerSide(tradePriceIdx int) (int8, error) {
 	makerSide := UNKNOWN
 	for i := 0; i <= tradePriceIdx; i++ {
 		l := &me.overLappedLevel[i]
-		l.BuyTakerStartIdx, l.BuyMakerTotal = findTakerStartIdx(height, l.BuyOrders)
+		l.BuyTakerStartIdx, l.BuyMakerTotal = findTakerStartIdx(me.LastMatchHeight, l.BuyOrders)
 		if l.HasBuyMaker() {
 			makerSide = BUYSIDE
 		}
@@ -139,7 +146,7 @@ func (me *MatchEng) determineTakerSide(height int64, tradePriceIdx int) (int8, e
 
 	for i := len(me.overLappedLevel) - 1; i >= tradePriceIdx; i-- {
 		l := &me.overLappedLevel[i]
-		l.SellTakerStartIdx, l.SellMakerTotal = findTakerStartIdx(height, l.SellOrders)
+		l.SellTakerStartIdx, l.SellMakerTotal = findTakerStartIdx(me.LastMatchHeight, l.SellOrders)
 		if l.HasSellMaker() {
 			if makerSide == BUYSIDE {
 				return UNKNOWN, errors.New("both buy side and sell side have maker orders.")
diff --git a/plugins/dex/matcheng/match_new_test.go b/plugins/dex/matcheng/match_new_test.go
index 82b849cd8..4198fab33 100644
--- a/plugins/dex/matcheng/match_new_test.go
+++ b/plugins/dex/matcheng/match_new_test.go
@@ -485,8 +485,8 @@ func TestMatchEng_determineTakerSide(t *testing.T) {
 		assert.Equal(sellMakerTotal, l.SellMakerTotal)
 		l.BuyTakerStartIdx, l.BuyMakerTotal, l.SellTakerStartIdx, l.SellMakerTotal = 0, 0, 0, 0
 	}
-
-	takerSide, err := me.determineTakerSide(100, 0)
+	me.LastMatchHeight = 99
+	takerSide, err := me.determineTakerSide(0)
 	assert.NoError(err)
 	assert.Equal(BUYSIDE, takerSide)
 	checkAndClear(&me.overLappedLevel[0], 0, 0, 2, 200)
@@ -494,7 +494,7 @@ func TestMatchEng_determineTakerSide(t *testing.T) {
 	checkAndClear(&me.overLappedLevel[2], 0, 0, 0, 0)
 	checkAndClear(&me.overLappedLevel[3], 0, 0, 0, 0)
 
-	takerSide, err = me.determineTakerSide(100, 1)
+	takerSide, err = me.determineTakerSide(1)
 	assert.NoError(err)
 	assert.Equal(BUYSIDE, takerSide)
 	checkAndClear(&me.overLappedLevel[0], 0, 0, 0, 0)
@@ -502,7 +502,7 @@ func TestMatchEng_determineTakerSide(t *testing.T) {
 	checkAndClear(&me.overLappedLevel[2], 0, 0, 0, 0)
 	checkAndClear(&me.overLappedLevel[3], 0, 0, 0, 0)
 
-	takerSide, err = me.determineTakerSide(100, 2)
+	takerSide, err = me.determineTakerSide(2)
 	assert.NoError(err)
 	assert.Equal(SELLSIDE, takerSide)
 	checkAndClear(&me.overLappedLevel[0], 0, 0, 0, 0)
@@ -510,7 +510,7 @@ func TestMatchEng_determineTakerSide(t *testing.T) {
 	checkAndClear(&me.overLappedLevel[2], 2, 200, 0, 0)
 	checkAndClear(&me.overLappedLevel[3], 0, 0, 0, 0)
 
-	takerSide, err = me.determineTakerSide(100, 3)
+	takerSide, err = me.determineTakerSide(3)
 	assert.NoError(err)
 	assert.Equal(SELLSIDE, takerSide)
 	checkAndClear(&me.overLappedLevel[0], 0, 0, 0, 0)
@@ -527,7 +527,7 @@ func TestMatchEng_determineTakerSide(t *testing.T) {
 			{"2", 100, 100, 0, 100},
 		},
 	}}
-	takerSide, err = me.determineTakerSide(100, 0)
+	takerSide, err = me.determineTakerSide(0)
 	assert.NoError(err)
 	assert.Equal(BUYSIDE, takerSide)
 	checkAndClear(&me.overLappedLevel[0], 0, 0, 0, 0)
@@ -541,7 +541,7 @@ func TestMatchEng_determineTakerSide(t *testing.T) {
 			{"2", 99, 100, 0, 100},
 		},
 	}}
-	takerSide, err = me.determineTakerSide(100, 0)
+	takerSide, err = me.determineTakerSide(0)
 	assert.EqualError(err, "both buy side and sell side have maker orders.")
 	assert.Equal(UNKNOWN, takerSide)
 }
@@ -923,6 +923,7 @@ func TestMatchEng_Match(t *testing.T) {
 	me.Book.InsertOrder("16", BUYSIDE, 100, 100, 20)
 
 	upgrade.Mgr.SetHeight(100)
+	me.LastMatchHeight = 99
 	assert.True(me.Match(100))
 	assert.Equal(4, len(me.overLappedLevel))
 	assert.Equal(int64(100), me.LastTradePrice)
@@ -978,7 +979,7 @@ func TestMatchEng_Match(t *testing.T) {
 	me.Book.InsertOrder("15", SELLSIDE, 100, 100, 30)
 	me.Book.InsertOrder("6", BUYSIDE, 100, 110, 40)
 	me.Book.InsertOrder("8", BUYSIDE, 100, 110, 100)
-
+	me.LastMatchHeight = 99
 	assert.True(me.Match(100))
 	assert.Equal(4, len(me.overLappedLevel))
 	assert.Equal(int64(104), me.LastTradePrice)
diff --git a/plugins/dex/matcheng/orderbook.go b/plugins/dex/matcheng/orderbook.go
index 087757571..27450d3d7 100644
--- a/plugins/dex/matcheng/orderbook.go
+++ b/plugins/dex/matcheng/orderbook.go
@@ -20,7 +20,8 @@ type OrderBookInterface interface {
 	GetOrder(id string, side int8, price int64) (OrderPart, error)
 	RemoveOrder(id string, side int8, price int64) (OrderPart, error)
 	RemoveOrders(beforeTime int64, side int8, cb func(OrderPart)) error
-	UpdateForEachPriceLevel(side int8, updater func(*PriceLevel))
+	RemoveOrdersBasedOnPriceLevel(expireTime int64, forceExpireTime int64, priceLevelsToReserve int, side int8, removeCallback func(ord OrderPart)) error
+	UpdateForEachPriceLevel(side int8, updater LevelIter)
 	GetPriceLevel(price int64, side int8) *PriceLevel
 	RemovePriceLevel(price int64, side int8) int
 	ShowDepth(maxLevels int, iterBuy LevelIter, iterSell LevelIter)
@@ -130,14 +131,26 @@ func (ob *OrderBookOnULList) RemoveOrder(id string, side int8, price int64) (Ord
 }
 
 func (ob *OrderBookOnULList) RemoveOrders(beforeTime int64, side int8, cb func(OrderPart)) error {
-	ob.UpdateForEachPriceLevel(side, func(pl *PriceLevel) {
+	ob.UpdateForEachPriceLevel(side, func(pl *PriceLevel, levelIndex int) {
 		pl.removeOrders(beforeTime, cb)
 	})
 
 	return nil
 }
 
-func (ob *OrderBookOnULList) UpdateForEachPriceLevel(side int8, updater func(*PriceLevel)) {
+// order beyond priceLevelsToReserve will be expired if it's placed before expireTime. All orders will be expired if they are placed before forceExpireTime
+func (ob *OrderBookOnULList) RemoveOrdersBasedOnPriceLevel(expireTime int64, forceExpireTime int64, priceLevelsToReserve int, side int8, removeCallback func(ord OrderPart)) error {
+	ob.UpdateForEachPriceLevel(side, func(pl *PriceLevel, levelIndex int) {
+		if levelIndex < priceLevelsToReserve {
+			pl.removeOrders(forceExpireTime, removeCallback)
+		} else {
+			pl.removeOrders(expireTime, removeCallback)
+		}
+	})
+	return nil
+}
+
+func (ob *OrderBookOnULList) UpdateForEachPriceLevel(side int8, updater LevelIter) {
 	q := ob.getSideQueue(side)
 	q.UpdateForEach(updater)
 }
@@ -174,11 +187,11 @@ func (ob *OrderBookOnULList) GetAllLevels() ([]PriceLevel, []PriceLevel) {
 	buys := make([]PriceLevel, 0, ob.buyQueue.capacity)
 	sells := make([]PriceLevel, 0, ob.sellQueue.capacity)
 	ob.buyQueue.Iterate(ob.buyQueue.capacity,
-		func(p *PriceLevel) {
+		func(p *PriceLevel, levelIndex int) {
 			buys = append(buys, *p)
 		})
 	ob.sellQueue.Iterate(ob.sellQueue.capacity,
-		func(p *PriceLevel) {
+		func(p *PriceLevel, levelIndex int) {
 			sells = append(sells, *p)
 		})
 	return buys, sells
diff --git a/plugins/dex/matcheng/orderbook_test.go b/plugins/dex/matcheng/orderbook_test.go
index be8aaee28..1602d8996 100644
--- a/plugins/dex/matcheng/orderbook_test.go
+++ b/plugins/dex/matcheng/orderbook_test.go
@@ -558,6 +558,32 @@ func TestOrderBookOnULList_RemoveOrders(t *testing.T) {
 	require.Len(t, sells, 0)
 }
 
+func TestOrderBookOnULList_RemoveOrdersBiasedly(t *testing.T) {
+	book := NewOrderBookOnULList(16, 4)
+	book.InsertOrder("1", BUYSIDE, 10000, 1000, 10000)
+	book.InsertOrder("2", BUYSIDE, 10001, 1000, 10000)
+	book.InsertOrder("3", BUYSIDE, 10002, 1000, 10000)
+	err := book.RemoveOrdersBasedOnPriceLevel(9999, 10001, 10, BUYSIDE, nil)
+	require.NoError(t, err)
+	buys, sells := book.GetAllLevels()
+	//t.Log(buys)
+	//t.Log(sells)
+	require.Len(t, buys, 1)
+	require.Len(t, buys[0].Orders, 2)
+	require.Len(t, sells, 0)
+	err = book.RemoveOrdersBasedOnPriceLevel(10003, 10001, 10, BUYSIDE, nil)
+	require.NoError(t, err)
+	buys, sells = book.GetAllLevels()
+	require.Len(t, buys, 1)
+	require.Len(t, buys[0].Orders, 2)
+	require.Len(t, sells, 0)
+	err = book.RemoveOrdersBasedOnPriceLevel(10003, 10003, 10, BUYSIDE, nil)
+	require.NoError(t, err)
+	buys, sells = book.GetAllLevels()
+	require.Len(t, buys, 0)
+	require.Len(t, sells, 0)
+}
+
 func TestOrderBookOnULList_GetOverlappedRange(t *testing.T) {
 	overlap := make([]OverLappedLevel, 4)
 	buyBuf := make([]PriceLevel, 16)
diff --git a/plugins/dex/matcheng/types.go b/plugins/dex/matcheng/types.go
index c90180f5b..913c988f4 100644
--- a/plugins/dex/matcheng/types.go
+++ b/plugins/dex/matcheng/types.go
@@ -190,7 +190,7 @@ func (overlapped *OverLappedLevel) HasSellTaker() bool {
 	return overlapped.SellTakerStartIdx < len(overlapped.SellOrders)
 }
 
-type LevelIter func(*PriceLevel)
+type LevelIter func(priceLevel *PriceLevel, levelIndex int)
 
 type MergedPriceLevel struct {
 	price    int64
diff --git a/plugins/dex/matcheng/unrolledlinkedlist.go b/plugins/dex/matcheng/unrolledlinkedlist.go
index 80846e276..62279bec0 100644
--- a/plugins/dex/matcheng/unrolledlinkedlist.go
+++ b/plugins/dex/matcheng/unrolledlinkedlist.go
@@ -332,7 +332,7 @@ func (ull *ULList) Iterate(levelNum int, iter LevelIter) {
 			if curLevel >= levelNum {
 				return
 			}
-			iter(&b.elements[i])
+			iter(&b.elements[i], curLevel)
 			curLevel += 1
 		}
 	}
@@ -395,6 +395,7 @@ func (ull *ULList) GetPriceLevel(p int64) *PriceLevel {
 func (ull *ULList) UpdateForEach(updater LevelIter) {
 	b := ull.begin
 	var last *bucket
+	levelIndex := 0
 	for b != ull.dend {
 		for i := 0; ; {
 			k := len(b.elements)
@@ -404,7 +405,8 @@ func (ull *ULList) UpdateForEach(updater LevelIter) {
 				break
 			}
 			pl := &b.elements[i]
-			updater(pl)
+			updater(pl, levelIndex)
+			levelIndex++
 			if len(pl.Orders) == 0 {
 				b.deleteElement(i)
 			} else {
diff --git a/plugins/dex/matcheng/unrolledlinkedlist_test.go b/plugins/dex/matcheng/unrolledlinkedlist_test.go
index fb9371136..e6b4d5b9f 100644
--- a/plugins/dex/matcheng/unrolledlinkedlist_test.go
+++ b/plugins/dex/matcheng/unrolledlinkedlist_test.go
@@ -453,7 +453,7 @@ func TestULList_UpdateForEach(t *testing.T) {
 	l.AddPriceLevel(&PriceLevel{Price: 1002, Orders: []OrderPart{{Id: "2", Time: 10000}}})
 	l.AddPriceLevel(&PriceLevel{Price: 1003, Orders: []OrderPart{{Id: "3", Time: 10000}}})
 	l.AddPriceLevel(&PriceLevel{Price: 1001, Orders: []OrderPart{{Id: "4", Time: 10000}}})
-	l.UpdateForEach(func(pl *PriceLevel) {
+	l.UpdateForEach(func(pl *PriceLevel, levelIndex int) {
 		if pl.Price <= 1003 {
 			pl.Orders = pl.Orders[:0]
 		}
@@ -467,7 +467,7 @@ func TestULList_UpdateForEach(t *testing.T) {
 	l.AddPriceLevel(&PriceLevel{Price: 1002, Orders: []OrderPart{{Id: "2", Time: 10000}}})
 	l.AddPriceLevel(&PriceLevel{Price: 1003, Orders: []OrderPart{{Id: "3", Time: 10000}}})
 	l.AddPriceLevel(&PriceLevel{Price: 1001, Orders: []OrderPart{{Id: "4", Time: 10000}}})
-	l.UpdateForEach(func(pl *PriceLevel) {
+	l.UpdateForEach(func(pl *PriceLevel, levelIndex int) {
 		if pl.Price <= 1006 {
 			pl.Orders = pl.Orders[:0]
 		}
@@ -536,7 +536,7 @@ func TestULList_Iterate(t *testing.T) {
 				allBuckets: tt.fields.allBuckets,
 			}
 			result := make([]PriceLevel, 0)
-			fillRes := func(p *PriceLevel) {
+			fillRes := func(p *PriceLevel, levelIndex int) {
 				result = append(result, *p)
 			}
 			if ull.Iterate(tt.args.maxLevel, fillRes); !reflect.DeepEqual(result, tt.want) {
@@ -546,3 +546,62 @@ func TestULList_Iterate(t *testing.T) {
 		})
 	}
 }
+
+func TestULList_UpdateForEachBiasedly(t *testing.T) {
+	l := NewULList(5, 2, compareBuy)
+	l.AddPriceLevel(&PriceLevel{Price: 1006, Orders: []OrderPart{{Id: "1", Time: 10000}}})
+	l.AddPriceLevel(&PriceLevel{Price: 1002, Orders: []OrderPart{{Id: "2", Time: 10000}}})
+	l.AddPriceLevel(&PriceLevel{Price: 1003, Orders: []OrderPart{{Id: "3", Time: 10000}}})
+	l.AddPriceLevel(&PriceLevel{Price: 1001, Orders: []OrderPart{{Id: "4", Time: 10000}}})
+	l.UpdateForEach(func(pl *PriceLevel, levelIndex int) {
+		t.Logf("Iterate preferred item: %v", pl)
+		if pl.Price <= 1003 {
+			pl.Orders = pl.Orders[:0]
+		}
+	})
+	require.Len(t, l.begin.elements, 1)
+	require.Equal(t, l.dend, l.begin.next)
+	require.Equal(t, int64(1006), l.begin.elements[0].Price)
+
+	l = NewULList(5, 2, compareBuy)
+	l.AddPriceLevel(&PriceLevel{Price: 1006, Orders: []OrderPart{{Id: "1", Time: 10000}}})
+	l.AddPriceLevel(&PriceLevel{Price: 1002, Orders: []OrderPart{{Id: "2", Time: 10000}}})
+	l.AddPriceLevel(&PriceLevel{Price: 1003, Orders: []OrderPart{{Id: "3", Time: 10000}}})
+	l.AddPriceLevel(&PriceLevel{Price: 1001, Orders: []OrderPart{{Id: "4", Time: 10000}}})
+	l.UpdateForEach(func(pl *PriceLevel, levelIndex int) {
+		if levelIndex < 2 {
+			if pl.Price <= 1002 {
+				pl.Orders = pl.Orders[:0]
+			}
+		} else {
+			if pl.Price <= 1003 {
+				pl.Orders = pl.Orders[:0]
+			}
+		}
+	})
+	require.Len(t, l.begin.elements, 1)
+	require.Equal(t, l.dend, l.begin.next.next)
+	require.Equal(t, int64(1006), l.begin.elements[0].Price)
+	require.Equal(t, int64(1003), l.begin.next.elements[0].Price)
+
+	l = NewULList(5, 2, compareBuy)
+	l.AddPriceLevel(&PriceLevel{Price: 1006, Orders: []OrderPart{{Id: "1", Time: 10000}, {Id: "2", Time: 10000}}})
+	l.AddPriceLevel(&PriceLevel{Price: 1003, Orders: []OrderPart{{Id: "3", Time: 10000}, {Id: "4", Time: 10000}}})
+	l.AddPriceLevel(&PriceLevel{Price: 1002, Orders: []OrderPart{{Id: "5", Time: 10000}}})
+	l.AddPriceLevel(&PriceLevel{Price: 1001, Orders: []OrderPart{{Id: "6", Time: 10000}, {Id: "7", Time: 10000}, {Id: "8", Time: 10000}}})
+	l.UpdateForEach(func(pl *PriceLevel, levelIndex int) {
+		if levelIndex < 2 {
+			if len(pl.Orders) > 1 {
+				pl.Orders = pl.Orders[:1]
+			}
+		} else {
+			if len(pl.Orders) > 2 {
+				pl.Orders = pl.Orders[:2]
+			}
+		}
+	})
+	require.Len(t, l.begin.elements[0].Orders, 1)
+	require.Len(t, l.begin.elements[1].Orders, 1)
+	require.Len(t, l.begin.next.elements[0].Orders, 1)
+	require.Len(t, l.begin.next.elements[1].Orders, 2)
+}
diff --git a/plugins/dex/order/fee.go b/plugins/dex/order/fee.go
index 807363a65..c5674d8b5 100644
--- a/plugins/dex/order/fee.go
+++ b/plugins/dex/order/fee.go
@@ -6,6 +6,8 @@ import (
 	"math"
 	"math/big"
 
+	"github.com/binance-chain/node/common/upgrade"
+
 	sdk "github.com/cosmos/cosmos-sdk/types"
 
 	tmlog "github.com/tendermint/tendermint/libs/log"
@@ -47,7 +49,7 @@ type FeeManager struct {
 	FeeConfig FeeConfig
 }
 
-func NewFeeManager(cdc *wire.Codec, storeKey sdk.StoreKey, logger tmlog.Logger) *FeeManager {
+func NewFeeManager(cdc *wire.Codec, logger tmlog.Logger) *FeeManager {
 	return &FeeManager{
 		cdc:       cdc,
 		logger:    logger,
@@ -75,7 +77,7 @@ func (m *FeeManager) CalcTradesFee(balances sdk.Coins, tradeTransfers TradeTrans
 	}
 	tradeTransfers.Sort()
 	for _, tran := range tradeTransfers {
-		fee := m.calcTradeFeeForSingleTransfer(balances, tran, engines)
+		fee := m.calcTradeFeeFromTransfer(balances, tran, engines)
 		tran.Fee = fee
 		if tran.IsBuyer() {
 			tran.Trade.BuyerFee = &fee
@@ -106,62 +108,79 @@ func (m *FeeManager) CalcExpiresFee(balances sdk.Coins, expireType transferEvent
 	return fees
 }
 
-func (m *FeeManager) calcTradeFeeForSingleTransfer(balances sdk.Coins, tran *Transfer, engines map[string]*matcheng.MatchEng) types.Fee {
+func (m *FeeManager) calcTradeFeeFromTransfer(balances sdk.Coins, tran *Transfer, engines map[string]*matcheng.MatchEng) types.Fee {
 	var feeToken sdk.Coin
 
-	var nativeFee int64
-	var isOverflow bool
+	nativeFee, isOverflow := m.calcNativeFee(tran, engines)
 	if tran.IsNativeIn() {
-		// always have enough balance to pay the fee.
-		nativeFee = m.calcTradeFee(big.NewInt(tran.in), FeeByNativeToken).Int64()
-		return types.NewFee(sdk.Coins{sdk.NewCoin(types.NativeTokenSymbol, nativeFee)}, types.FeeForProposer)
-	} else if tran.IsNativeOut() {
-		nativeFee, isOverflow = m.calcNativeFee(types.NativeTokenSymbol, tran.out, engines)
-	} else {
-		nativeFee, isOverflow = m.calcNativeFee(tran.inAsset, tran.in, engines)
+		// special case, in this case, we always have
+		// 1. the fee is paid by native token
+		// 2. the balance is enough to pay the fee.
+		// 3. never have int64 overflow
+		return dexFeeWrap(sdk.NewCoin(types.NativeTokenSymbol, nativeFee))
 	}
 
 	if isOverflow || nativeFee == 0 || nativeFee > balances.AmountOf(types.NativeTokenSymbol) {
 		// 1. if the fee is too low and round to 0, we charge by inAsset
 		// 2. no enough NativeToken, use the received tokens as fee
-		feeToken = sdk.NewCoin(tran.inAsset, m.calcTradeFee(big.NewInt(tran.in), FeeByTradeToken).Int64())
+		feeToken = sdk.NewCoin(tran.inAsset, m.TradeFee(big.NewInt(tran.in), FeeByTradeToken).Int64())
 		m.logger.Debug("No enough native token to pay trade fee", "feeToken", feeToken)
 	} else {
 		// have sufficient native token to pay the fees
 		feeToken = sdk.NewCoin(types.NativeTokenSymbol, nativeFee)
 	}
-	return types.NewFee(sdk.Coins{feeToken}, types.FeeForProposer)
+	return dexFeeWrap(feeToken)
 }
 
-func (m *FeeManager) calcNativeFee(inSymbol string, inQty int64, engines map[string]*matcheng.MatchEng) (fee int64, isOverflow bool) {
-	var nativeNotional *big.Int
-	if isNativeToken(inSymbol) {
-		nativeNotional = big.NewInt(inQty)
+func (m *FeeManager) calcNativeFee(tran *Transfer, engines map[string]*matcheng.MatchEng) (fee int64, isOverflow bool) {
+	var nativeFee *big.Int
+	if tran.IsNativeIn() {
+		nativeFee = m.TradeFee(big.NewInt(tran.in), FeeByNativeToken)
+	} else if tran.IsNativeOut() {
+		nativeFee = m.TradeFee(big.NewInt(tran.out), FeeByNativeToken)
 	} else {
-		// price against native token,
-		// both `nativeNotional` and `feeByNativeToken` may overflow when it's a non-BNB pair like ABC_XYZ
-		if engine, ok := engines[utils.Assets2TradingPair(inSymbol, types.NativeTokenSymbol)]; ok {
-			// XYZ_BNB
-			nativeNotional = utils.CalBigNotional(engine.LastTradePrice, inQty)
-		} else {
-			// BNB_XYZ
-			engine := engines[utils.Assets2TradingPair(types.NativeTokenSymbol, inSymbol)]
-			var amount big.Int
-			nativeNotional = amount.Div(
-				amount.Mul(
-					big.NewInt(inQty),
-					big.NewInt(cmnUtils.Fixed8One.ToInt64())),
-				big.NewInt(engine.LastTradePrice))
+		// pair pattern: ABC_XYZ/XYZ_ABC, inAsset: ABC
+		// must exist ABC/BNB. or ABC/BUSD after upgrade
+		notional, pairExist := m.calcNotional(tran.inAsset, tran.in, types.NativeTokenSymbol, engines)
+		if !pairExist {
+			if sdk.IsUpgrade(upgrade.BEP70) && len(BUSDSymbol) > 0 {
+				// must be ABC_BUSD pair, we just use BUSD_BNB price to get the notional
+				var qty int64
+				if tran.inAsset == BUSDSymbol {
+					qty = tran.in
+				} else {
+					// outAsset is BUSD
+					qty = tran.out
+				}
+
+				notional, pairExist = m.calcNotional(BUSDSymbol, qty, types.NativeTokenSymbol, engines)
+				if pairExist {
+					// must not happen
+					m.logger.Error(BUSDSymbol + " must be listed against " + types.NativeTokenSymbol)
+				}
+			}
 		}
+		nativeFee = m.TradeFee(notional, FeeByNativeToken)
 	}
-
-	nativeFee := m.calcTradeFee(nativeNotional, FeeByNativeToken)
 	if nativeFee.IsInt64() {
 		return nativeFee.Int64(), false
 	}
 	return 0, true
 }
 
+func (m *FeeManager) calcNotional(asset string, qty int64, quoteAsset string, engines map[string]*matcheng.MatchEng) (notional *big.Int, engineFound bool) {
+	if engine, ok := m.getEngine(engines, asset, quoteAsset); ok {
+		notional = utils.CalBigNotional(engine.LastTradePrice, qty)
+	} else if engine, ok = m.getEngine(engines, quoteAsset, asset); ok {
+		var amt big.Int
+		notional = amt.Div(amt.Mul(big.NewInt(qty), big.NewInt(cmnUtils.Fixed8One.ToInt64())), big.NewInt(engine.LastTradePrice))
+	} else {
+		return notional, false
+	}
+
+	return notional, true
+}
+
 // DEPRECATED
 // Note1: the result of `CalcTradeFeeDeprecated` depends on the balances of the acc,
 // so the right way of allocation is:
@@ -177,7 +196,7 @@ func (m *FeeManager) CalcTradeFee(balances sdk.Coins, tradeIn sdk.Coin, engines
 	inSymbol := tradeIn.Denom
 	inAmt := tradeIn.Amount
 	if inSymbol == types.NativeTokenSymbol {
-		feeToken = sdk.NewCoin(types.NativeTokenSymbol, m.calcTradeFee(big.NewInt(inAmt), FeeByNativeToken).Int64())
+		feeToken = sdk.NewCoin(types.NativeTokenSymbol, m.TradeFee(big.NewInt(inAmt), FeeByNativeToken).Int64())
 	} else {
 		// price against native token,
 		// both `amountOfNativeToken` and `feeByNativeToken` may overflow when it's a non-BNB pair like ABC_XYZ
@@ -195,7 +214,7 @@ func (m *FeeManager) CalcTradeFee(balances sdk.Coins, tradeIn sdk.Coin, engines
 					big.NewInt(cmnUtils.Fixed8One.ToInt64())),
 				big.NewInt(market.LastTradePrice))
 		}
-		feeByNativeToken := m.calcTradeFee(amountOfNativeToken, FeeByNativeToken)
+		feeByNativeToken := m.TradeFee(amountOfNativeToken, FeeByNativeToken)
 		if feeByNativeToken.IsInt64() && feeByNativeToken.Int64() != 0 &&
 			feeByNativeToken.Int64() <= balances.AmountOf(types.NativeTokenSymbol) {
 			// 1. if the fee is too low and round to 0, we charge by inAsset
@@ -203,12 +222,12 @@ func (m *FeeManager) CalcTradeFee(balances sdk.Coins, tradeIn sdk.Coin, engines
 			feeToken = sdk.NewCoin(types.NativeTokenSymbol, feeByNativeToken.Int64())
 		} else {
 			// no enough NativeToken, use the received tokens as fee
-			feeToken = sdk.NewCoin(inSymbol, m.calcTradeFee(big.NewInt(inAmt), FeeByTradeToken).Int64())
+			feeToken = sdk.NewCoin(inSymbol, m.TradeFee(big.NewInt(inAmt), FeeByTradeToken).Int64())
 			m.logger.Debug("No enough native token to pay trade fee", "feeToken", feeToken)
 		}
 	}
 
-	return types.NewFee(sdk.Coins{feeToken}, types.FeeForProposer)
+	return dexFeeWrap(feeToken)
 }
 
 // Note: the result of `CalcFixedFee` depends on the balances of the acc,
@@ -231,50 +250,51 @@ func (m *FeeManager) CalcFixedFee(balances sdk.Coins, eventType transferEventTyp
 		return types.Fee{}
 	}
 
-	var feeToken sdk.Coin
 	nativeTokenBalance := balances.AmountOf(types.NativeTokenSymbol)
 	if nativeTokenBalance >= feeAmountNative || inAsset == types.NativeTokenSymbol {
-		feeToken = sdk.NewCoin(types.NativeTokenSymbol, cmnUtils.MinInt(feeAmountNative, nativeTokenBalance))
-	} else {
-		// the amount may overflow int64, so use big.Int instead.
-		// TODO: (perf) may remove the big.Int use to improve the performance
-		var amount *big.Int
-		if market, ok := engines[utils.Assets2TradingPair(inAsset, types.NativeTokenSymbol)]; ok {
-			// XYZ_BNB
-			var tmp big.Int
-			amount = tmp.Div(tmp.Mul(
-				big.NewInt(feeAmount),
-				big.NewInt(cmnUtils.Fixed8One.ToInt64())),
-				big.NewInt(market.LastTradePrice))
-		} else {
-			// BNB_XYZ
-			market = engines[utils.Assets2TradingPair(types.NativeTokenSymbol, inAsset)]
-			amount = utils.CalBigNotional(market.LastTradePrice, feeAmount)
-		}
+		return dexFeeWrap(sdk.NewCoin(types.NativeTokenSymbol, cmnUtils.MinInt(feeAmountNative, nativeTokenBalance)))
+	}
 
-		if amount.IsInt64() {
-			feeAmount = amount.Int64()
-		} else {
-			feeAmount = math.MaxInt64
+	// the amount may overflow int64, so use big.Int instead.
+	// TODO: (perf) may remove the big.Int use to improve the performance
+	amount, nativePairExist := m.calcNotional(types.NativeTokenSymbol, feeAmount, inAsset, engines)
+	if !nativePairExist {
+		// for BUSD pairs, it is possible that there is no trading pair between BNB and inAsset, e.g., BUSD -> XYZ
+		if sdk.IsUpgrade(upgrade.BEP70) && len(BUSDSymbol) > 0 {
+			busdAmount, busdPairExist := m.calcNotional(types.NativeTokenSymbol, feeAmount, BUSDSymbol, engines)
+			if busdPairExist {
+				var busdAmountInt64 int64
+				if !busdAmount.IsInt64() {
+					m.logger.Error("fixed fee is too high", "eventType", eventType, "fee", feeAmount)
+					busdAmountInt64 = math.MaxInt64
+				} else {
+					busdAmountInt64 = busdAmount.Int64()
+				}
+
+				var pairExist bool
+				amount, pairExist = m.calcNotional(BUSDSymbol, busdAmountInt64, inAsset, engines)
+				if !pairExist {
+					// must exist
+					m.logger.Error(inAsset + " must be listed against " + BUSDSymbol)
+				}
+			} else {
+				m.logger.Error(BUSDSymbol + " must be listed against " + types.NativeTokenSymbol)
+			}
 		}
-		feeAmount = cmnUtils.MinInt(feeAmount, balances.AmountOf(inAsset))
-		feeToken = sdk.NewCoin(inAsset, feeAmount)
 	}
 
-	return types.NewFee(sdk.Coins{feeToken}, types.FeeForProposer)
-}
-
-func (m *FeeManager) calcTradeFee(amount *big.Int, feeType FeeType) *big.Int {
-	var feeRate int64
-	if feeType == FeeByNativeToken {
-		feeRate = m.FeeConfig.FeeRateNative
-	} else if feeType == FeeByTradeToken {
-		feeRate = m.FeeConfig.FeeRate
+	if amount.IsInt64() {
+		feeAmount = amount.Int64()
+	} else {
+		feeAmount = math.MaxInt64
 	}
+	feeAmount = cmnUtils.MinInt(feeAmount, balances.AmountOf(inAsset))
+	return dexFeeWrap(sdk.NewCoin(inAsset, feeAmount))
+}
 
-	// TODO: (Perf) find a more efficient way to replace the big.Int solution.
-	var fee big.Int
-	return fee.Div(fee.Mul(amount, big.NewInt(feeRate)), FeeRateMultiplier)
+// for each trade, the fee only contains one kind of token. And distribution type is always FeeForProposer
+func dexFeeWrap(fee sdk.Coin) types.Fee {
+	return types.NewFee(sdk.Coins{fee}, types.FeeForProposer)
 }
 
 func isNativeToken(symbol string) bool {
@@ -293,6 +313,19 @@ func (m *FeeManager) CancelFees() (int64, int64) {
 	return m.FeeConfig.CancelFeeNative, m.FeeConfig.CancelFee
 }
 
+func (m *FeeManager) TradeFee(amount *big.Int, feeType FeeType) *big.Int {
+	var feeRate int64
+	if feeType == FeeByNativeToken {
+		feeRate = m.FeeConfig.FeeRateNative
+	} else if feeType == FeeByTradeToken {
+		feeRate = m.FeeConfig.FeeRate
+	}
+
+	// TODO: (Perf) find a more efficient way to replace the big.Int solution.
+	var fee big.Int
+	return fee.Div(fee.Mul(amount, big.NewInt(feeRate)), FeeRateMultiplier)
+}
+
 func (m *FeeManager) ExpireFee(feeType FeeType) int64 {
 	if feeType == FeeByNativeToken {
 		return m.FeeConfig.ExpireFeeNative
@@ -391,3 +424,9 @@ func ParamToFeeConfig(feeParams []param.FeeParam) *FeeConfig {
 	}
 	return nil
 }
+
+// Get engine for trading pair baseAsset_quoteAsset
+func (m *FeeManager) getEngine(engines map[string]*matcheng.MatchEng, baseAsset, quoteAsset string) (engine *matcheng.MatchEng, ok bool) {
+	engine, ok = engines[utils.Assets2TradingPair(baseAsset, quoteAsset)]
+	return
+}
diff --git a/plugins/dex/order/fee_test.go b/plugins/dex/order/fee_test.go
index dd6f3eea6..903bd89ea 100644
--- a/plugins/dex/order/fee_test.go
+++ b/plugins/dex/order/fee_test.go
@@ -26,78 +26,94 @@ func NewTestFeeConfig() FeeConfig {
 	return feeConfig
 }
 
-func TestFeeManager_calcTradeFeeForSingleTransfer(t *testing.T) {
+func feeManagerCalcTradeFeeForSingleTransfer(t *testing.T, symbol string) {
 	ctx, am, keeper := setup()
 	keeper.FeeManager.UpdateConfig(NewTestFeeConfig())
-	keeper.AddEngine(dextype.NewTradingPair("ABC-000", "BNB", 1e7))
+	keeper.AddEngine(dextype.NewTradingPair(symbol, "BNB", 1e7))
 	keeper.AddEngine(dextype.NewTradingPair("BNB", "XYZ-111", 1e7))
 	_, acc := testutils.NewAccount(ctx, am, 0)
 	tran := Transfer{
-		inAsset:  "ABC-000",
+		inAsset:  symbol,
 		in:       1000,
 		outAsset: "BNB",
 		out:      100,
 	}
 	// no enough bnb or native fee rounding to 0
-	fee := keeper.FeeManager.calcTradeFeeForSingleTransfer(acc.GetCoins(), &tran, keeper.engines)
-	require.Equal(t, sdk.Coins{{"ABC-000", 1}}, fee.Tokens)
+	fee := keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines)
+	require.Equal(t, sdk.Coins{{symbol, 1}}, fee.Tokens)
 	_, acc = testutils.NewAccount(ctx, am, 100)
-	fee = keeper.FeeManager.calcTradeFeeForSingleTransfer(acc.GetCoins(), &tran, keeper.engines)
-	require.Equal(t, sdk.Coins{{"ABC-000", 1}}, fee.Tokens)
+	fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines)
+	require.Equal(t, sdk.Coins{{symbol, 1}}, fee.Tokens)
 
 	tran = Transfer{
-		inAsset:  "ABC-000",
+		inAsset:  symbol,
 		in:       1000000,
 		outAsset: "BNB",
 		out:      10000,
 	}
 	_, acc = testutils.NewAccount(ctx, am, 1)
-	fee = keeper.FeeManager.calcTradeFeeForSingleTransfer(acc.GetCoins(), &tran, keeper.engines)
-	require.Equal(t, sdk.Coins{{"ABC-000", 1000}}, fee.Tokens)
+	fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines)
+	require.Equal(t, sdk.Coins{{symbol, 1000}}, fee.Tokens)
 	_, acc = testutils.NewAccount(ctx, am, 100)
-	fee = keeper.FeeManager.calcTradeFeeForSingleTransfer(acc.GetCoins(), &tran, keeper.engines)
+	fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines)
 	require.Equal(t, sdk.Coins{{"BNB", 5}}, fee.Tokens)
 
 	tran = Transfer{
 		inAsset:  "BNB",
 		in:       100,
-		outAsset: "ABC-000",
+		outAsset: symbol,
 		out:      1000,
 	}
 	_, acc = testutils.NewAccount(ctx, am, 100)
-	fee = keeper.FeeManager.calcTradeFeeForSingleTransfer(acc.GetCoins(), &tran, keeper.engines)
+	fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines)
 	require.Equal(t, sdk.Coins{{"BNB", 0}}, fee.Tokens)
 
 	tran = Transfer{
 		inAsset:  "BNB",
 		in:       10000,
-		outAsset: "ABC-000",
+		outAsset: symbol,
 		out:      100000,
 	}
-	fee = keeper.FeeManager.calcTradeFeeForSingleTransfer(acc.GetCoins(), &tran, keeper.engines)
+	fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines)
 	require.Equal(t, sdk.Coins{{"BNB", 5}}, fee.Tokens)
 
 	tran = Transfer{
-		inAsset:  "ABC-000",
+		inAsset:  symbol,
 		in:       100000,
 		outAsset: "XYZ-111",
 		out:      100000,
 	}
-	acc.SetCoins(sdk.Coins{{"ABC-000", 1000000}, {"BNB", 100}})
-	fee = keeper.FeeManager.calcTradeFeeForSingleTransfer(acc.GetCoins(), &tran, keeper.engines)
+	acc.SetCoins(sdk.Coins{{symbol, 1000000}, {"BNB", 100}})
+	fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines)
 	require.Equal(t, sdk.Coins{{"BNB", 5}}, fee.Tokens)
 	tran = Transfer{
 		inAsset:  "XYZ-111",
 		in:       100000,
-		outAsset: "ABC-000",
+		outAsset: symbol,
 		out:      100000,
 	}
 	acc.SetCoins(sdk.Coins{{"XYZ-111", 1000000}, {"BNB", 1000}})
-	fee = keeper.FeeManager.calcTradeFeeForSingleTransfer(acc.GetCoins(), &tran, keeper.engines)
+	fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines)
 	require.Equal(t, sdk.Coins{{"BNB", 500}}, fee.Tokens)
 }
 
+func TestFeeManager_calcTradeFeeForSingleTransfer(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	symbol := "ABC-000"
+	feeManagerCalcTradeFeeForSingleTransfer(t, symbol)
+}
+
+func TestFeeManager_calcTradeFeeForSingleTransferMini(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	symbol := "ABC-000M"
+	feeManagerCalcTradeFeeForSingleTransfer(t, symbol)
+}
+
 func TestFeeManager_CalcTradesFee(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
 	ctx, am, keeper := setup()
 	keeper.FeeManager.UpdateConfig(NewTestFeeConfig())
 	keeper.AddEngine(dextype.NewTradingPair("ABC-000", "BNB", 1e7))
@@ -106,6 +122,7 @@ func TestFeeManager_CalcTradesFee(t *testing.T) {
 	keeper.AddEngine(dextype.NewTradingPair("XYZ-111", "BTC", 2e4))
 	keeper.AddEngine(dextype.NewTradingPair("BNB", "BTC", 5e5))
 	keeper.AddEngine(dextype.NewTradingPair("ABC-000", "XYZ-111", 6e7))
+	keeper.AddEngine(dextype.NewTradingPair("ZYX-000M", "BNB", 1e8))
 
 	tradeTransfers := TradeTransfers{
 		{inAsset: "ABC-000", outAsset: "BNB", Oid: "1", in: 1e5, out: 2e4, Trade: &matcheng.Trade{}},
@@ -118,40 +135,48 @@ func TestFeeManager_CalcTradesFee(t *testing.T) {
 		{inAsset: "BNB", outAsset: "ABC-000", Oid: "8", in: 5e8, out: 60e8, Trade: &matcheng.Trade{}},
 		{inAsset: "ABC-000", outAsset: "BNB", Oid: "9", in: 7e6, out: 5e5, Trade: &matcheng.Trade{}},
 		{inAsset: "ABC-000", outAsset: "BTC", Oid: "10", in: 6e5, out: 8e1, Trade: &matcheng.Trade{}},
+		{inAsset: "ZYX-000M", outAsset: "BNB", Oid: "11", in: 2e7, out: 2e6, Trade: &matcheng.Trade{}},
 	}
 	_, acc := testutils.NewAccount(ctx, am, 0)
 	_ = acc.SetCoins(sdk.Coins{
 		{"ABC-000", 100e8},
 		{"BNB", 15251400},
 		{"BTC", 10e8},
-		{"XYZ-000", 100e8},
+		{"XYZ-111", 100e8},
+		{"ZYX-000M", 100e8},
 	})
 	fees := keeper.FeeManager.CalcTradesFee(acc.GetCoins(), tradeTransfers, keeper.engines)
-	require.Equal(t, "ABC-000:8000;BNB:15251305;BTC:100000;XYZ-111:2000", fees.String())
+	require.Equal(t, "ABC-000:8000;BNB:15251305;BTC:100000;XYZ-111:2000;ZYX-000M:20000", fees.String())
 	require.Equal(t, "BNB:250000", tradeTransfers[0].Fee.String())
 	require.Equal(t, "BNB:15000000", tradeTransfers[1].Fee.String())
 	require.Equal(t, "BNB:10", tradeTransfers[2].Fee.String())
 	require.Equal(t, "BNB:250", tradeTransfers[3].Fee.String())
 	require.Equal(t, "BTC:100000", tradeTransfers[4].Fee.String())
 	require.Equal(t, "BNB:1000", tradeTransfers[5].Fee.String())
-	require.Equal(t, "BNB:15", tradeTransfers[6].Fee.String())
-	require.Equal(t, "BNB:30", tradeTransfers[7].Fee.String())
-	require.Equal(t, "ABC-000:8000", tradeTransfers[8].Fee.String())
-	require.Equal(t, "XYZ-111:2000", tradeTransfers[9].Fee.String())
+	require.Equal(t, "ZYX-000M:20000", tradeTransfers[6].Fee.String())
+	require.Equal(t, "BNB:15", tradeTransfers[7].Fee.String())
+	require.Equal(t, "BNB:30", tradeTransfers[8].Fee.String())
+	require.Equal(t, "ABC-000:8000", tradeTransfers[9].Fee.String())
+	require.Equal(t, "XYZ-111:2000", tradeTransfers[10].Fee.String())
+
 	require.Equal(t, sdk.Coins{
 		{"ABC-000", 100e8},
 		{"BNB", 15251400},
 		{"BTC", 10e8},
-		{"XYZ-000", 100e8},
+		{"XYZ-111", 100e8},
+		{"ZYX-000M", 100e8},
 	}, acc.GetCoins())
 }
 
 func TestFeeManager_CalcExpiresFee(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
 	ctx, am, keeper := setup()
 	keeper.FeeManager.UpdateConfig(NewTestFeeConfig())
 	keeper.AddEngine(dextype.NewTradingPair("ABC-000", "BNB", 1e7))
 	keeper.AddEngine(dextype.NewTradingPair("XYZ-111", "BNB", 2e7))
 	keeper.AddEngine(dextype.NewTradingPair("BNB", "BTC", 5e5))
+	keeper.AddEngine(dextype.NewTradingPair("ZYX-000M", "BNB", 1e8))
 
 	// in BNB
 	expireTransfers := ExpireTransfers{
@@ -165,6 +190,7 @@ func TestFeeManager_CalcExpiresFee(t *testing.T) {
 		{inAsset: "BNB", Symbol: "ABC-000_BNB", Oid: "8"},
 		{inAsset: "ABC-000", Symbol: "ABC-000_BNB", Oid: "9"},
 		{inAsset: "ABC-000", Symbol: "ABC-000_BTC", Oid: "10"},
+		{inAsset: "ZYX-000M", Symbol: "ZYX-000M_BTC", Oid: "11"},
 	}
 	_, acc := testutils.NewAccount(ctx, am, 0)
 	_ = acc.SetCoins(sdk.Coins{
@@ -172,9 +198,10 @@ func TestFeeManager_CalcExpiresFee(t *testing.T) {
 		{"BNB", 120000},
 		{"BTC", 10e8},
 		{"XYZ-111", 800000},
+		{"ZYX-000M", 900000},
 	})
 	fees := keeper.FeeManager.CalcExpiresFee(acc.GetCoins(), eventFullyExpire, expireTransfers, keeper.engines, nil)
-	require.Equal(t, "ABC-000:1000000;BNB:120000;BTC:500;XYZ-111:800000", fees.String())
+	require.Equal(t, "ABC-000:1000000;BNB:120000;BTC:500;XYZ-111:800000;ZYX-000M:100000", fees.String())
 	require.Equal(t, "BNB:20000", expireTransfers[0].Fee.String())
 	require.Equal(t, "BNB:20000", expireTransfers[1].Fee.String())
 	require.Equal(t, "BNB:20000", expireTransfers[2].Fee.String())
@@ -185,18 +212,34 @@ func TestFeeManager_CalcExpiresFee(t *testing.T) {
 	require.Equal(t, "BTC:500", expireTransfers[7].Fee.String())
 	require.Equal(t, "XYZ-111:500000", expireTransfers[8].Fee.String())
 	require.Equal(t, "XYZ-111:300000", expireTransfers[9].Fee.String())
+	require.Equal(t, "ZYX-000M:100000", expireTransfers[10].Fee.String())
 	require.Equal(t, sdk.Coins{
 		{"ABC-000", 100e8},
 		{"BNB", 120000},
 		{"BTC", 10e8},
 		{"XYZ-111", 800000},
+		{"ZYX-000M", 900000},
 	}, acc.GetCoins())
 }
 
-func TestFeeManager_CalcTradeFee(t *testing.T) {
+func TestFeeManager_calcTradeFee(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	symbol := "ABC-000"
+	feeManagerCalcTradeFee(t, symbol)
+}
+
+func TestFeeManager_calcTradeFeeMini(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	symbol := "ABC-000M"
+	feeManagerCalcTradeFee(t, symbol)
+}
+
+func feeManagerCalcTradeFee(t *testing.T, symbol string) {
 	ctx, am, keeper := setup()
 	keeper.FeeManager.UpdateConfig(NewTestFeeConfig())
-	keeper.AddEngine(dextype.NewTradingPair("ABC-000", "BNB", 1e7))
+	keeper.AddEngine(dextype.NewTradingPair(symbol, "BNB", 1e7))
 	// BNB
 	_, acc := testutils.NewAccount(ctx, am, 0)
 	// the tradeIn amount is large enough to make the fee > 0
@@ -211,38 +254,54 @@ func TestFeeManager_CalcTradeFee(t *testing.T) {
 	// !BNB
 	_, acc = testutils.NewAccount(ctx, am, 100)
 	// has enough bnb
-	tradeIn = sdk.NewCoin("ABC-000", 1000e8)
+	tradeIn = sdk.NewCoin(symbol, 1000e8)
 	acc.SetCoins(sdk.Coins{sdk.NewCoin(types.NativeTokenSymbol, 1e8)})
 	fee = keeper.FeeManager.CalcTradeFee(acc.GetCoins(), tradeIn, keeper.engines)
 	require.Equal(t, sdk.Coins{sdk.NewCoin(types.NativeTokenSymbol, 5e6)}, fee.Tokens)
 	// no enough bnb
 	acc.SetCoins(sdk.Coins{sdk.NewCoin(types.NativeTokenSymbol, 1e6)})
 	fee = keeper.FeeManager.CalcTradeFee(acc.GetCoins(), tradeIn, keeper.engines)
-	require.Equal(t, sdk.Coins{sdk.NewCoin("ABC-000", 1e8)}, fee.Tokens)
+	require.Equal(t, sdk.Coins{sdk.NewCoin(symbol, 1e8)}, fee.Tokens)
 
 	// very high price to produce int64 overflow
-	keeper.AddEngine(dextype.NewTradingPair("ABC-000", "BNB", 1e16))
+	keeper.AddEngine(dextype.NewTradingPair(symbol, "BNB", 1e16))
 	// has enough bnb
-	tradeIn = sdk.NewCoin("ABC-000", 1000e8)
+	tradeIn = sdk.NewCoin(symbol, 1000e8)
 	acc.SetCoins(sdk.Coins{sdk.NewCoin(types.NativeTokenSymbol, 1e16)})
 	fee = keeper.FeeManager.CalcTradeFee(acc.GetCoins(), tradeIn, keeper.engines)
 	require.Equal(t, sdk.Coins{sdk.NewCoin(types.NativeTokenSymbol, 5e15)}, fee.Tokens)
 	// no enough bnb, fee is within int64
 	acc.SetCoins(sdk.Coins{sdk.NewCoin(types.NativeTokenSymbol, 1e15)})
 	fee = keeper.FeeManager.CalcTradeFee(acc.GetCoins(), tradeIn, keeper.engines)
-	require.Equal(t, sdk.Coins{sdk.NewCoin("ABC-000", 1e8)}, fee.Tokens)
+	require.Equal(t, sdk.Coins{sdk.NewCoin(symbol, 1e8)}, fee.Tokens)
 	// no enough bnb, even the fee overflows
-	tradeIn = sdk.NewCoin("ABC-000", 1e16)
+	tradeIn = sdk.NewCoin(symbol, 1e16)
 	fee = keeper.FeeManager.CalcTradeFee(acc.GetCoins(), tradeIn, keeper.engines)
-	require.Equal(t, sdk.Coins{sdk.NewCoin("ABC-000", 1e13)}, fee.Tokens)
+	require.Equal(t, sdk.Coins{sdk.NewCoin(symbol, 1e13)}, fee.Tokens)
 }
 
 func TestFeeManager_CalcFixedFee(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	symbol1 := "ABC-000"
+	symbol2 := "BTC-000"
+	feeManagerCalcFixedFee(t, symbol1, symbol2)
+}
+
+func TestFeeManager_CalcFixedFeeMini(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	symbol1 := "ABC-000M"
+	symbol2 := "BTC-000M"
+	feeManagerCalcFixedFee(t, symbol1, symbol2)
+}
+
+func feeManagerCalcFixedFee(t *testing.T, symbol1 string, symbol2 string) {
 	ctx, am, keeper := setup()
 	keeper.FeeManager.UpdateConfig(NewTestFeeConfig())
 	_, acc := testutils.NewAccount(ctx, am, 1e4)
-	keeper.AddEngine(dextype.NewTradingPair("ABC-000", "BNB", 1e7))
-	keeper.AddEngine(dextype.NewTradingPair("BNB", "BTC-000", 1e5))
+	keeper.AddEngine(dextype.NewTradingPair(symbol1, "BNB", 1e7))
+	keeper.AddEngine(dextype.NewTradingPair("BNB", symbol2, 1e5))
 	// in BNB
 	// no enough BNB, but inAsset == BNB
 	fee := keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, types.NativeTokenSymbol, keeper.engines)
@@ -259,30 +318,178 @@ func TestFeeManager_CalcFixedFee(t *testing.T) {
 	require.Equal(t, sdk.Coins{sdk.NewCoin(types.NativeTokenSymbol, 2e4)}, fee.Tokens)
 
 	// ABC-000_BNB, sell ABC-000
-	fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, "ABC-000", keeper.engines)
+	fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, symbol1, keeper.engines)
 	require.Equal(t, sdk.Coins{sdk.NewCoin(types.NativeTokenSymbol, 2e4)}, fee.Tokens)
 
 	// No enough native token, but enough ABC-000
-	acc.SetCoins(sdk.Coins{{Denom: types.NativeTokenSymbol, Amount: 1e4}, {Denom: "ABC-000", Amount: 1e8}})
-	fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, "ABC-000", keeper.engines)
-	require.Equal(t, sdk.Coins{sdk.NewCoin("ABC-000", 1e6)}, fee.Tokens)
+	acc.SetCoins(sdk.Coins{{Denom: types.NativeTokenSymbol, Amount: 1e4}, {Denom: symbol1, Amount: 1e8}})
+	fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, symbol1, keeper.engines)
+	require.Equal(t, sdk.Coins{sdk.NewCoin(symbol1, 1e6)}, fee.Tokens)
 
 	// No enough native token and ABC-000
-	acc.SetCoins(sdk.Coins{{Denom: types.NativeTokenSymbol, Amount: 1e4}, {Denom: "ABC-000", Amount: 1e5}})
-	fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, "ABC-000", keeper.engines)
-	require.Equal(t, sdk.Coins{sdk.NewCoin("ABC-000", 1e5)}, fee.Tokens)
+	acc.SetCoins(sdk.Coins{{Denom: types.NativeTokenSymbol, Amount: 1e4}, {Denom: symbol1, Amount: 1e5}})
+	fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, symbol1, keeper.engines)
+	require.Equal(t, sdk.Coins{sdk.NewCoin(symbol1, 1e5)}, fee.Tokens)
 
 	// BNB_BTC-000, sell BTC-000
-	acc.SetCoins(sdk.Coins{{Denom: "BTC-000", Amount: 1e4}})
-	fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, "BTC-000", keeper.engines)
-	require.Equal(t, sdk.Coins{sdk.NewCoin("BTC-000", 1e2)}, fee.Tokens)
+	acc.SetCoins(sdk.Coins{{Denom: symbol2, Amount: 1e4}})
+	fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, symbol2, keeper.engines)
+	require.Equal(t, sdk.Coins{sdk.NewCoin(symbol2, 1e2)}, fee.Tokens)
 
 	// extreme prices
-	keeper.AddEngine(dextype.NewTradingPair("ABC-000", "BNB", 1))
-	keeper.AddEngine(dextype.NewTradingPair("BNB", "BTC-000", 1e16))
-	acc.SetCoins(sdk.Coins{{Denom: "ABC-000", Amount: 1e16}, {Denom: "BTC-000", Amount: 1e16}})
+	keeper.AddEngine(dextype.NewTradingPair(symbol1, "BNB", 1))
+	keeper.AddEngine(dextype.NewTradingPair("BNB", symbol2, 1e16))
+	acc.SetCoins(sdk.Coins{{Denom: symbol1, Amount: 1e16}, {Denom: symbol2, Amount: 1e16}})
+	fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, symbol1, keeper.engines)
+	require.Equal(t, sdk.Coins{sdk.NewCoin(symbol1, 1e13)}, fee.Tokens)
+	fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, symbol2, keeper.engines)
+	require.Equal(t, sdk.Coins{sdk.NewCoin(symbol2, 1e13)}, fee.Tokens)
+}
+
+func TestFeeManager_calcTradeFeeForSingleTransfer_SupportBUSD(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	ctx, am, keeper := setup()
+	keeper.FeeManager.UpdateConfig(NewTestFeeConfig())
+	keeper.SetBUSDSymbol("BUSD-BD1")
+
+	// existing BNB -> BUSD trading pair
+	keeper.AddEngine(dextype.NewTradingPair("BNB", "BUSD-BD1", 1e5))
+	keeper.AddEngine(dextype.NewTradingPair("ABC-000", "BUSD-BD1", 1e7))
+	keeper.AddEngine(dextype.NewTradingPair("BUSD-BD1", "XYZ-999", 1e6))
+
+	// enough BNB, BNB will be collected
+	_, acc := testutils.NewAccount(ctx, am, 1e5)
+
+	// transferred in BNB
+	tran := Transfer{
+		inAsset: "BNB",
+		in:      2e3,
+	}
+	fee := keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines)
+	require.Equal(t, sdk.Coins{{"BNB", 1}}, fee.Tokens)
+
+	// transferred in BUSD-BD1
+	tran = Transfer{
+		inAsset:  "BUSD-BD1",
+		in:       1e3,
+		outAsset: "ABC-000",
+		out:      1e4,
+	}
+	fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines)
+	require.Equal(t, sdk.Coins{{"BNB", 5e2}}, fee.Tokens)
+
+	// transferred in ABC-000
+	tran = Transfer{
+		inAsset:  "ABC-000",
+		in:       1e3,
+		outAsset: "BUSD-BD1",
+		out:      100,
+	}
+	fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines)
+	require.Equal(t, sdk.Coins{{"BNB", 50}}, fee.Tokens)
+
+	// transferred in XYZ-999
+	tran = Transfer{
+		inAsset:  "XYZ-999",
+		in:       1e3,
+		outAsset: "BUSD-BD1",
+		out:      1e5,
+	}
+	fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines)
+	require.Equal(t, sdk.Coins{{"BNB", 5e4}}, fee.Tokens)
+
+	// existing BUSD -> BNB trading pair
+	ctx, am, keeper = setup()
+	keeper.FeeManager.UpdateConfig(NewTestFeeConfig())
+	keeper.AddEngine(dextype.NewTradingPair("BUSD-BD1", "BNB", 1e8))
+	keeper.AddEngine(dextype.NewTradingPair("ABC-000", "BUSD-BD1", 1e7))
+	keeper.AddEngine(dextype.NewTradingPair("BUSD-BD1", "XYZ-999", 1e6))
+
+	// enough BNB, BNB will be collected
+	_, acc = testutils.NewAccount(ctx, am, 1e10)
+
+	// transferred in BUSD-BD1
+	tran = Transfer{
+		inAsset:  "BUSD-BD1",
+		in:       1e4,
+		outAsset: "ABC-000",
+		out:      1e5,
+	}
+	fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines)
+	require.Equal(t, sdk.Coins{{"BNB", 5}}, fee.Tokens)
+
+	// transferred in ABC-000
+	tran = Transfer{
+		inAsset:  "ABC-000",
+		in:       1e6,
+		outAsset: "BUSD-BD1",
+		out:      1e5,
+	}
+	fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines)
+	require.Equal(t, sdk.Coins{{"BNB", 50}}, fee.Tokens)
+
+	// transferred in XYZ-999
+	tran = Transfer{
+		inAsset:  "XYZ-999",
+		in:       1e3,
+		outAsset: "BUSD-BD1",
+		out:      1e5,
+	}
+	fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines)
+	require.Equal(t, sdk.Coins{{"BNB", 50}}, fee.Tokens)
+}
+
+func TestFeeManager_CalcFixedFee_SupportBUSD(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	ctx, am, keeper := setup()
+	keeper.FeeManager.UpdateConfig(NewTestFeeConfig())
+	keeper.SetBUSDSymbol("BUSD-BD1")
+
+	// existing BNB -> BUSD trading pair
+	_, acc := testutils.NewAccount(ctx, am, 0)
+	keeper.AddEngine(dextype.NewTradingPair("BNB", "BUSD-BD1", 1e5))
+	keeper.AddEngine(dextype.NewTradingPair("ABC-000", "BUSD-BD1", 1e7))
+	keeper.AddEngine(dextype.NewTradingPair("BUSD-BD1", "XYZ-999", 1e6))
+
+	// no enough BNB, the transferred-in asset will be collected
+	// buy BUSD-BD1
+	acc.SetCoins(sdk.Coins{{Denom: "BUSD-BD1", Amount: 1e4}})
+	fee := keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, "BUSD-BD1", keeper.engines)
+	require.Equal(t, sdk.Coins{sdk.NewCoin("BUSD-BD1", 1e2)}, fee.Tokens)
+
+	// buy ABC-000
+	acc.SetCoins(sdk.Coins{{Denom: "ABC-000", Amount: 1e4}})
+	fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, "ABC-000", keeper.engines)
+	require.Equal(t, sdk.Coins{sdk.NewCoin("ABC-000", 1e3)}, fee.Tokens)
+
+	// buy XYZ-999
+	acc.SetCoins(sdk.Coins{{Denom: "XYZ-999", Amount: 1e4}})
+	fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, "XYZ-999", keeper.engines)
+	require.Equal(t, sdk.Coins{sdk.NewCoin("XYZ-999", 1)}, fee.Tokens)
+
+	// existing BUSD -> BNB trading pair
+	ctx, am, keeper = setup()
+	keeper.FeeManager.UpdateConfig(NewTestFeeConfig())
+	_, acc = testutils.NewAccount(ctx, am, 0)
+	keeper.AddEngine(dextype.NewTradingPair("BUSD-BD1", "BNB", 1e9))
+	keeper.AddEngine(dextype.NewTradingPair("ABC-000", "BUSD-BD1", 1e7))
+	keeper.AddEngine(dextype.NewTradingPair("BUSD-BD1", "XYZ-999", 1e6))
+
+	// no enough BNB, the transferred-in asset will be collected
+	// buy BUSD-BD1
+	acc.SetCoins(sdk.Coins{{Denom: "BUSD-BD1", Amount: 1e11}})
+	fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, "BUSD-BD1", keeper.engines)
+	require.Equal(t, sdk.Coins{sdk.NewCoin("BUSD-BD1", 1e4)}, fee.Tokens)
+
+	// buy ABC-000
+	acc.SetCoins(sdk.Coins{{Denom: "ABC-000", Amount: 1e10}})
 	fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, "ABC-000", keeper.engines)
-	require.Equal(t, sdk.Coins{sdk.NewCoin("ABC-000", 1e13)}, fee.Tokens)
-	fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, "BTC-000", keeper.engines)
-	require.Equal(t, sdk.Coins{sdk.NewCoin("BTC-000", 1e13)}, fee.Tokens)
+	require.Equal(t, sdk.Coins{sdk.NewCoin("ABC-000", 1e5)}, fee.Tokens)
+
+	// buy XYZ-999
+	acc.SetCoins(sdk.Coins{{Denom: "XYZ-999", Amount: 1e10}})
+	fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, "XYZ-999", keeper.engines)
+	require.Equal(t, sdk.Coins{sdk.NewCoin("XYZ-999", 1e2)}, fee.Tokens)
 }
diff --git a/plugins/dex/order/handler.go b/plugins/dex/order/handler.go
index fc9c09706..7903575a5 100644
--- a/plugins/dex/order/handler.go
+++ b/plugins/dex/order/handler.go
@@ -9,16 +9,13 @@ import (
 
 	"github.com/cosmos/cosmos-sdk/baseapp"
 	sdk "github.com/cosmos/cosmos-sdk/types"
-	"github.com/cosmos/cosmos-sdk/x/auth"
 
 	"github.com/binance-chain/node/common/fees"
 	common "github.com/binance-chain/node/common/types"
 	"github.com/binance-chain/node/common/upgrade"
 	me "github.com/binance-chain/node/plugins/dex/matcheng"
-	"github.com/binance-chain/node/plugins/dex/store"
 	"github.com/binance-chain/node/plugins/dex/types"
 	"github.com/binance-chain/node/plugins/dex/utils"
-	"github.com/binance-chain/node/wire"
 )
 
 type NewOrderResponse struct {
@@ -26,13 +23,13 @@ type NewOrderResponse struct {
 }
 
 // NewHandler - returns a handler for dex type messages.
-func NewHandler(cdc *wire.Codec, k *Keeper, accKeeper auth.AccountKeeper) sdk.Handler {
+func NewHandler(dexKeeper *DexKeeper) sdk.Handler {
 	return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
 		switch msg := msg.(type) {
 		case NewOrderMsg:
-			return handleNewOrder(ctx, cdc, k, msg)
+			return handleNewOrder(ctx, dexKeeper, msg)
 		case CancelOrderMsg:
-			return handleCancelOrder(ctx, k, msg)
+			return handleCancelOrder(ctx, dexKeeper, msg)
 		default:
 			errMsg := fmt.Sprintf("Unrecognized dex msg type: %v", reflect.TypeOf(msg).Name())
 			return sdk.ErrUnknownRequest(errMsg).Result()
@@ -40,51 +37,27 @@ func NewHandler(cdc *wire.Codec, k *Keeper, accKeeper auth.AccountKeeper) sdk.Ha
 	}
 }
 
-func validateOrder(ctx sdk.Context, pairMapper store.TradingPairMapper, acc sdk.Account, msg NewOrderMsg) error {
-	baseAsset, quoteAsset, err := utils.TradingPair2Assets(msg.Symbol)
-	if err != nil {
-		return err
-	}
-
-	seq := acc.GetSequence()
-	expectedID := GenerateOrderID(seq, msg.Sender)
-	if expectedID != msg.Id {
-		return fmt.Errorf("the order ID(%s) given did not match the expected one: `%s`", msg.Id, expectedID)
-	}
-
-	pair, err := pairMapper.GetTradingPair(ctx, baseAsset, quoteAsset)
-	if err != nil {
-		return err
-	}
-
-	if msg.Quantity <= 0 || msg.Quantity%pair.LotSize.ToInt64() != 0 {
-		return fmt.Errorf("quantity(%v) is not rounded to lotSize(%v)", msg.Quantity, pair.LotSize.ToInt64())
-	}
-
-	if msg.Price <= 0 || msg.Price%pair.TickSize.ToInt64() != 0 {
-		return fmt.Errorf("price(%v) is not rounded to tickSize(%v)", msg.Price, pair.TickSize.ToInt64())
-	}
-
-	if sdk.IsUpgrade(upgrade.LotSizeOptimization) {
-		if utils.IsUnderMinNotional(msg.Price, msg.Quantity) {
-			return errors.New("notional value of the order is too small")
-		}
-	}
-
-	if utils.IsExceedMaxNotional(msg.Price, msg.Quantity) {
-		return errors.New("notional value of the order is too large(cannot fit in int64)")
-	}
-
-	return nil
-}
-
-func validateQtyAndLockBalance(ctx sdk.Context, keeper *Keeper, acc common.NamedAccount, msg NewOrderMsg) error {
+func validateQtyAndLockBalance(ctx sdk.Context, keeper *DexKeeper, acc common.NamedAccount, msg NewOrderMsg) error {
 	symbol := strings.ToUpper(msg.Symbol)
 	baseAssetSymbol, quoteAssetSymbol := utils.TradingPair2AssetsSafe(symbol)
 	notional := utils.CalBigNotionalInt64(msg.Price, msg.Quantity)
 
 	// note: the check sequence is well designed.
 	freeBalance := acc.GetCoins()
+
+	if sdk.IsUpgrade(sdk.BEP8) && isMiniSymbolPair(baseAssetSymbol, quoteAssetSymbol) {
+		var quantityBigEnough bool
+		if msg.Side == Side.BUY {
+			quantityBigEnough = msg.Quantity >= common.MiniTokenMinExecutionAmount
+		} else if msg.Side == Side.SELL {
+			quantityBigEnough = (msg.Quantity >= common.MiniTokenMinExecutionAmount) || freeBalance.AmountOf(symbol) == msg.Quantity
+		}
+		if !quantityBigEnough {
+			return fmt.Errorf("quantity is too small, the min quantity is %d or total free balance of the mini token",
+				common.MiniTokenMinExecutionAmount)
+		}
+	}
+
 	var toLockCoins sdk.Coins
 	if msg.Side == Side.BUY {
 		// for buy orders,
@@ -126,16 +99,14 @@ func validateQtyAndLockBalance(ctx sdk.Context, keeper *Keeper, acc common.Named
 }
 
 func handleNewOrder(
-	ctx sdk.Context, cdc *wire.Codec, keeper *Keeper, msg NewOrderMsg,
+	ctx sdk.Context, dexKeeper *DexKeeper, msg NewOrderMsg,
 ) sdk.Result {
-	// TODO: the below is mostly copied from FreezeToken. It should be rewritten once "locked" becomes a field on account
-	// this check costs least.
-	if _, ok := keeper.OrderExists(msg.Symbol, msg.Id); ok {
+	if _, ok := dexKeeper.OrderExists(msg.Symbol, msg.Id); ok {
 		errString := fmt.Sprintf("Duplicated order [%v] on symbol [%v]", msg.Id, msg.Symbol)
 		return sdk.NewError(types.DefaultCodespace, types.CodeDuplicatedOrder, errString).Result()
 	}
 
-	acc := keeper.am.GetAccount(ctx, msg.Sender).(common.NamedAccount)
+	acc := dexKeeper.am.GetAccount(ctx, msg.Sender).(common.NamedAccount)
 	if !ctx.IsReCheckTx() {
 		//for recheck:
 		// 1. sequence is verified in anteHandler
@@ -143,14 +114,15 @@ func handleNewOrder(
 		// 3. trading pair is verified
 		// 4. price/qty may have odd tick size/lot size, but it can be handled as
 		//    other existing orders.
-		err := validateOrder(ctx, keeper.PairMapper, acc, msg)
+		err := validateOrder(ctx, dexKeeper, acc, msg)
+
 		if err != nil {
 			return sdk.NewError(types.DefaultCodespace, types.CodeInvalidOrderParam, err.Error()).Result()
 		}
 	}
 
 	// the following is done in the app's checkstate / deliverstate, so it's safe to ignore isCheckTx
-	err := validateQtyAndLockBalance(ctx, keeper, acc, msg)
+	err := validateQtyAndLockBalance(ctx, dexKeeper, acc, msg)
 	if err != nil {
 		return sdk.NewError(types.DefaultCodespace, types.CodeInvalidOrderParam, err.Error()).Result()
 	}
@@ -168,7 +140,7 @@ func handleNewOrder(
 				if txSrc, ok := ctx.Value(baseapp.TxSourceKey).(int64); ok {
 					txSource = txSrc
 				} else {
-					keeper.logger.Error("cannot get txSource from ctx")
+					dexKeeper.logger.Error("cannot get txSource from ctx")
 				}
 			})
 			msg := OrderInfo{
@@ -176,7 +148,9 @@ func handleNewOrder(
 				height, timestamp,
 				height, timestamp,
 				0, txHash, txSource}
-			err := keeper.AddOrder(msg, false)
+
+			err := dexKeeper.AddOrder(msg, false)
+
 			if err != nil {
 				return sdk.NewError(types.DefaultCodespace, types.CodeFailInsertOrder, err.Error()).Result()
 			}
@@ -200,9 +174,9 @@ func handleNewOrder(
 
 // Handle CancelOffer -
 func handleCancelOrder(
-	ctx sdk.Context, keeper *Keeper, msg CancelOrderMsg,
+	ctx sdk.Context, dexKeeper *DexKeeper, msg CancelOrderMsg,
 ) sdk.Result {
-	origOrd, ok := keeper.OrderExists(msg.Symbol, msg.RefId)
+	origOrd, ok := dexKeeper.OrderExists(msg.Symbol, msg.RefId)
 
 	//only check whether there exists order to cancel
 	if !ok {
@@ -216,21 +190,21 @@ func handleCancelOrder(
 		return sdk.NewError(types.DefaultCodespace, types.CodeFailLocateOrderToCancel, errString).Result()
 	}
 
-	ord, err := keeper.GetOrder(origOrd.Id, origOrd.Symbol, origOrd.Side, origOrd.Price)
+	ord, err := dexKeeper.GetOrder(origOrd.Id, origOrd.Symbol, origOrd.Side, origOrd.Price)
 	if err != nil {
 		return sdk.NewError(types.DefaultCodespace, types.CodeFailLocateOrderToCancel, err.Error()).Result()
 	}
 	transfer := TransferFromCanceled(ord, origOrd, false)
-	sdkError := keeper.doTransfer(ctx, &transfer)
+	sdkError := dexKeeper.doTransfer(ctx, &transfer)
 	if sdkError != nil {
 		return sdkError.Result()
 	}
 	fee := common.Fee{}
 	if !transfer.FeeFree() {
-		acc := keeper.am.GetAccount(ctx, msg.Sender)
-		fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), transfer.eventType, transfer.inAsset, keeper.engines)
+		acc := dexKeeper.am.GetAccount(ctx, msg.Sender)
+		fee = dexKeeper.FeeManager.CalcFixedFee(acc.GetCoins(), transfer.eventType, transfer.inAsset, dexKeeper.GetEngines())
 		acc.SetCoins(acc.GetCoins().Minus(fee.Tokens))
-		keeper.am.SetAccount(ctx, acc)
+		dexKeeper.am.SetAccount(ctx, acc)
 	}
 
 	// this is done in memory! we must not run this block in checktx or simulate!
@@ -242,11 +216,11 @@ func handleCancelOrder(
 			fees.Pool.AddFee(txHash, fee)
 		}
 		//remove order from cache and order book
-		err := keeper.RemoveOrder(origOrd.Id, origOrd.Symbol, func(ord me.OrderPart) {
-			if keeper.CollectOrderInfoForPublish {
+		err := dexKeeper.RemoveOrder(origOrd.Id, origOrd.Symbol, func(ord me.OrderPart) {
+			if dexKeeper.ShouldPublishOrder() {
 				change := OrderChange{msg.RefId, Canceled, fee.String(), nil}
-				keeper.OrderChanges = append(keeper.OrderChanges, change)
-				keeper.updateRoundOrderFee(string(msg.Sender), fee)
+				dexKeeper.UpdateOrderChangeSync(change, msg.Symbol)
+				dexKeeper.updateRoundOrderFee(string(msg.Sender), fee)
 			}
 		})
 		if err != nil {
@@ -256,3 +230,41 @@ func handleCancelOrder(
 
 	return sdk.Result{}
 }
+
+func validateOrder(ctx sdk.Context, dexKeeper *DexKeeper, acc sdk.Account, msg NewOrderMsg) error {
+	baseAsset, quoteAsset, err := utils.TradingPair2Assets(msg.Symbol)
+	if err != nil {
+		return err
+	}
+
+	seq := acc.GetSequence()
+	expectedID := GenerateOrderID(seq, msg.Sender)
+	if expectedID != msg.Id {
+		return fmt.Errorf("the order ID(%s) given did not match the expected one: `%s`", msg.Id, expectedID)
+	}
+
+	pair, err := dexKeeper.PairMapper.GetTradingPair(ctx, baseAsset, quoteAsset)
+	if err != nil {
+		return err
+	}
+
+	if msg.Quantity <= 0 || msg.Quantity%pair.LotSize.ToInt64() != 0 {
+		return fmt.Errorf("quantity(%v) is not rounded to lotSize(%v)", msg.Quantity, pair.LotSize.ToInt64())
+	}
+
+	if msg.Price <= 0 || msg.Price%pair.TickSize.ToInt64() != 0 {
+		return fmt.Errorf("price(%v) is not rounded to tickSize(%v)", msg.Price, pair.TickSize.ToInt64())
+	}
+
+	if sdk.IsUpgrade(upgrade.LotSizeOptimization) {
+		if utils.IsUnderMinNotional(msg.Price, msg.Quantity) {
+			return errors.New("notional value of the order is too small")
+		}
+	}
+
+	if utils.IsExceedMaxNotional(msg.Price, msg.Quantity) {
+		return errors.New("notional value of the order is too large(cannot fit in int64)")
+	}
+
+	return nil
+}
diff --git a/plugins/dex/order/handler_test.go b/plugins/dex/order/handler_test.go
index f64935567..4c0eace20 100644
--- a/plugins/dex/order/handler_test.go
+++ b/plugins/dex/order/handler_test.go
@@ -16,22 +16,24 @@ import (
 	"github.com/binance-chain/node/common"
 	"github.com/binance-chain/node/plugins/dex/store"
 	"github.com/binance-chain/node/plugins/dex/types"
+	dextypes "github.com/binance-chain/node/plugins/dex/types"
 	"github.com/binance-chain/node/wire"
 )
 
-func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey, *sdk.KVStoreKey) {
+func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey, *sdk.KVStoreKey, *sdk.KVStoreKey) {
 	db := dbm.NewMemDB()
 	key := sdk.NewKVStoreKey("pair") // TODO: can this be "pairs" as in the constant?
 	key2 := sdk.NewKVStoreKey(common.AccountStoreName)
+	key3 := sdk.NewKVStoreKey(common.DexStoreName)
 	ms := cstore.NewCommitMultiStore(db)
 	ms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db)
 	ms.MountStoreWithDB(key2, sdk.StoreTypeIAVL, db)
 	ms.LoadLatestVersion()
-	return ms, key, key2
+	return ms, key, key2, key3
 }
 
-func setupMappers() (store.TradingPairMapper, auth.AccountKeeper, sdk.Context) {
-	ms, key, key2 := setupMultiStore()
+func setupMappers() (store.TradingPairMapper, auth.AccountKeeper, sdk.Context, *DexKeeper) {
+	ms, key, key2, key3 := setupMultiStore()
 	var cdc = wire.NewCodec()
 	auth.RegisterBaseAccount(cdc)
 	cdc.RegisterConcrete(types.TradingPair{}, "dex/TradingPair", nil)
@@ -39,7 +41,8 @@ func setupMappers() (store.TradingPairMapper, auth.AccountKeeper, sdk.Context) {
 	accMapper := auth.NewAccountKeeper(cdc, key2, auth.ProtoBaseAccount)
 	accountCache := getAccountCache(cdc, ms, key2)
 	ctx := sdk.NewContext(ms, abci.Header{}, sdk.RunTxModeDeliver, log.NewNopLogger()).WithAccountCache(accountCache)
-	return pairMapper, accMapper, ctx
+	keeper := NewDexKeeper(key3, accMapper, pairMapper, sdk.NewCodespacer().RegisterNext(dextypes.DefaultCodespace), 2, cdc, false)
+	return pairMapper, accMapper, ctx, keeper
 }
 
 func setupAccount(ctx sdk.Context, accMapper auth.AccountKeeper) (sdk.Account, sdk.AccAddress) {
@@ -61,7 +64,7 @@ func setupAccount(ctx sdk.Context, accMapper auth.AccountKeeper) (sdk.Account, s
 }
 
 func TestHandler_ValidateOrder_OrderNotExist(t *testing.T) {
-	pairMapper, accMapper, ctx := setupMappers()
+	pairMapper, accMapper, ctx, keeper := setupMappers()
 	pair := types.NewTradingPair("AAA-000", "BNB", 1e8)
 	err := pairMapper.AddTradingPair(ctx, pair)
 	require.NoError(t, err)
@@ -76,13 +79,13 @@ func TestHandler_ValidateOrder_OrderNotExist(t *testing.T) {
 		Id:       fmt.Sprintf("%X-0", acc.GetAddress()),
 	}
 
-	err = validateOrder(ctx, pairMapper, acc, msg)
+	err = validateOrder(ctx, keeper, acc, msg)
 	require.Error(t, err)
 	require.Equal(t, fmt.Sprintf("trading pair not found: %s", msg.Symbol), err.Error())
 }
 
 func TestHandler_ValidateOrder_WrongSymbol(t *testing.T) {
-	pairMapper, _, ctx := setupMappers()
+	_, _, ctx, keeper := setupMappers()
 
 	msgs := []NewOrderMsg{
 		{
@@ -103,14 +106,14 @@ func TestHandler_ValidateOrder_WrongSymbol(t *testing.T) {
 	}
 
 	for _, msg := range msgs {
-		err := validateOrder(ctx, pairMapper, nil, msg)
+		err := validateOrder(ctx, keeper, nil, msg)
 		require.Error(t, err)
 		require.Equal(t, fmt.Sprintf("Failed to parse trading pair symbol:%s into assets", msg.Symbol), err.Error())
 	}
 }
 
 func TestHandler_ValidateOrder_WrongPrice(t *testing.T) {
-	pairMapper, accMapper, ctx := setupMappers()
+	pairMapper, accMapper, ctx, keeper := setupMappers()
 	pair := types.NewTradingPair("AAA-000", "BNB", 1e8)
 	err := pairMapper.AddTradingPair(ctx, pair)
 	require.NoError(t, err)
@@ -125,13 +128,13 @@ func TestHandler_ValidateOrder_WrongPrice(t *testing.T) {
 		Id:       fmt.Sprintf("%X-0", acc.GetAddress()),
 	}
 
-	err = validateOrder(ctx, pairMapper, acc, msg)
+	err = validateOrder(ctx, keeper, acc, msg)
 	require.Error(t, err)
 	require.Equal(t, fmt.Sprintf("price(%v) is not rounded to tickSize(%v)", msg.Price, pair.TickSize.ToInt64()), err.Error())
 }
 
 func TestHandler_ValidateOrder_WrongQuantity(t *testing.T) {
-	pairMapper, accMapper, ctx := setupMappers()
+	pairMapper, accMapper, ctx, keeper := setupMappers()
 	pair := types.NewTradingPair("AAA-000", "BNB", 1e8)
 	err := pairMapper.AddTradingPair(ctx, pair)
 	require.NoError(t, err)
@@ -146,13 +149,13 @@ func TestHandler_ValidateOrder_WrongQuantity(t *testing.T) {
 		Id:       fmt.Sprintf("%X-0", acc.GetAddress()),
 	}
 
-	err = validateOrder(ctx, pairMapper, acc, msg)
+	err = validateOrder(ctx, keeper, acc, msg)
 	require.Error(t, err)
 	require.Equal(t, fmt.Sprintf("quantity(%v) is not rounded to lotSize(%v)", msg.Quantity, pair.LotSize.ToInt64()), err.Error())
 }
 
 func TestHandler_ValidateOrder_Normal(t *testing.T) {
-	pairMapper, accMapper, ctx := setupMappers()
+	pairMapper, accMapper, ctx, keeper := setupMappers()
 	err := pairMapper.AddTradingPair(ctx, types.NewTradingPair("AAA-000", "BNB", 1e8))
 	require.NoError(t, err)
 
@@ -166,12 +169,12 @@ func TestHandler_ValidateOrder_Normal(t *testing.T) {
 		Id:       fmt.Sprintf("%X-0", acc.GetAddress()),
 	}
 
-	err = validateOrder(ctx, pairMapper, acc, msg)
+	err = validateOrder(ctx, keeper, acc, msg)
 	require.NoError(t, err)
 }
 
 func TestHandler_ValidateOrder_MaxNotional(t *testing.T) {
-	pairMapper, accMapper, ctx := setupMappers()
+	pairMapper, accMapper, ctx, keeper := setupMappers()
 	err := pairMapper.AddTradingPair(ctx, types.NewTradingPair("AAA-000", "BNB", 1e8))
 	require.NoError(t, err)
 
@@ -185,7 +188,7 @@ func TestHandler_ValidateOrder_MaxNotional(t *testing.T) {
 		Id:       fmt.Sprintf("%X-0", acc.GetAddress()),
 	}
 
-	err = validateOrder(ctx, pairMapper, acc, msg)
+	err = validateOrder(ctx, keeper, acc, msg)
 	require.Error(t, err)
 	require.Equal(t, "notional value of the order is too large(cannot fit in int64)", err.Error())
 }
diff --git a/plugins/dex/order/keeper.go b/plugins/dex/order/keeper.go
index c50ecfba2..b90c84e5f 100644
--- a/plugins/dex/order/keeper.go
+++ b/plugins/dex/order/keeper.go
@@ -4,17 +4,18 @@ import (
 	"errors"
 	"fmt"
 	"math"
+	"math/big"
 	"strings"
 	"sync"
 	"time"
 
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/cosmos/cosmos-sdk/x/auth"
+
 	dbm "github.com/tendermint/tendermint/libs/db"
 	tmlog "github.com/tendermint/tendermint/libs/log"
 	tmstore "github.com/tendermint/tendermint/store"
 
-	sdk "github.com/cosmos/cosmos-sdk/types"
-	"github.com/cosmos/cosmos-sdk/x/auth"
-
 	"github.com/binance-chain/node/common/fees"
 	bnclog "github.com/binance-chain/node/common/log"
 	"github.com/binance-chain/node/common/types"
@@ -30,84 +31,127 @@ import (
 )
 
 const (
-	numPricesStored  = 2000
-	pricesStoreEvery = 1000
-	minimalNumPrices = 500
+	BEP2TypeValue        = 1
+	MiniTypeValue        = 2
+	preferencePriceLevel = 500
 )
 
+type SymbolPairType int8
+
+var PairType = struct {
+	BEP2 SymbolPairType
+	MINI SymbolPairType
+}{BEP2TypeValue, MiniTypeValue}
+
+var BUSDSymbol string
+
 type FeeHandler func(map[string]*types.Fee)
 type TransferHandler func(Transfer)
 
-// in the future, this may be distributed via Sharding
-type Keeper struct {
+type DexKeeper struct {
 	PairMapper                 store.TradingPairMapper
-	am                         auth.AccountKeeper
 	storeKey                   sdk.StoreKey // The key used to access the store from the Context.
 	codespace                  sdk.CodespaceType
-	engines                    map[string]*me.MatchEng
-	recentPrices               map[string]*utils.FixedSizeRing  // symbol -> latest "numPricesStored" prices per "pricesStoreEvery" blocks
-	allOrders                  map[string]map[string]*OrderInfo // symbol -> order ID -> order
-	OrderChangesMtx            *sync.Mutex                      // guard OrderChanges and OrderInfosForPub during PreDevlierTx (which is async)
-	OrderChanges               OrderChanges                     // order changed in this block, will be cleaned before matching for new block
-	OrderInfosForPub           OrderInfoForPublish              // for publication usage
-	roundOrders                map[string][]string              // limit to the total tx number in a block
-	roundIOCOrders             map[string][]string
-	RoundOrderFees             FeeHolder // order (and trade) related fee of this round, str of addr bytes -> fee
-	poolSize                   uint      // number of concurrent channels, counted in the pow of 2
-	cdc                        *wire.Codec
+	recentPrices               map[string]*utils.FixedSizeRing // symbol -> latest "numPricesStored" prices per "pricesStoreEvery" blocks
+	am                         auth.AccountKeeper
 	FeeManager                 *FeeManager
-	CollectOrderInfoForPublish bool
+	RoundOrderFees             FeeHolder // order (and trade) related fee of this round, str of addr bytes -> fee
+	CollectOrderInfoForPublish bool      //TODO separate for each order keeper
+	engines                    map[string]*me.MatchEng
+	pairsType                  map[string]SymbolPairType
 	logger                     tmlog.Logger
+	poolSize                   uint // number of concurrent channels, counted in the pow of 2
+	cdc                        *wire.Codec
+	OrderKeepers               []DexOrderKeeper
 }
 
-func CreateMatchEng(pairSymbol string, basePrice, lotSize int64) *me.MatchEng {
-	return me.NewMatchEng(pairSymbol, basePrice, lotSize, 0.05)
-}
-
-// NewKeeper - Returns the Keeper
-func NewKeeper(key sdk.StoreKey, am auth.AccountKeeper, tradingPairMapper store.TradingPairMapper, codespace sdk.CodespaceType,
-	concurrency uint, cdc *wire.Codec, collectOrderInfoForPublish bool) *Keeper {
+func NewDexKeeper(key sdk.StoreKey, am auth.AccountKeeper, tradingPairMapper store.TradingPairMapper, codespace sdk.CodespaceType, concurrency uint, cdc *wire.Codec, collectOrderInfoForPublish bool) *DexKeeper {
 	logger := bnclog.With("module", "dexkeeper")
-	return &Keeper{
+	bep2OrderKeeper, miniOrderKeeper := NewBEP2OrderKeeper(), NewMiniOrderKeeper()
+	if collectOrderInfoForPublish {
+		bep2OrderKeeper.enablePublish()
+		miniOrderKeeper.enablePublish()
+	}
+
+	return &DexKeeper{
 		PairMapper:                 tradingPairMapper,
-		am:                         am,
 		storeKey:                   key,
 		codespace:                  codespace,
-		engines:                    make(map[string]*me.MatchEng),
 		recentPrices:               make(map[string]*utils.FixedSizeRing, 256),
-		allOrders:                  make(map[string]map[string]*OrderInfo, 256), // need to init the nested map when a new symbol added.
-		OrderChangesMtx:            &sync.Mutex{},
-		OrderChanges:               make(OrderChanges, 0),
-		OrderInfosForPub:           make(OrderInfoForPublish),
-		roundOrders:                make(map[string][]string, 256),
-		roundIOCOrders:             make(map[string][]string, 256),
+		am:                         am,
 		RoundOrderFees:             make(map[string]*types.Fee, 256),
+		FeeManager:                 NewFeeManager(cdc, logger),
+		CollectOrderInfoForPublish: collectOrderInfoForPublish,
+		engines:                    make(map[string]*me.MatchEng),
+		pairsType:                  make(map[string]SymbolPairType),
 		poolSize:                   concurrency,
 		cdc:                        cdc,
-		FeeManager:                 NewFeeManager(cdc, key, logger),
-		CollectOrderInfoForPublish: collectOrderInfoForPublish,
 		logger:                     logger,
+		OrderKeepers:               []DexOrderKeeper{bep2OrderKeeper, miniOrderKeeper},
 	}
 }
 
-func (kp *Keeper) Init(ctx sdk.Context, blockInterval, daysBack int, blockStore *tmstore.BlockStore, stateDB dbm.DB, lastHeight int64, txDecoder sdk.TxDecoder) {
+func (kp *DexKeeper) Init(ctx sdk.Context, blockInterval, daysBack int, blockStore *tmstore.BlockStore, stateDB dbm.DB, lastHeight int64, txDecoder sdk.TxDecoder) {
 	kp.initOrderBook(ctx, blockInterval, daysBack, blockStore, stateDB, lastHeight, txDecoder)
 	kp.InitRecentPrices(ctx)
 }
 
-func (kp *Keeper) InitRecentPrices(ctx sdk.Context) {
+func (kp *DexKeeper) InitRecentPrices(ctx sdk.Context) {
 	kp.recentPrices = kp.PairMapper.GetRecentPrices(ctx, pricesStoreEvery, numPricesStored)
 }
 
-func (kp *Keeper) AddEngine(pair dexTypes.TradingPair) *me.MatchEng {
-	symbol := strings.ToUpper(pair.GetSymbol())
-	eng := CreateMatchEng(symbol, pair.ListPrice.ToInt64(), pair.LotSize.ToInt64())
-	kp.engines[symbol] = eng
-	kp.allOrders[symbol] = map[string]*OrderInfo{}
-	return eng
+func (kp *DexKeeper) SetBUSDSymbol(symbol string) {
+	BUSDSymbol = symbol
+}
+
+func (kp *DexKeeper) EnablePublish() {
+	kp.CollectOrderInfoForPublish = true
+	for i := range kp.OrderKeepers {
+		kp.OrderKeepers[i].enablePublish()
+	}
+}
+
+func (kp *DexKeeper) GetPairType(symbol string) SymbolPairType {
+	pairType, ok := kp.pairsType[symbol]
+	if !ok {
+		if dexUtils.IsMiniTokenTradingPair(symbol) {
+			pairType = PairType.MINI
+		} else {
+			pairType = PairType.BEP2
+		}
+	}
+	return pairType
+}
+
+func (kp *DexKeeper) getOrderKeeper(symbol string) (DexOrderKeeper, error) {
+	pairType, ok := kp.pairsType[symbol]
+	if !ok {
+		err := fmt.Errorf("invalid symbol: %s", symbol)
+		kp.logger.Debug(err.Error())
+		return nil, err
+	}
+	for i := range kp.OrderKeepers {
+		if kp.OrderKeepers[i].supportPairType(pairType) {
+			return kp.OrderKeepers[i], nil
+		}
+	}
+	err := fmt.Errorf("failed to find orderKeeper for symbol pair [%s]", symbol)
+	kp.logger.Error(err.Error())
+	return nil, err
+}
+
+func (kp *DexKeeper) mustGetOrderKeeper(symbol string) DexOrderKeeper {
+	pairType := kp.pairsType[symbol]
+	for i := range kp.OrderKeepers {
+		if kp.OrderKeepers[i].supportPairType(pairType) {
+			return kp.OrderKeepers[i]
+		}
+	}
+
+	panic(fmt.Errorf("invalid symbol %s", symbol))
 }
 
-func (kp *Keeper) UpdateTickSizeAndLotSize(ctx sdk.Context) {
+func (kp *DexKeeper) UpdateTickSizeAndLotSize(ctx sdk.Context) {
 	tradingPairs := kp.PairMapper.ListAllTradingPairs(ctx)
 	lotSizeCache := make(map[string]int64) // baseAsset -> lotSize
 	for _, pair := range tradingPairs {
@@ -131,7 +175,7 @@ func (kp *Keeper) UpdateTickSizeAndLotSize(ctx sdk.Context) {
 	}
 }
 
-func (kp *Keeper) determineTickAndLotSize(pair dexTypes.TradingPair, priceWMA int64, lotSizeCache map[string]int64) (tickSize, lotSize int64) {
+func (kp *DexKeeper) determineTickAndLotSize(pair dexTypes.TradingPair, priceWMA int64, lotSizeCache map[string]int64) (tickSize, lotSize int64) {
 	tickSize = dexUtils.CalcTickSize(priceWMA)
 	if !sdk.IsUpgrade(upgrade.LotSizeOptimization) {
 		lotSize = dexUtils.CalcLotSize(priceWMA)
@@ -146,7 +190,7 @@ func (kp *Keeper) determineTickAndLotSize(pair dexTypes.TradingPair, priceWMA in
 	return
 }
 
-func (kp *Keeper) DetermineLotSize(baseAssetSymbol, quoteAssetSymbol string, price int64) (lotSize int64) {
+func (kp *DexKeeper) DetermineLotSize(baseAssetSymbol, quoteAssetSymbol string, price int64) (lotSize int64) {
 	var priceAgainstNative int64
 	if baseAssetSymbol == types.NativeTokenSymbol {
 		// price of BNB/BNB is 1e8
@@ -154,17 +198,23 @@ func (kp *Keeper) DetermineLotSize(baseAssetSymbol, quoteAssetSymbol string, pri
 	} else if quoteAssetSymbol == types.NativeTokenSymbol {
 		priceAgainstNative = price
 	} else {
-		if ps, ok := kp.recentPrices[dexUtils.Assets2TradingPair(baseAssetSymbol, types.NativeTokenSymbol)]; ok {
-			priceAgainstNative = dexUtils.CalcPriceWMA(ps)
-		} else if ps, ok = kp.recentPrices[dexUtils.Assets2TradingPair(types.NativeTokenSymbol, baseAssetSymbol)]; ok {
-			wma := dexUtils.CalcPriceWMA(ps)
-			priceAgainstNative = 1e16 / wma
-		} else {
-			// the recentPrices still have not collected any price yet, iff the native pair is listed for less than kp.pricesStoreEvery blocks
-			if engine, ok := kp.engines[dexUtils.Assets2TradingPair(baseAssetSymbol, types.NativeTokenSymbol)]; ok {
-				priceAgainstNative = engine.LastTradePrice
-			} else if engine, ok = kp.engines[dexUtils.Assets2TradingPair(types.NativeTokenSymbol, baseAssetSymbol)]; ok {
-				priceAgainstNative = 1e16 / engine.LastTradePrice
+		var found bool
+		priceAgainstNative, found = kp.calcPriceAgainst(baseAssetSymbol, types.NativeTokenSymbol)
+		if !found {
+			if sdk.IsUpgrade(upgrade.BEP70) && len(BUSDSymbol) > 0 {
+				var tmp = big.NewInt(0)
+				priceAgainstBUSD, ok := kp.calcPriceAgainst(baseAssetSymbol, BUSDSymbol)
+				if !ok {
+					// for newly added pair, there is no trading yet
+					priceAgainstBUSD = price
+				}
+				priceBUSDAgainstNative, _ := kp.calcPriceAgainst(BUSDSymbol, types.NativeTokenSymbol)
+				tmp = tmp.Div(tmp.Mul(big.NewInt(priceAgainstBUSD), big.NewInt(priceBUSDAgainstNative)), big.NewInt(1e8))
+				if tmp.IsInt64() {
+					priceAgainstNative = tmp.Int64()
+				} else {
+					priceAgainstNative = math.MaxInt64
+				}
 			} else {
 				// should not happen
 				kp.logger.Error("DetermineLotSize failed because no native pair found", "base", baseAssetSymbol, "quote", quoteAssetSymbol)
@@ -175,7 +225,31 @@ func (kp *Keeper) DetermineLotSize(baseAssetSymbol, quoteAssetSymbol string, pri
 	return lotSize
 }
 
-func (kp *Keeper) UpdateLotSize(symbol string, lotSize int64) {
+func (kp *DexKeeper) calcPriceAgainst(symbol, targetSymbol string) (int64, bool) {
+	var priceAgainst int64 = 0
+	var found bool
+	if ps, ok := kp.recentPrices[dexUtils.Assets2TradingPair(symbol, targetSymbol)]; ok {
+		priceAgainst = dexUtils.CalcPriceWMA(ps)
+		found = true
+	} else if ps, ok = kp.recentPrices[dexUtils.Assets2TradingPair(targetSymbol, symbol)]; ok {
+		wma := dexUtils.CalcPriceWMA(ps)
+		priceAgainst = 1e16 / wma
+		found = true
+	} else {
+		// the recentPrices still have not collected any price yet, iff the native pair is listed for less than kp.pricesStoreEvery blocks
+		if engine, ok := kp.engines[dexUtils.Assets2TradingPair(symbol, targetSymbol)]; ok {
+			priceAgainst = engine.LastTradePrice
+			found = true
+		} else if engine, ok = kp.engines[dexUtils.Assets2TradingPair(targetSymbol, symbol)]; ok {
+			priceAgainst = 1e16 / engine.LastTradePrice
+			found = true
+		}
+	}
+
+	return priceAgainst, found
+}
+
+func (kp *DexKeeper) UpdateLotSize(symbol string, lotSize int64) {
 	eng, ok := kp.engines[symbol]
 	if !ok {
 		panic(fmt.Sprintf("match engine of symbol %s doesn't exist", symbol))
@@ -183,7 +257,25 @@ func (kp *Keeper) UpdateLotSize(symbol string, lotSize int64) {
 	eng.LotSize = lotSize
 }
 
-func (kp *Keeper) AddOrder(info OrderInfo, isRecovery bool) (err error) {
+func (kp *DexKeeper) AddEngine(pair dexTypes.TradingPair) *me.MatchEng {
+	symbol := strings.ToUpper(pair.GetSymbol())
+	eng := CreateMatchEng(symbol, pair.ListPrice.ToInt64(), pair.LotSize.ToInt64())
+	kp.engines[symbol] = eng
+	pairType := PairType.BEP2
+	if dexUtils.IsMiniTokenTradingPair(symbol) {
+		pairType = PairType.MINI
+	}
+	kp.pairsType[symbol] = pairType
+	for i := range kp.OrderKeepers {
+		if kp.OrderKeepers[i].supportPairType(pairType) {
+			kp.OrderKeepers[i].initOrders(symbol)
+			break
+		}
+	}
+	return eng
+}
+
+func (kp *DexKeeper) AddOrder(info OrderInfo, isRecovery bool) (err error) {
 	//try update order book first
 	symbol := strings.ToUpper(info.Symbol)
 	eng, ok := kp.engines[symbol]
@@ -197,26 +289,7 @@ func (kp *Keeper) AddOrder(info OrderInfo, isRecovery bool) (err error) {
 		return err
 	}
 
-	if kp.CollectOrderInfoForPublish {
-		change := OrderChange{info.Id, Ack, "", nil}
-		// deliberately not add this message to orderChanges
-		if !isRecovery {
-			kp.OrderChanges = append(kp.OrderChanges, change)
-		}
-		kp.logger.Debug("add order to order changes map", "orderId", info.Id, "isRecovery", isRecovery)
-		kp.OrderInfosForPub[info.Id] = &info
-	}
-
-	kp.allOrders[symbol][info.Id] = &info
-	if ids, ok := kp.roundOrders[symbol]; ok {
-		kp.roundOrders[symbol] = append(ids, info.Id)
-	} else {
-		newIds := make([]string, 0, 16)
-		kp.roundOrders[symbol] = append(newIds, info.Id)
-	}
-	if info.TimeInForce == TimeInForce.IOC {
-		kp.roundIOCOrders[symbol] = append(kp.roundIOCOrders[symbol], info.Id)
-	}
+	kp.mustGetOrderKeeper(symbol).addOrder(symbol, info, isRecovery)
 	kp.logger.Debug("Added orders", "symbol", symbol, "id", info.Id)
 	return nil
 }
@@ -225,29 +298,22 @@ func orderNotFound(symbol, id string) error {
 	return fmt.Errorf("Failed to find order [%v] on symbol [%v]", id, symbol)
 }
 
-func (kp *Keeper) RemoveOrder(id string, symbol string, postCancelHandler func(ord me.OrderPart)) (err error) {
+func (kp *DexKeeper) RemoveOrder(id string, symbol string, postCancelHandler func(ord me.OrderPart)) error {
 	symbol = strings.ToUpper(symbol)
-	ordMsg, ok := kp.OrderExists(symbol, id)
-	if !ok {
-		return orderNotFound(symbol, id)
-	}
-	eng, ok := kp.engines[symbol]
-	if !ok {
-		return orderNotFound(symbol, id)
-	}
-	delete(kp.allOrders[symbol], id)
-	ord, err := eng.Book.RemoveOrder(id, ordMsg.Side, ordMsg.Price)
-	if err != nil {
-		return err
-	}
-
-	if postCancelHandler != nil {
-		postCancelHandler(ord)
+	if dexOrderKeeper, err := kp.getOrderKeeper(symbol); err == nil {
+		ord, err := dexOrderKeeper.removeOrder(kp, id, symbol)
+		if err != nil {
+			return err
+		}
+		if postCancelHandler != nil {
+			postCancelHandler(ord)
+		}
+		return nil
 	}
-	return nil
+	return orderNotFound(symbol, id)
 }
 
-func (kp *Keeper) GetOrder(id string, symbol string, side int8, price int64) (ord me.OrderPart, err error) {
+func (kp *DexKeeper) GetOrder(id string, symbol string, side int8, price int64) (ord me.OrderPart, err error) {
 	symbol = strings.ToUpper(symbol)
 	_, ok := kp.OrderExists(symbol, id)
 	if !ok {
@@ -260,11 +326,9 @@ func (kp *Keeper) GetOrder(id string, symbol string, side int8, price int64) (or
 	return eng.Book.GetOrder(id, side, price)
 }
 
-func (kp *Keeper) OrderExists(symbol, id string) (OrderInfo, bool) {
-	if orders, ok := kp.allOrders[symbol]; ok {
-		if msg, ok := orders[id]; ok {
-			return *msg, ok
-		}
+func (kp *DexKeeper) OrderExists(symbol, id string) (OrderInfo, bool) {
+	if dexOrderKeeper, err := kp.getOrderKeeper(symbol); err == nil {
+		return dexOrderKeeper.orderExists(symbol, id)
 	}
 	return OrderInfo{}, false
 }
@@ -282,81 +346,7 @@ func channelHash(accAddress sdk.AccAddress, bucketNumber int) int {
 	return sum % bucketNumber
 }
 
-func (kp *Keeper) matchAndDistributeTradesForSymbol(symbol string, height, timestamp int64, orders map[string]*OrderInfo,
-	distributeTrade bool, tradeOuts []chan Transfer) {
-	engine := kp.engines[symbol]
-	concurrency := len(tradeOuts)
-	// please note there is no logging in matching, expecting to see the order book details
-	// from the exchange's order book stream.
-	if engine.Match(height) {
-		kp.logger.Debug("Match finish:", "symbol", symbol, "lastTradePrice", engine.LastTradePrice)
-		for i := range engine.Trades {
-			t := &engine.Trades[i]
-			updateOrderMsg(orders[t.Bid], t.BuyCumQty, height, timestamp)
-			updateOrderMsg(orders[t.Sid], t.SellCumQty, height, timestamp)
-			if distributeTrade {
-				t1, t2 := TransferFromTrade(t, symbol, kp.allOrders[symbol])
-				c := channelHash(t1.accAddress, concurrency)
-				tradeOuts[c] <- t1
-				c = channelHash(t2.accAddress, concurrency)
-				tradeOuts[c] <- t2
-			}
-		}
-		droppedIds := engine.DropFilledOrder() //delete from order books
-		for _, id := range droppedIds {
-			delete(orders, id) //delete from order cache
-		}
-		kp.logger.Debug("Drop filled orders", "total", droppedIds)
-	} else {
-		// FUTURE-TODO:
-		// when Match() failed, have to unsolicited cancel all the new orders
-		// in this block. Ideally the order IDs would be stored in the EndBlock response,
-		// but this is not implemented yet, pending Tendermint to better handle EndBlock
-		// for index service.
-		kp.logger.Error("Fatal error occurred in matching, cancel all incoming new orders",
-			"symbol", symbol)
-		thisRoundIds := kp.roundOrders[symbol]
-		for _, id := range thisRoundIds {
-			msg := orders[id]
-			delete(orders, id)
-			if ord, err := engine.Book.RemoveOrder(id, msg.Side, msg.Price); err == nil {
-				kp.logger.Info("Removed due to match failure", "ordID", msg.Id)
-				if distributeTrade {
-					c := channelHash(msg.Sender, concurrency)
-					tradeOuts[c] <- TransferFromCanceled(ord, *msg, true)
-				}
-			} else {
-				kp.logger.Error("Failed to remove order, may be fatal!", "orderID", id)
-			}
-
-			// let the order status publisher publish these abnormal
-			// order status change outs.
-			if kp.CollectOrderInfoForPublish {
-				kp.OrderChangesMtx.Lock()
-				kp.OrderChanges = append(kp.OrderChanges, OrderChange{id, FailedMatching, "", nil})
-				kp.OrderChangesMtx.Unlock()
-			}
-		}
-		return // no need to handle IOC
-	}
-	iocIDs := kp.roundIOCOrders[symbol]
-	for _, id := range iocIDs {
-		if msg, ok := orders[id]; ok {
-			delete(orders, id)
-			if ord, err := engine.Book.RemoveOrder(id, msg.Side, msg.Price); err == nil {
-				kp.logger.Debug("Removed unclosed IOC order", "ordID", msg.Id)
-				if distributeTrade {
-					c := channelHash(msg.Sender, concurrency)
-					tradeOuts[c] <- TransferFromExpired(ord, *msg)
-				}
-			} else {
-				kp.logger.Error("Failed to remove IOC order, may be fatal!", "orderID", id)
-			}
-		}
-	}
-}
-
-func (kp *Keeper) SubscribeParamChange(hub *paramhub.Keeper) {
+func (kp *DexKeeper) SubscribeParamChange(hub *paramhub.Keeper) {
 	hub.SubscribeParamChange(
 		func(ctx sdk.Context, changes []interface{}) {
 			for _, c := range changes {
@@ -394,108 +384,35 @@ func (kp *Keeper) SubscribeParamChange(hub *paramhub.Keeper) {
 		})
 }
 
-// Run as postConsume procedure of async, no concurrent updates of orders map
-func updateOrderMsg(order *OrderInfo, cumQty, height, timestamp int64) {
-	order.CumQty = cumQty
-	order.LastUpdatedHeight = height
-	order.LastUpdatedTimestamp = timestamp
-}
-
-// please note if distributeTrade this method will work in async mode, otherwise in sync mode.
-func (kp *Keeper) matchAndDistributeTrades(distributeTrade bool, height, timestamp int64) []chan Transfer {
-	size := len(kp.roundOrders)
-	// size is the number of pairs that have new orders, i.e. it should call match()
-	if size == 0 {
-		kp.logger.Info("No new orders for any pair, give up matching")
-		return nil
-	}
-
-	concurrency := 1 << kp.poolSize
-	tradeOuts := make([]chan Transfer, concurrency)
-	if distributeTrade {
-		ordNum := 0
-		for _, perSymbol := range kp.roundOrders {
-			ordNum += len(perSymbol)
-		}
-		for i := range tradeOuts {
-			//assume every new order would have 2 trades and generate 4 transfer
-			tradeOuts[i] = make(chan Transfer, ordNum*4/concurrency)
-		}
-	}
-
-	symbolCh := make(chan string, concurrency)
-	producer := func() {
-		for symbol := range kp.roundOrders {
-			symbolCh <- symbol
-		}
-		close(symbolCh)
-	}
-	matchWorker := func() {
-		i := 0
-		for symbol := range symbolCh {
-			i++
-			kp.matchAndDistributeTradesForSymbol(symbol, height, timestamp, kp.allOrders[symbol], distributeTrade, tradeOuts)
-		}
-	}
-
-	if distributeTrade {
-		utils.ConcurrentExecuteAsync(concurrency, producer, matchWorker, func() {
-			for _, tradeOut := range tradeOuts {
-				close(tradeOut)
-			}
-		})
-	} else {
-		utils.ConcurrentExecuteSync(concurrency, producer, matchWorker)
-	}
-	return tradeOuts
-}
-
-func (kp *Keeper) GetOrderBookLevels(pair string, maxLevels int) []store.OrderBookLevel {
-	orderbook := make([]store.OrderBookLevel, maxLevels)
+func (kp *DexKeeper) GetOrderBookLevels(pair string, maxLevels int) (orderbook []store.OrderBookLevel, pendingMatch bool) {
+	orderbook = make([]store.OrderBookLevel, maxLevels)
 
 	i, j := 0, 0
-
 	if eng, ok := kp.engines[pair]; ok {
 		// TODO: check considered bucket splitting?
-		eng.Book.ShowDepth(maxLevels, func(p *me.PriceLevel) {
+		eng.Book.ShowDepth(maxLevels, func(p *me.PriceLevel, levelIndex int) {
 			orderbook[i].BuyPrice = utils.Fixed8(p.Price)
 			orderbook[i].BuyQty = utils.Fixed8(p.TotalLeavesQty())
 			i++
-		},
-			func(p *me.PriceLevel) {
-				orderbook[j].SellPrice = utils.Fixed8(p.Price)
-				orderbook[j].SellQty = utils.Fixed8(p.TotalLeavesQty())
-				j++
-			})
-	}
-	return orderbook
-}
-
-func (kp *Keeper) GetOpenOrders(pair string, addr sdk.AccAddress) []store.OpenOrder {
-	openOrders := make([]store.OpenOrder, 0)
-
-	for _, order := range kp.allOrders[pair] {
-		if string(order.Sender.Bytes()) == string(addr.Bytes()) {
-			openOrders = append(
-				openOrders,
-				store.OpenOrder{
-					order.Id,
-					pair,
-					utils.Fixed8(order.Price),
-					utils.Fixed8(order.Quantity),
-					utils.Fixed8(order.CumQty),
-					order.CreatedHeight,
-					order.CreatedTimestamp,
-					order.LastUpdatedHeight,
-					order.LastUpdatedTimestamp,
-				})
-		}
+		}, func(p *me.PriceLevel, levelIndex int) {
+			orderbook[j].SellPrice = utils.Fixed8(p.Price)
+			orderbook[j].SellQty = utils.Fixed8(p.TotalLeavesQty())
+			j++
+		})
+		roundOrders := kp.mustGetOrderKeeper(pair).getRoundOrdersForPair(pair)
+		pendingMatch = len(roundOrders) > 0
 	}
+	return orderbook, pendingMatch
+}
 
-	return openOrders
+func (kp *DexKeeper) GetOpenOrders(pair string, addr sdk.AccAddress) []store.OpenOrder {
+	if dexOrderKeeper, err := kp.getOrderKeeper(pair); err == nil {
+		return dexOrderKeeper.getOpenOrders(pair, addr)
+	}
+	return make([]store.OpenOrder, 0)
 }
 
-func (kp *Keeper) GetOrderBooks(maxLevels int) ChangedPriceLevelsMap {
+func (kp *DexKeeper) GetOrderBooks(maxLevels int) ChangedPriceLevelsMap {
 	var res = make(ChangedPriceLevelsMap)
 	for pair, eng := range kp.engines {
 		buys := make(map[int64]int64)
@@ -503,9 +420,9 @@ func (kp *Keeper) GetOrderBooks(maxLevels int) ChangedPriceLevelsMap {
 		res[pair] = ChangedPriceLevelsPerSymbol{buys, sells}
 
 		// TODO: check considered bucket splitting?
-		eng.Book.ShowDepth(maxLevels, func(p *me.PriceLevel) {
+		eng.Book.ShowDepth(maxLevels, func(p *me.PriceLevel, levelIndex int) {
 			buys[p.Price] = p.TotalLeavesQty()
-		}, func(p *me.PriceLevel) {
+		}, func(p *me.PriceLevel, levelIndex int) {
 			sells[p.Price] = p.TotalLeavesQty()
 		})
 	}
@@ -513,7 +430,7 @@ func (kp *Keeper) GetOrderBooks(maxLevels int) ChangedPriceLevelsMap {
 	return res
 }
 
-func (kp *Keeper) GetPriceLevel(pair string, side int8, price int64) *me.PriceLevel {
+func (kp *DexKeeper) GetPriceLevel(pair string, side int8, price int64) *me.PriceLevel {
 	if eng, ok := kp.engines[pair]; ok {
 		return eng.Book.GetPriceLevel(price, side)
 	} else {
@@ -521,7 +438,7 @@ func (kp *Keeper) GetPriceLevel(pair string, side int8, price int64) *me.PriceLe
 	}
 }
 
-func (kp *Keeper) GetLastTrades(height int64, pair string) ([]me.Trade, int64) {
+func (kp *DexKeeper) GetLastTrades(height int64, pair string) ([]me.Trade, int64) {
 	if eng, ok := kp.engines[pair]; ok {
 		if eng.LastMatchHeight == height {
 			return eng.Trades, eng.LastTradePrice
@@ -531,24 +448,28 @@ func (kp *Keeper) GetLastTrades(height int64, pair string) ([]me.Trade, int64) {
 }
 
 // !!! FOR TEST USE ONLY
-func (kp *Keeper) GetLastTradesForPair(pair string) ([]me.Trade, int64) {
+func (kp *DexKeeper) GetLastTradesForPair(pair string) ([]me.Trade, int64) {
 	if eng, ok := kp.engines[pair]; ok {
 		return eng.Trades, eng.LastTradePrice
 	}
 	return nil, 0
 }
 
-func (kp *Keeper) ClearOrderBook(pair string) {
+func (kp *DexKeeper) ClearOrderBook(pair string) {
 	if eng, ok := kp.engines[pair]; ok {
 		eng.Book.Clear()
 	}
 }
 
-func (kp *Keeper) ClearOrderChanges() {
-	kp.OrderChanges = kp.OrderChanges[:0]
+func (kp *DexKeeper) ClearOrderChanges() {
+	for _, orderKeeper := range kp.OrderKeepers {
+		if orderKeeper.supportUpgradeVersion() {
+			orderKeeper.clearOrderChanges()
+		}
+	}
 }
 
-func (kp *Keeper) doTransfer(ctx sdk.Context, tran *Transfer) sdk.Error {
+func (kp *DexKeeper) doTransfer(ctx sdk.Context, tran *Transfer) sdk.Error {
 	account := kp.am.GetAccount(ctx, tran.accAddress).(types.NamedAccount)
 	newLocked := account.GetLockedCoins().Minus(sdk.Coins{sdk.NewCoin(tran.outAsset, tran.unlock)})
 	// these two non-negative check are to ensure the Transfer gen result is correct before we actually operate the acc.
@@ -575,12 +496,15 @@ func (kp *Keeper) doTransfer(ctx sdk.Context, tran *Transfer) sdk.Error {
 	return nil
 }
 
-func (kp *Keeper) clearAfterMatch() {
-	kp.roundOrders = make(map[string][]string, 256)
-	kp.roundIOCOrders = make(map[string][]string, 256)
+func (kp *DexKeeper) ClearAfterMatch() {
+	for _, orderKeeper := range kp.OrderKeepers {
+		if orderKeeper.supportUpgradeVersion() {
+			orderKeeper.clearAfterMatch()
+		}
+	}
 }
 
-func (kp *Keeper) StoreTradePrices(ctx sdk.Context) {
+func (kp *DexKeeper) StoreTradePrices(ctx sdk.Context) {
 	// TODO: check block height != 0
 	if ctx.BlockHeight()%pricesStoreEvery == 0 {
 		lastTradePrices := make(map[string]int64, len(kp.engines))
@@ -597,7 +521,7 @@ func (kp *Keeper) StoreTradePrices(ctx sdk.Context) {
 	}
 }
 
-func (kp *Keeper) allocate(ctx sdk.Context, tranCh <-chan Transfer, postAllocateHandler func(tran Transfer)) (
+func (kp *DexKeeper) allocate(ctx sdk.Context, tranCh <-chan Transfer, postAllocateHandler func(tran Transfer)) (
 	types.Fee, map[string]*types.Fee) {
 	if !sdk.IsUpgrade(upgrade.BEP19) {
 		return kp.allocateBeforeGalileo(ctx, tranCh, postAllocateHandler)
@@ -671,7 +595,7 @@ func (kp *Keeper) allocate(ctx sdk.Context, tranCh <-chan Transfer, postAllocate
 }
 
 // DEPRECATED
-func (kp *Keeper) allocateBeforeGalileo(ctx sdk.Context, tranCh <-chan Transfer, postAllocateHandler func(tran Transfer)) (
+func (kp *DexKeeper) allocateBeforeGalileo(ctx sdk.Context, tranCh <-chan Transfer, postAllocateHandler func(tran Transfer)) (
 	types.Fee, map[string]*types.Fee) {
 	// use string of the addr as the key since map makes a fast path for string key.
 	// Also, making the key have same length is also an optimization.
@@ -752,11 +676,10 @@ func (kp *Keeper) allocateBeforeGalileo(ctx sdk.Context, tranCh <-chan Transfer,
 	return totalFee, feesPerAcc
 }
 
-func (kp *Keeper) allocateAndCalcFee(
+func (kp *DexKeeper) allocateAndCalcFee(
 	ctx sdk.Context,
 	tradeOuts []chan Transfer,
-	postAlloTransHandler TransferHandler,
-) types.Fee {
+	postAlloTransHandler TransferHandler) types.Fee {
 	concurrency := len(tradeOuts)
 	var wg sync.WaitGroup
 	wg.Add(concurrency)
@@ -787,47 +710,21 @@ func (kp *Keeper) allocateAndCalcFee(
 	return totalFee
 }
 
-// MatchAll will only concurrently match but do not allocate into accounts
-func (kp *Keeper) MatchAll(height, timestamp int64) {
-	tradeOuts := kp.matchAndDistributeTrades(false, height, timestamp) //only match
-	if tradeOuts == nil {
-		kp.logger.Info("No order comes in for the block")
-	}
-	kp.clearAfterMatch()
-}
-
-// MatchAndAllocateAll() is concurrently matching and allocating across
-// all the symbols' order books, among all the clients
-// Return whether match has been done in this height
-func (kp *Keeper) MatchAndAllocateAll(
-	ctx sdk.Context,
-	postAlloTransHandler TransferHandler,
-) {
-	kp.logger.Debug("Start Matching for all...", "height", ctx.BlockHeader().Height, "symbolNum", len(kp.roundOrders))
-	timestamp := ctx.BlockHeader().Time.UnixNano()
-	tradeOuts := kp.matchAndDistributeTrades(true, ctx.BlockHeader().Height, timestamp)
-	if tradeOuts == nil {
-		kp.logger.Info("No order comes in for the block")
+func (kp *DexKeeper) expireOrders(ctx sdk.Context, blockTime time.Time) []chan Transfer {
+	allOrders := make(map[string]map[string]*OrderInfo) //TODO replace by iterator
+	for _, orderKeeper := range kp.OrderKeepers {
+		if orderKeeper.supportUpgradeVersion() {
+			allOrders = appendAllOrdersMap(allOrders, orderKeeper.getAllOrders())
+		}
 	}
-
-	totalFee := kp.allocateAndCalcFee(ctx, tradeOuts, postAlloTransHandler)
-	fees.Pool.AddAndCommitFee("MATCH", totalFee)
-	kp.clearAfterMatch()
-}
-
-func (kp *Keeper) expireOrders(ctx sdk.Context, blockTime time.Time) []chan Transfer {
-	size := len(kp.allOrders)
+	size := len(allOrders)
 	if size == 0 {
 		kp.logger.Info("No orders to expire")
 		return nil
 	}
 
-	// TODO: make effectiveDays configurable
-	const effectiveDays = 3
-	expireHeight, err := kp.GetBreatheBlockHeight(ctx, blockTime, effectiveDays)
+	expireHeight, forceExpireHeight, err := kp.getExpireHeight(ctx, blockTime)
 	if err != nil {
-		// breathe block not found, that should only happens in in the first three days, just log it and ignore.
-		kp.logger.Info(err.Error())
 		return nil
 	}
 
@@ -844,7 +741,7 @@ func (kp *Keeper) expireOrders(ctx sdk.Context, blockTime time.Time) []chan Tran
 	}
 
 	expire := func(orders map[string]*OrderInfo, engine *me.MatchEng, side int8) {
-		engine.Book.RemoveOrders(expireHeight, side, func(ord me.OrderPart) {
+		removeCallback := func(ord me.OrderPart) {
 			// gen transfer
 			if ordMsg, ok := orders[ord.Id]; ok && ordMsg != nil {
 				h := channelHash(ordMsg.Sender, concurrency)
@@ -854,20 +751,25 @@ func (kp *Keeper) expireOrders(ctx sdk.Context, blockTime time.Time) []chan Tran
 			} else {
 				kp.logger.Error("failed to locate order to remove in order book", "oid", ord.Id)
 			}
-		})
+		}
+		if !sdk.IsUpgrade(upgrade.BEP67) {
+			engine.Book.RemoveOrders(expireHeight, side, removeCallback)
+		} else {
+			engine.Book.RemoveOrdersBasedOnPriceLevel(expireHeight, forceExpireHeight, preferencePriceLevel, side, removeCallback)
+		}
 	}
 
 	symbolCh := make(chan string, concurrency)
 	utils.ConcurrentExecuteAsync(concurrency,
 		func() {
-			for symbol := range kp.allOrders {
+			for symbol := range allOrders {
 				symbolCh <- symbol
 			}
 			close(symbolCh)
 		}, func() {
 			for symbol := range symbolCh {
 				engine := kp.engines[symbol]
-				orders := kp.allOrders[symbol]
+				orders := allOrders[symbol]
 				expire(orders, engine, me.BUYSIDE)
 				expire(orders, engine, me.SELLSIDE)
 			}
@@ -880,7 +782,31 @@ func (kp *Keeper) expireOrders(ctx sdk.Context, blockTime time.Time) []chan Tran
 	return transferChs
 }
 
-func (kp *Keeper) ExpireOrders(
+func (kp *DexKeeper) getExpireHeight(ctx sdk.Context, blockTime time.Time) (expireHeight, forceExpireHeight int64, noBreatheBlock error) {
+	const effectiveDays = 3
+	expireHeight, noBreatheBlock = kp.GetBreatheBlockHeight(ctx, blockTime, effectiveDays)
+	if noBreatheBlock != nil {
+		// breathe block not found, that should only happens in in the first three days, just log it and ignore.
+		kp.logger.Error(noBreatheBlock.Error())
+		return -1, -1, noBreatheBlock
+	}
+
+	if sdk.IsUpgrade(upgrade.BEP67) {
+		const forceExpireDays = 30
+		var err error
+		forceExpireHeight, err = kp.GetBreatheBlockHeight(ctx, blockTime, forceExpireDays)
+		if err != nil {
+			//if breathe block of 30 days ago not found, the breathe block of 3 days ago still can be processed, so return err=nil
+			kp.logger.Error(err.Error())
+			return expireHeight, -1, nil
+		}
+	} else {
+		forceExpireHeight = -1
+	}
+	return expireHeight, forceExpireHeight, nil
+}
+
+func (kp *DexKeeper) ExpireOrders(
 	ctx sdk.Context,
 	blockTime time.Time,
 	postAlloTransHandler TransferHandler,
@@ -894,7 +820,7 @@ func (kp *Keeper) ExpireOrders(
 	fees.Pool.AddAndCommitFee("EXPIRE", totalFee)
 }
 
-func (kp *Keeper) MarkBreatheBlock(ctx sdk.Context, height int64, blockTime time.Time) {
+func (kp *DexKeeper) MarkBreatheBlock(ctx sdk.Context, height int64, blockTime time.Time) {
 	key := utils.Int642Bytes(blockTime.Unix() / utils.SecondsPerDay)
 	store := ctx.KVStore(kp.storeKey)
 	bz, err := kp.cdc.MarshalBinaryBare(height)
@@ -905,7 +831,7 @@ func (kp *Keeper) MarkBreatheBlock(ctx sdk.Context, height int64, blockTime time
 	store.Set([]byte(key), bz)
 }
 
-func (kp *Keeper) GetBreatheBlockHeight(ctx sdk.Context, timeNow time.Time, daysBack int) (int64, error) {
+func (kp *DexKeeper) GetBreatheBlockHeight(ctx sdk.Context, timeNow time.Time, daysBack int) (int64, error) {
 	store := ctx.KVStore(kp.storeKey)
 	t := timeNow.AddDate(0, 0, -daysBack).Unix()
 	day := t / utils.SecondsPerDay
@@ -922,7 +848,7 @@ func (kp *Keeper) GetBreatheBlockHeight(ctx sdk.Context, timeNow time.Time, days
 	return height, nil
 }
 
-func (kp *Keeper) GetLastBreatheBlockHeight(ctx sdk.Context, latestBlockHeight int64, timeNow time.Time, blockInterval, daysBack int) int64 {
+func (kp *DexKeeper) GetLastBreatheBlockHeight(ctx sdk.Context, latestBlockHeight int64, timeNow time.Time, blockInterval, daysBack int) int64 {
 	if blockInterval != 0 {
 		return (latestBlockHeight / int64(blockInterval)) * int64(blockInterval)
 	} else {
@@ -953,7 +879,7 @@ func (kp *Keeper) GetLastBreatheBlockHeight(ctx sdk.Context, latestBlockHeight i
 
 // deliberately make `fee` parameter not a pointer
 // in case we modify the original fee (which will be referenced when distribute to validator)
-func (kp *Keeper) updateRoundOrderFee(addr string, fee types.Fee) {
+func (kp *DexKeeper) updateRoundOrderFee(addr string, fee types.Fee) {
 	if existingFee, ok := kp.RoundOrderFees[addr]; ok {
 		existingFee.AddFee(fee)
 	} else {
@@ -961,20 +887,47 @@ func (kp *Keeper) updateRoundOrderFee(addr string, fee types.Fee) {
 	}
 }
 
-func (kp *Keeper) ClearRoundFee() {
+func (kp *DexKeeper) ClearRoundFee() {
 	kp.RoundOrderFees = make(map[string]*types.Fee, 256)
 }
 
-// used by state sync to clear memory order book after we synced latest breathe block
-func (kp *Keeper) ClearOrders() {
-	kp.engines = make(map[string]*me.MatchEng)
-	kp.allOrders = make(map[string]map[string]*OrderInfo, 256)
-	kp.roundOrders = make(map[string][]string, 256)
-	kp.roundIOCOrders = make(map[string][]string, 256)
-	kp.OrderInfosForPub = make(OrderInfoForPublish)
+func (kp *DexKeeper) CanDelistTradingPair(ctx sdk.Context, baseAsset, quoteAsset string) error {
+	// trading pair against native token should not be delisted if there is any other trading pair exist
+	baseAsset = strings.ToUpper(baseAsset)
+	quoteAsset = strings.ToUpper(quoteAsset)
+
+	if baseAsset == quoteAsset {
+		return fmt.Errorf("base asset symbol should not be identical to quote asset symbol")
+	}
+
+	if !kp.PairMapper.Exists(ctx, baseAsset, quoteAsset) {
+		return fmt.Errorf("trading pair %s_%s does not exist", baseAsset, quoteAsset)
+	}
+
+	if baseAsset != types.NativeTokenSymbol && quoteAsset != types.NativeTokenSymbol {
+		return nil
+	}
+
+	var symbolToCheck string
+	if baseAsset != types.NativeTokenSymbol {
+		symbolToCheck = baseAsset
+	} else {
+		symbolToCheck = quoteAsset
+	}
+
+	tradingPairs := kp.PairMapper.ListAllTradingPairs(ctx)
+	for _, pair := range tradingPairs { //TODO
+		if (pair.BaseAssetSymbol == symbolToCheck && pair.QuoteAssetSymbol != types.NativeTokenSymbol) ||
+			(pair.QuoteAssetSymbol == symbolToCheck && pair.BaseAssetSymbol != types.NativeTokenSymbol) {
+			return fmt.Errorf("trading pair %s_%s should not exist before delisting %s_%s",
+				pair.BaseAssetSymbol, pair.QuoteAssetSymbol, baseAsset, quoteAsset)
+		}
+	}
+
+	return nil
 }
 
-func (kp *Keeper) DelistTradingPair(ctx sdk.Context, symbol string, postAllocTransHandler TransferHandler) {
+func (kp *DexKeeper) DelistTradingPair(ctx sdk.Context, symbol string, postAllocTransHandler TransferHandler) {
 	_, ok := kp.engines[symbol]
 	if !ok {
 		kp.logger.Error("delist symbol does not exist", "symbol", symbol)
@@ -988,8 +941,8 @@ func (kp *Keeper) DelistTradingPair(ctx sdk.Context, symbol string, postAllocTra
 	}
 
 	delete(kp.engines, symbol)
-	delete(kp.allOrders, symbol)
 	delete(kp.recentPrices, symbol)
+	kp.mustGetOrderKeeper(symbol).deleteOrdersForPair(symbol)
 
 	baseAsset, quoteAsset := dexUtils.TradingPair2AssetsSafe(symbol)
 	err := kp.PairMapper.DeleteTradingPair(ctx, baseAsset, quoteAsset)
@@ -998,8 +951,13 @@ func (kp *Keeper) DelistTradingPair(ctx sdk.Context, symbol string, postAllocTra
 	}
 }
 
-func (kp *Keeper) expireAllOrders(ctx sdk.Context, symbol string) []chan Transfer {
-	orderNum := len(kp.allOrders[symbol])
+func (kp *DexKeeper) expireAllOrders(ctx sdk.Context, symbol string) []chan Transfer {
+	ordersOfSymbol := make(map[string]*OrderInfo)
+	if dexOrderKeeper, err := kp.getOrderKeeper(symbol); err == nil {
+		ordersOfSymbol = dexOrderKeeper.getAllOrdersForPair(symbol)
+	}
+
+	orderNum := len(ordersOfSymbol)
 	if orderNum == 0 {
 		kp.logger.Info("no orders to expire", "symbol", symbol)
 		return nil
@@ -1027,7 +985,7 @@ func (kp *Keeper) expireAllOrders(ctx sdk.Context, symbol string) []chan Transfe
 
 	go func() {
 		engine := kp.engines[symbol]
-		orders := kp.allOrders[symbol]
+		orders := ordersOfSymbol
 		expire(orders, engine, me.BUYSIDE)
 		expire(orders, engine, me.SELLSIDE)
 
@@ -1039,7 +997,7 @@ func (kp *Keeper) expireAllOrders(ctx sdk.Context, symbol string) []chan Transfe
 	return transferChs
 }
 
-func (kp *Keeper) CanListTradingPair(ctx sdk.Context, baseAsset, quoteAsset string) error {
+func (kp *DexKeeper) CanListTradingPair(ctx sdk.Context, baseAsset, quoteAsset string) error {
 	// trading pair against native token should exist if quote token is not native token
 	baseAsset = strings.ToUpper(baseAsset)
 	quoteAsset = strings.ToUpper(quoteAsset)
@@ -1048,60 +1006,145 @@ func (kp *Keeper) CanListTradingPair(ctx sdk.Context, baseAsset, quoteAsset stri
 		return fmt.Errorf("base asset symbol should not be identical to quote asset symbol")
 	}
 
-	if kp.PairMapper.Exists(ctx, baseAsset, quoteAsset) || kp.PairMapper.Exists(ctx, quoteAsset, baseAsset) {
+	if kp.pairExistsBetween(ctx, baseAsset, quoteAsset) {
 		return errors.New("trading pair exists")
 	}
 
 	if baseAsset != types.NativeTokenSymbol &&
 		quoteAsset != types.NativeTokenSymbol {
 
-		if !kp.PairMapper.Exists(ctx, baseAsset, types.NativeTokenSymbol) &&
-			!kp.PairMapper.Exists(ctx, types.NativeTokenSymbol, baseAsset) {
+		// support busd pair listing including mini-token as base
+		if sdk.IsUpgrade(upgrade.BEP70) && len(BUSDSymbol) > 0 {
+			if baseAsset == BUSDSymbol || quoteAsset == BUSDSymbol {
+				if kp.pairExistsBetween(ctx, types.NativeTokenSymbol, BUSDSymbol) {
+					return nil
+				}
+			}
+		}
+
+		if !kp.pairExistsBetween(ctx, types.NativeTokenSymbol, baseAsset) {
 			return fmt.Errorf("token %s should be listed against BNB before against %s",
 				baseAsset, quoteAsset)
 		}
 
-		if !kp.PairMapper.Exists(ctx, quoteAsset, types.NativeTokenSymbol) &&
-			!kp.PairMapper.Exists(ctx, types.NativeTokenSymbol, quoteAsset) {
+		if !kp.pairExistsBetween(ctx, types.NativeTokenSymbol, quoteAsset) {
 			return fmt.Errorf("token %s should be listed against BNB before listing %s against %s",
 				quoteAsset, baseAsset, quoteAsset)
 		}
 	}
+
 	return nil
 }
 
-func (kp *Keeper) CanDelistTradingPair(ctx sdk.Context, baseAsset, quoteAsset string) error {
-	// trading pair against native token should not be delisted if there is any other trading pair exist
-	baseAsset = strings.ToUpper(baseAsset)
-	quoteAsset = strings.ToUpper(quoteAsset)
+func (kp *DexKeeper) GetAllOrders() map[string]map[string]*OrderInfo {
+	allOrders := make(map[string]map[string]*OrderInfo)
+	for _, orderKeeper := range kp.OrderKeepers {
+		allOrders = appendAllOrdersMap(allOrders, orderKeeper.getAllOrders())
+	}
+	return allOrders
+}
 
-	if baseAsset == quoteAsset {
-		return fmt.Errorf("base asset symbol should not be identical to quote asset symbol")
+// ONLY FOR TEST USE
+func (kp *DexKeeper) GetAllOrdersForPair(symbol string) map[string]*OrderInfo {
+	return kp.mustGetOrderKeeper(symbol).getAllOrdersForPair(symbol)
+}
+
+func (kp *DexKeeper) ReloadOrder(symbol string, orderInfo *OrderInfo, height int64) {
+	kp.mustGetOrderKeeper(symbol).reloadOrder(symbol, orderInfo, height)
+}
+
+func (kp *DexKeeper) GetOrderChanges(pairType SymbolPairType) OrderChanges {
+	for _, orderKeeper := range kp.OrderKeepers {
+		if orderKeeper.supportPairType(pairType) {
+			return orderKeeper.getOrderChanges()
+		}
 	}
+	kp.logger.Error("pairType is not supported %d for OrderChanges", pairType)
+	return make(OrderChanges, 0)
+}
+func (kp *DexKeeper) GetAllOrderChanges() OrderChanges {
+	var res OrderChanges
+	for _, orderKeeper := range kp.OrderKeepers {
+		if orderKeeper.supportUpgradeVersion() {
+			res = append(res, orderKeeper.getOrderChanges()...)
+		}
+	}
+	return res
+}
 
-	if !kp.PairMapper.Exists(ctx, baseAsset, quoteAsset) {
-		return fmt.Errorf("trading pair %s_%s does not exist", baseAsset, quoteAsset)
+func (kp *DexKeeper) UpdateOrderChangeSync(change OrderChange, symbol string) {
+	if dexOrderKeeper, err := kp.getOrderKeeper(symbol); err == nil {
+		dexOrderKeeper.appendOrderChangeSync(change)
+		return
 	}
+	kp.logger.Error("symbol is not supported %d", symbol)
+}
 
-	if baseAsset != types.NativeTokenSymbol && quoteAsset != types.NativeTokenSymbol {
-		return nil
+func (kp *DexKeeper) GetOrderInfosForPub(pairType SymbolPairType) OrderInfoForPublish {
+	for _, orderKeeper := range kp.OrderKeepers {
+		if orderKeeper.supportPairType(pairType) {
+			return orderKeeper.getOrderInfosForPub()
+		}
 	}
+	kp.logger.Error("pairType is not supported %d for OrderInfosForPub", pairType)
+	return make(OrderInfoForPublish)
+}
 
-	var symbolToCheck string
-	if baseAsset != types.NativeTokenSymbol {
-		symbolToCheck = baseAsset
-	} else {
-		symbolToCheck = quoteAsset
+func (kp *DexKeeper) GetAllOrderInfosForPub() OrderInfoForPublish {
+	orderInfoForPub := make(OrderInfoForPublish)
+	for _, orderKeeper := range kp.OrderKeepers {
+		if orderKeeper.supportUpgradeVersion() {
+			orderInfoForPub = appendOrderInfoForPub(orderInfoForPub, orderKeeper.getOrderInfosForPub())
+		}
 	}
+	return orderInfoForPub
+}
 
-	tradingPairs := kp.PairMapper.ListAllTradingPairs(ctx)
-	for _, pair := range tradingPairs {
-		if (pair.BaseAssetSymbol == symbolToCheck && pair.QuoteAssetSymbol != types.NativeTokenSymbol) ||
-			(pair.QuoteAssetSymbol == symbolToCheck && pair.BaseAssetSymbol != types.NativeTokenSymbol) {
-			return fmt.Errorf("trading pair %s_%s should not exist before delisting %s_%s",
-				pair.BaseAssetSymbol, pair.QuoteAssetSymbol, baseAsset, quoteAsset)
+func (kp *DexKeeper) RemoveOrderInfosForPub(pair string, orderId string) {
+	if orderKeeper, err := kp.getOrderKeeper(pair); err == nil {
+		orderKeeper.removeOrderInfosForPub(orderId)
+		return
+	}
+
+	kp.logger.Error("pair is not supported %d", pair)
+}
+
+func (kp *DexKeeper) ShouldPublishOrder() bool {
+	return kp.CollectOrderInfoForPublish
+}
+
+func (kp *DexKeeper) GetEngines() map[string]*me.MatchEng {
+	return kp.engines
+}
+func appendAllOrdersMap(ms ...map[string]map[string]*OrderInfo) map[string]map[string]*OrderInfo {
+	res := make(map[string]map[string]*OrderInfo)
+	for _, m := range ms {
+		for k, v := range m {
+			res[k] = v
 		}
 	}
+	return res
+}
 
-	return nil
+func appendOrderInfoForPub(ms ...OrderInfoForPublish) OrderInfoForPublish {
+	res := make(OrderInfoForPublish)
+	for _, m := range ms {
+		for k, v := range m {
+			res[k] = v
+		}
+	}
+	return res
+}
+
+func CreateMatchEng(pairSymbol string, basePrice, lotSize int64) *me.MatchEng {
+	return me.NewMatchEng(pairSymbol, basePrice, lotSize, 0.05)
+}
+
+func isMiniSymbolPair(baseAsset, quoteAsset string) bool {
+	return types.IsMiniTokenSymbol(baseAsset) || types.IsMiniTokenSymbol(quoteAsset)
+}
+
+// Check whether there is trading pair between two symbols
+func (kp *DexKeeper) pairExistsBetween(ctx sdk.Context, symbolA, symbolB string) bool {
+	return kp.PairMapper.Exists(ctx, symbolA, symbolB) || kp.PairMapper.Exists(ctx, symbolB, symbolA)
 }
diff --git a/plugins/dex/order/keeper_match.go b/plugins/dex/order/keeper_match.go
new file mode 100644
index 000000000..0b439e664
--- /dev/null
+++ b/plugins/dex/order/keeper_match.go
@@ -0,0 +1,178 @@
+package order
+
+import (
+	sdk "github.com/cosmos/cosmos-sdk/types"
+
+	"github.com/binance-chain/node/common/fees"
+	"github.com/binance-chain/node/common/utils"
+)
+
+func (kp *DexKeeper) SelectSymbolsToMatch(height int64, matchAllSymbols bool) []string {
+	symbolsToMatch := make([]string, 0, 256)
+	for _, orderKeeper := range kp.OrderKeepers {
+		if orderKeeper.supportUpgradeVersion() {
+			symbolsToMatch = append(symbolsToMatch, orderKeeper.selectSymbolsToMatch(height, matchAllSymbols)...)
+		}
+	}
+	return symbolsToMatch
+}
+
+func (kp *DexKeeper) MatchAndAllocateSymbols(ctx sdk.Context, postAlloTransHandler TransferHandler, matchAllSymbols bool) {
+	kp.logger.Debug("Start Matching for all...", "height", ctx.BlockHeader().Height)
+	blockHeader := ctx.BlockHeader()
+	timestamp := blockHeader.Time.UnixNano()
+
+	symbolsToMatch := kp.SelectSymbolsToMatch(blockHeader.Height, matchAllSymbols)
+	kp.logger.Info("symbols to match", "symbols", symbolsToMatch)
+	var tradeOuts []chan Transfer
+	if len(symbolsToMatch) == 0 {
+		kp.logger.Info("No order comes in for the block")
+	} else {
+		tradeOuts = kp.matchAndDistributeTrades(true, blockHeader.Height, timestamp)
+	}
+
+	totalFee := kp.allocateAndCalcFee(ctx, tradeOuts, postAlloTransHandler)
+	fees.Pool.AddAndCommitFee("MATCH", totalFee)
+	kp.ClearAfterMatch()
+}
+
+// please note if distributeTrade this method will work in async mode, otherwise in sync mode.
+// Always run kp.SelectSymbolsToMatch(ctx.BlockHeader().Height, matchAllSymbols) before matchAndDistributeTrades
+func (kp *DexKeeper) matchAndDistributeTrades(distributeTrade bool, height, timestamp int64) []chan Transfer {
+	concurrency := 1 << kp.poolSize
+	tradeOuts := make([]chan Transfer, concurrency)
+
+	if distributeTrade {
+		ordNum := 0
+		for i := range kp.OrderKeepers {
+			ordNum += kp.OrderKeepers[i].getRoundOrdersNum()
+		}
+
+		for i := range tradeOuts {
+			//assume every new order would have 2 trades and generate 4 transfer
+			tradeOuts[i] = make(chan Transfer, ordNum*4/concurrency)
+		}
+	}
+
+	symbolCh := make(chan string, concurrency)
+	producer := func() {
+		for i := range kp.OrderKeepers {
+			kp.OrderKeepers[i].iterateRoundSelectedPairs(func(symbol string) {
+				symbolCh <- symbol
+			})
+		}
+		close(symbolCh)
+	}
+	matchWorker := func() {
+		for symbol := range symbolCh {
+			kp.matchAndDistributeTradesForSymbol(symbol, height, timestamp, distributeTrade, tradeOuts)
+		}
+
+	}
+
+	if distributeTrade {
+		utils.ConcurrentExecuteAsync(concurrency, producer, matchWorker, func() {
+			for _, tradeOut := range tradeOuts {
+				close(tradeOut)
+			}
+		})
+	} else {
+		utils.ConcurrentExecuteSync(concurrency, producer, matchWorker)
+	}
+	return tradeOuts
+}
+
+func (kp *DexKeeper) MatchSymbols(height, timestamp int64, matchAllSymbols bool) {
+	symbolsToMatch := kp.SelectSymbolsToMatch(height, matchAllSymbols)
+	kp.logger.Debug("symbols to match", "symbols", symbolsToMatch)
+
+	if len(symbolsToMatch) == 0 {
+		kp.logger.Info("No order comes in for the block")
+	} else {
+		kp.matchAndDistributeTrades(false, height, timestamp)
+	}
+
+	kp.ClearAfterMatch()
+}
+
+func (kp *DexKeeper) matchAndDistributeTradesForSymbol(symbol string, height, timestamp int64, distributeTrade bool,
+	tradeOuts []chan Transfer) {
+	engine := kp.engines[symbol]
+	concurrency := len(tradeOuts)
+	orderKeeper := kp.mustGetOrderKeeper(symbol)
+	orders := orderKeeper.getAllOrdersForPair(symbol)
+	// please note there is no logging in matching, expecting to see the order book details
+	// from the exchange's order book stream.
+	if engine.Match(height) {
+		kp.logger.Debug("Match finish:", "symbol", symbol, "lastTradePrice", engine.LastTradePrice)
+		for i := range engine.Trades {
+			t := &engine.Trades[i]
+			updateOrderMsg(orders[t.Bid], t.BuyCumQty, height, timestamp)
+			updateOrderMsg(orders[t.Sid], t.SellCumQty, height, timestamp)
+			if distributeTrade {
+				t1, t2 := TransferFromTrade(t, symbol, orders)
+				c := channelHash(t1.accAddress, concurrency)
+				tradeOuts[c] <- t1
+				c = channelHash(t2.accAddress, concurrency)
+				tradeOuts[c] <- t2
+			}
+		}
+		droppedIds := engine.DropFilledOrder() //delete from order books
+		for _, id := range droppedIds {
+			delete(orders, id) //delete from order cache
+		}
+		kp.logger.Debug("Drop filled orders", "total", droppedIds)
+	} else {
+		// FUTURE-TODO:
+		// when Match() failed, have to unsolicited cancel all the new orders
+		// in this block. Ideally the order IDs would be stored in the EndBlock response,
+		// but this is not implemented yet, pending Tendermint to better handle EndBlock
+		// for index service.
+		kp.logger.Error("Fatal error occurred in matching, cancel all incoming new orders",
+			"symbol", symbol)
+		thisRoundIds := orderKeeper.getRoundOrdersForPair(symbol)
+		for _, id := range thisRoundIds {
+			msg := orders[id]
+			delete(orders, id)
+			if ord, err := engine.Book.RemoveOrder(id, msg.Side, msg.Price); err == nil {
+				kp.logger.Info("Removed due to match failure", "ordID", msg.Id)
+				if distributeTrade {
+					c := channelHash(msg.Sender, concurrency)
+					tradeOuts[c] <- TransferFromCanceled(ord, *msg, true)
+				}
+			} else {
+				kp.logger.Error("Failed to remove order, may be fatal!", "orderID", id)
+			}
+
+			// let the order status publisher publish these abnormal
+			// order status change outs.
+			if kp.CollectOrderInfoForPublish {
+				orderKeeper.appendOrderChangeSync(OrderChange{id, FailedMatching, "", nil})
+			}
+		}
+		return // no need to handle IOC
+	}
+	var iocIDs []string
+	iocIDs = orderKeeper.getRoundIOCOrdersForPair(symbol)
+	for _, id := range iocIDs {
+		if msg, ok := orders[id]; ok {
+			delete(orders, id)
+			if ord, err := engine.Book.RemoveOrder(id, msg.Side, msg.Price); err == nil {
+				kp.logger.Debug("Removed unclosed IOC order", "ordID", msg.Id)
+				if distributeTrade {
+					c := channelHash(msg.Sender, concurrency)
+					tradeOuts[c] <- TransferFromExpired(ord, *msg)
+				}
+			} else {
+				kp.logger.Error("Failed to remove IOC order, may be fatal!", "orderID", id)
+			}
+		}
+	}
+}
+
+// Run as postConsume procedure of async, no concurrent updates of orders map
+func updateOrderMsg(order *OrderInfo, cumQty, height, timestamp int64) {
+	order.CumQty = cumQty
+	order.LastUpdatedHeight = height
+	order.LastUpdatedTimestamp = timestamp
+}
diff --git a/plugins/dex/order/keeper_recovery.go b/plugins/dex/order/keeper_recovery.go
index cd8ea2a29..860e6dc82 100644
--- a/plugins/dex/order/keeper_recovery.go
+++ b/plugins/dex/order/keeper_recovery.go
@@ -28,9 +28,10 @@ import (
 )
 
 type OrderBookSnapshot struct {
-	Buys           []me.PriceLevel `json:"buys"`
-	Sells          []me.PriceLevel `json:"sells"`
-	LastTradePrice int64           `json:"lasttradeprice"`
+	Buys            []me.PriceLevel `json:"buys"`
+	Sells           []me.PriceLevel `json:"sells"`
+	LastTradePrice  int64           `json:"lasttradeprice"`
+	LastMatchHeight int64           `json:"lastmatchheight"`
 }
 
 type ActiveOrders struct {
@@ -59,12 +60,16 @@ func compressAndSave(snapshot interface{}, cdc *wire.Codec, key string, kv sdk.K
 	return nil
 }
 
-func (kp *Keeper) SnapShotOrderBook(ctx sdk.Context, height int64) (effectedStoreKeys []string, err error) {
+func (kp *DexKeeper) SnapShotOrderBook(ctx sdk.Context, height int64) (effectedStoreKeys []string, err error) {
 	kvstore := ctx.KVStore(kp.storeKey)
 	effectedStoreKeys = make([]string, 0)
 	for pair, eng := range kp.engines {
 		buys, sells := eng.Book.GetAllLevels()
-		snapshot := OrderBookSnapshot{Buys: buys, Sells: sells, LastTradePrice: eng.LastTradePrice}
+		var snapshot OrderBookSnapshot
+		snapshot = OrderBookSnapshot{Buys: buys, Sells: sells, LastTradePrice: eng.LastTradePrice}
+		if sdk.IsUpgrade(upgrade.BEP8) {
+			snapshot.LastMatchHeight = eng.LastMatchHeight
+		}
 		key := genOrderBookSnapshotKey(height, pair)
 		effectedStoreKeys = append(effectedStoreKeys, key)
 		err := compressAndSave(snapshot, kp.cdc, key, kvstore)
@@ -76,7 +81,8 @@ func (kp *Keeper) SnapShotOrderBook(ctx sdk.Context, height int64) (effectedStor
 
 	msgKeys := make([]string, 0)
 	idSymbolMap := make(map[string]string)
-	for symbol, orderMap := range kp.allOrders {
+	allOrders := kp.GetAllOrders()
+	for symbol, orderMap := range allOrders {
 		for id := range orderMap {
 			idSymbolMap[id] = symbol
 			msgKeys = append(msgKeys, id)
@@ -85,7 +91,7 @@ func (kp *Keeper) SnapShotOrderBook(ctx sdk.Context, height int64) (effectedStor
 	sort.Strings(msgKeys)
 	msgs := make([]OrderInfo, len(msgKeys), len(msgKeys))
 	for i, key := range msgKeys {
-		msgs[i] = *kp.allOrders[idSymbolMap[key]][key]
+		msgs[i] = *allOrders[idSymbolMap[key]][key]
 	}
 
 	snapshot := ActiveOrders{Orders: msgs}
@@ -95,7 +101,7 @@ func (kp *Keeper) SnapShotOrderBook(ctx sdk.Context, height int64) (effectedStor
 	return effectedStoreKeys, compressAndSave(snapshot, kp.cdc, key, kvstore)
 }
 
-func (kp *Keeper) LoadOrderBookSnapshot(ctx sdk.Context, latestBlockHeight int64, timeOfLatestBlock time.Time, blockInterval, daysBack int) (int64, error) {
+func (kp *DexKeeper) LoadOrderBookSnapshot(ctx sdk.Context, latestBlockHeight int64, timeOfLatestBlock time.Time, blockInterval, daysBack int) (int64, error) {
 	height := kp.GetLastBreatheBlockHeight(ctx, latestBlockHeight, timeOfLatestBlock, blockInterval, daysBack)
 	ctx.Logger().Info("Loading order book snapshot from last breathe block", "blockHeight", height)
 	allPairs := kp.PairMapper.ListAllTradingPairs(ctx)
@@ -111,6 +117,7 @@ func (kp *Keeper) LoadOrderBookSnapshot(ctx sdk.Context, latestBlockHeight int64
 		return height, nil
 	}
 
+	upgrade.Mgr.SetHeight(height)
 	kvStore := ctx.KVStore(kp.storeKey)
 	for _, pair := range allPairs {
 		symbol := pair.GetSymbol()
@@ -145,6 +152,11 @@ func (kp *Keeper) LoadOrderBookSnapshot(ctx sdk.Context, latestBlockHeight int64
 			eng.Book.InsertPriceLevel(&pl, me.SELLSIDE)
 		}
 		eng.LastTradePrice = ob.LastTradePrice
+		if sdk.IsUpgrade(upgrade.BEP8) {
+			eng.LastMatchHeight = ob.LastMatchHeight
+		} else {
+			eng.LastMatchHeight = height
+		}
 		ctx.Logger().Info("Successfully Loaded order snapshot", "pair", pair)
 	}
 	key := genActiveOrdersSnapshotKey(height)
@@ -168,25 +180,13 @@ func (kp *Keeper) LoadOrderBookSnapshot(ctx sdk.Context, latestBlockHeight int64
 	for _, m := range ao.Orders {
 		orderHolder := m
 		symbol := strings.ToUpper(m.Symbol)
-		kp.allOrders[symbol][m.Id] = &orderHolder
-		if m.CreatedHeight == height {
-			kp.roundOrders[symbol] = append(kp.roundOrders[symbol], m.Id)
-			if m.TimeInForce == TimeInForce.IOC {
-				kp.roundIOCOrders[symbol] = append(kp.roundIOCOrders[symbol], m.Id)
-			}
-		}
-		if kp.CollectOrderInfoForPublish {
-			if _, exists := kp.OrderInfosForPub[m.Id]; !exists {
-				bnclog.Debug("add order to order changes map, during load snapshot, from active orders", "orderId", m.Id)
-				kp.OrderInfosForPub[m.Id] = &orderHolder
-			}
-		}
+		kp.ReloadOrder(symbol, &orderHolder, height)
 	}
 	ctx.Logger().Info("Recovered active orders. Snapshot is fully loaded")
 	return height, nil
 }
 
-func (kp *Keeper) replayOneBlocks(logger log.Logger, block *tmtypes.Block, stateDB dbm.DB, txDecoder sdk.TxDecoder,
+func (kp *DexKeeper) replayOneBlocks(logger log.Logger, block *tmtypes.Block, stateDB dbm.DB, txDecoder sdk.TxDecoder,
 	height int64, timestamp time.Time) {
 	if block == nil {
 		logger.Error("No block is loaded. Ignore replay for orderbook")
@@ -234,7 +234,7 @@ func (kp *Keeper) replayOneBlocks(logger log.Logger, block *tmtypes.Block, state
 				err := kp.RemoveOrder(msg.RefId, msg.Symbol, func(ord me.OrderPart) {
 					if kp.CollectOrderInfoForPublish {
 						bnclog.Debug("deleted order from order changes map", "orderId", msg.RefId, "isRecovery", true)
-						delete(kp.OrderInfosForPub, msg.RefId)
+						kp.RemoveOrderInfosForPub(msg.Symbol, msg.RefId)
 					}
 				})
 				if err != nil {
@@ -245,10 +245,10 @@ func (kp *Keeper) replayOneBlocks(logger log.Logger, block *tmtypes.Block, state
 		}
 	}
 	logger.Info("replayed all tx. Starting match", "height", height)
-	kp.MatchAll(height, t) //no need to check result
+	kp.MatchSymbols(height, t, false) //no need to check result
 }
 
-func (kp *Keeper) ReplayOrdersFromBlock(ctx sdk.Context, bc *tmstore.BlockStore, stateDb dbm.DB, lastHeight, breatheHeight int64,
+func (kp *DexKeeper) ReplayOrdersFromBlock(ctx sdk.Context, bc *tmstore.BlockStore, stateDb dbm.DB, lastHeight, breatheHeight int64,
 	txDecoder sdk.TxDecoder) error {
 	for i := breatheHeight + 1; i <= lastHeight; i++ {
 		block := bc.LoadBlock(i)
@@ -259,7 +259,7 @@ func (kp *Keeper) ReplayOrdersFromBlock(ctx sdk.Context, bc *tmstore.BlockStore,
 	return nil
 }
 
-func (kp *Keeper) initOrderBook(ctx sdk.Context, blockInterval, daysBack int, blockStore *tmstore.BlockStore, stateDB dbm.DB, lastHeight int64, txDecoder sdk.TxDecoder) {
+func (kp *DexKeeper) initOrderBook(ctx sdk.Context, blockInterval, daysBack int, blockStore *tmstore.BlockStore, stateDB dbm.DB, lastHeight int64, txDecoder sdk.TxDecoder) {
 	var timeOfLatestBlock time.Time
 	if lastHeight == 0 {
 		timeOfLatestBlock = utils.Now()
@@ -272,7 +272,7 @@ func (kp *Keeper) initOrderBook(ctx sdk.Context, blockInterval, daysBack int, bl
 		panic(err)
 	}
 	logger := ctx.Logger().With("module", "dex")
-	logger.Info("Initialized Block Store for replay", "toHeight", lastHeight)
+	logger.Info("Initialized Block Store for replay", "fromHeight", height, "toHeight", lastHeight)
 	err = kp.ReplayOrdersFromBlock(ctx.WithLogger(logger), blockStore, stateDB, lastHeight, height, txDecoder)
 	if err != nil {
 		panic(err)
diff --git a/plugins/dex/order/keeper_test.go b/plugins/dex/order/keeper_test.go
index a895fb669..7dbeb4b02 100644
--- a/plugins/dex/order/keeper_test.go
+++ b/plugins/dex/order/keeper_test.go
@@ -52,12 +52,12 @@ func MakeCodec() *wire.Codec {
 	return cdc
 }
 
-func MakeKeeper(cdc *wire.Codec) *Keeper {
+func MakeKeeper(cdc *wire.Codec) *DexKeeper {
 	accKeeper := auth.NewAccountKeeper(cdc, common.AccountStoreKey, types.ProtoAppAccount)
 	codespacer := sdk.NewCodespacer()
 	pairMapper := store.NewTradingPairMapper(cdc, common.PairStoreKey)
-	keeper := NewKeeper(common.DexStoreKey, accKeeper, pairMapper,
-		codespacer.RegisterNext(dextypes.DefaultCodespace), 2, cdc, true)
+	keeper := NewDexKeeper(common.DexStoreKey, accKeeper, pairMapper, codespacer.RegisterNext(dextypes.DefaultCodespace), 2, cdc, true)
+
 	return keeper
 }
 
@@ -107,6 +107,8 @@ func TestKeeper_MatchFailure(t *testing.T) {
 	msg = NewNewOrderMsg(accAdd, "123462", Side.BUY, "XYZ-000_BNB", 99000, 15000000)
 	ord = OrderInfo{msg, 42, 0, 42, 0, 0, "", 0}
 	keeper.AddOrder(ord, false)
+	symbolsToMatch := keeper.SelectSymbolsToMatch(ctx.BlockHeader().Height, false)
+	logger.Info("symbols to match", "symbols", symbolsToMatch)
 	tradeOuts := keeper.matchAndDistributeTrades(true, 42, 0)
 	c := channelHash(accAdd, 4)
 	i := 0
@@ -182,7 +184,7 @@ func MakeAddress() (sdk.AccAddress, secp256k1.PrivKeySecp256k1) {
 	return addr, privKey
 }
 
-func effectedStoredKVPairs(keeper *Keeper, ctx sdk.Context, keys []string) map[string][]byte {
+func effectedStoredKVPairs(keeper *DexKeeper, ctx sdk.Context, keys []string) map[string][]byte {
 	res := make(map[string][]byte, len(keys))
 	store := ctx.KVStore(keeper.storeKey)
 	for _, key := range keys {
@@ -217,8 +219,8 @@ func TestKeeper_SnapShotOrderBook(t *testing.T) {
 	keeper.AddOrder(OrderInfo{msg, 42, 84, 42, 84, 0, "", 0}, false)
 	msg = NewNewOrderMsg(accAdd, "123462", Side.BUY, "XYZ-000_BNB", 96000, 1500000)
 	keeper.AddOrder(OrderInfo{msg, 42, 84, 42, 84, 0, "", 0}, false)
-	assert.Equal(1, len(keeper.allOrders))
-	assert.Equal(7, len(keeper.allOrders["XYZ-000_BNB"]))
+	assert.Equal(1, len(keeper.GetAllOrders()))
+	assert.Equal(7, len(keeper.GetAllOrdersForPair("XYZ-000_BNB")))
 	assert.Equal(1, len(keeper.engines))
 
 	effectedStoredKeys1, err := keeper.SnapShotOrderBook(ctx, 43)
@@ -231,8 +233,8 @@ func TestKeeper_SnapShotOrderBook(t *testing.T) {
 	keeper.MarkBreatheBlock(ctx, 43, time.Now())
 	keeper2 := MakeKeeper(cdc)
 	h, err := keeper2.LoadOrderBookSnapshot(ctx, 43, utils.Now(), 0, 10)
-	assert.Equal(7, len(keeper2.allOrders["XYZ-000_BNB"]))
-	o123459 := keeper2.allOrders["XYZ-000_BNB"]["123459"]
+	assert.Equal(7, len(keeper2.GetAllOrdersForPair("XYZ-000_BNB")))
+	o123459 := keeper2.GetAllOrdersForPair("XYZ-000_BNB")["123459"]
 	assert.Equal(int64(98000), o123459.Price)
 	assert.Equal(int64(1000000), o123459.Quantity)
 	assert.Equal(int64(0), o123459.CumQty)
@@ -271,25 +273,25 @@ func TestKeeper_SnapShotAndLoadAfterMatch(t *testing.T) {
 	keeper.AddOrder(info123457, false)
 	msg := NewNewOrderMsg(accAdd, "123458", Side.SELL, "XYZ-000_BNB", 100000, 2000000)
 	keeper.AddOrder(OrderInfo{msg, 42, 0, 42, 0, 0, "", 0}, false)
-	assert.Equal(1, len(keeper.allOrders))
-	assert.Equal(3, len(keeper.allOrders["XYZ-000_BNB"]))
+	assert.Equal(1, len(keeper.GetAllOrders()))
+	assert.Equal(3, len(keeper.GetAllOrdersForPair("XYZ-000_BNB")))
 	assert.Equal(1, len(keeper.engines))
 
-	keeper.MatchAll(42, 0)
+	keeper.MatchSymbols(42, 0, false)
 	_, err := keeper.SnapShotOrderBook(ctx, 43)
 	assert.Nil(err)
 	keeper.MarkBreatheBlock(ctx, 43, time.Now())
 	keeper2 := MakeKeeper(cdc)
 	h, err := keeper2.LoadOrderBookSnapshot(ctx, 43, utils.Now(), 0, 10)
-	assert.Equal(2, len(keeper2.allOrders["XYZ-000_BNB"]))
-	assert.Equal(int64(102000), keeper2.allOrders["XYZ-000_BNB"]["123456"].Price)
-	assert.Equal(int64(2000000), keeper2.allOrders["XYZ-000_BNB"]["123456"].CumQty)
-	assert.Equal(int64(10000), keeper2.allOrders["XYZ-000_BNB"]["123457"].Price)
-	assert.Equal(int64(0), keeper2.allOrders["XYZ-000_BNB"]["123457"].CumQty)
+	assert.Equal(2, len(keeper2.GetAllOrdersForPair("XYZ-000_BNB")))
+	assert.Equal(int64(102000), keeper2.GetAllOrdersForPair("XYZ-000_BNB")["123456"].Price)
+	assert.Equal(int64(2000000), keeper2.GetAllOrdersForPair("XYZ-000_BNB")["123456"].CumQty)
+	assert.Equal(int64(10000), keeper2.GetAllOrdersForPair("XYZ-000_BNB")["123457"].Price)
+	assert.Equal(int64(0), keeper2.GetAllOrdersForPair("XYZ-000_BNB")["123457"].CumQty)
 	info123456_Changed := info123456
 	info123456_Changed.CumQty = 2000000
-	assert.Equal(info123456_Changed, *keeper2.OrderInfosForPub["123456"])
-	assert.Equal(info123457, *keeper2.OrderInfosForPub["123457"])
+	assert.Equal(info123456_Changed, *keeper2.GetOrderInfosForPub(PairType.BEP2)["123456"])
+	assert.Equal(info123457, *keeper2.GetOrderInfosForPub(PairType.BEP2)["123457"])
 	assert.Equal(1, len(keeper2.engines))
 	assert.Equal(int64(102000), keeper2.engines["XYZ-000_BNB"].LastTradePrice)
 	assert.Equal(int64(43), h)
@@ -325,7 +327,7 @@ func TestKeeper_SnapShotOrderBookEmpty(t *testing.T) {
 	keeper2 := MakeKeeper(cdc)
 	h, err := keeper2.LoadOrderBookSnapshot(ctx, 43, utils.Now(), 0, 10)
 	assert.Equal(int64(43), h)
-	assert.Equal(0, len(keeper2.allOrders["XYZ-000_BNB"]))
+	assert.Equal(0, len(keeper2.GetAllOrdersForPair("XYZ-000_BNB")))
 	buys, sells = keeper2.engines["XYZ-000_BNB"].Book.GetAllLevels()
 	assert.Equal(0, len(buys))
 	assert.Equal(0, len(sells))
@@ -538,7 +540,7 @@ func getAccountCache(cdc *codec.Codec, ms sdk.MultiStore, accountKey *sdk.KVStor
 	return auth.NewAccountCache(accountStoreCache)
 }
 
-func setup() (ctx sdk.Context, mapper auth.AccountKeeper, keeper *Keeper) {
+func setup() (ctx sdk.Context, mapper auth.AccountKeeper, keeper *DexKeeper) {
 	ms, capKey, capKey2 := testutils.SetupMultiStoreForUnitTest()
 	cdc := wire.NewCodec()
 	types.RegisterWire(cdc)
@@ -547,7 +549,8 @@ func setup() (ctx sdk.Context, mapper auth.AccountKeeper, keeper *Keeper) {
 	accountCache := getAccountCache(cdc, ms, capKey)
 	pairMapper := store.NewTradingPairMapper(cdc, common.PairStoreKey)
 	ctx = sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, sdk.RunTxModeDeliver, log.NewNopLogger()).WithAccountCache(accountCache)
-	keeper = NewKeeper(capKey2, mapper, pairMapper, sdk.NewCodespacer().RegisterNext(dextypes.DefaultCodespace), 2, cdc, false)
+
+	keeper = NewDexKeeper(capKey2, mapper, pairMapper, sdk.NewCodespacer().RegisterNext(dextypes.DefaultCodespace), 2, cdc, false)
 	return
 }
 
@@ -579,13 +582,13 @@ func TestKeeper_ExpireOrders(t *testing.T) {
 	require.Len(t, sells, 1)
 	require.Len(t, sells[0].Orders, 1)
 	require.Equal(t, int64(2e8), sells[0].TotalLeavesQty())
-	require.Len(t, keeper.allOrders["ABC-000_BNB"], 1)
+	require.Len(t, keeper.GetAllOrdersForPair("ABC-000_BNB"), 1)
 	buys, sells = keeper.engines["XYZ-000_BNB"].Book.GetAllLevels()
 	require.Len(t, buys, 1)
 	require.Len(t, sells, 0)
 	require.Len(t, buys[0].Orders, 1)
 	require.Equal(t, int64(2e6), buys[0].TotalLeavesQty())
-	require.Len(t, keeper.allOrders["XYZ-000_BNB"], 1)
+	require.Len(t, keeper.GetAllOrdersForPair("XYZ-000_BNB"), 1)
 	expectFees := types.NewFee(sdk.Coins{
 		sdk.NewCoin("BNB", 6e4),
 		sdk.NewCoin("ABC-000", 1e7),
@@ -603,6 +606,52 @@ func TestKeeper_ExpireOrders(t *testing.T) {
 	fees.Pool.Clear()
 }
 
+func TestKeeper_ExpireOrdersBasedOnPrice(t *testing.T) {
+	ctx, am, keeper := setup()
+	upgrade.Mgr.AddUpgradeHeight(upgrade.BEP67, -1)
+	keeper.FeeManager.UpdateConfig(NewTestFeeConfig())
+	_, acc := testutils.NewAccount(ctx, am, 0)
+	addr := acc.GetAddress()
+	keeper.AddEngine(dextypes.NewTradingPair("ABC-000", "BNB", 1e6))
+	keeper.AddEngine(dextypes.NewTradingPair("XYZ-000", "BNB", 1e6))
+
+	keeper.AddOrder(OrderInfo{NewNewOrderMsg(addr, "1", Side.BUY, "ABC-000_BNB", 3e6, 3e6), 4999, 0, 5001, 0, 0, "", 0}, false)
+	keeper.AddOrder(OrderInfo{NewNewOrderMsg(addr, "2", Side.BUY, "ABC-000_BNB", 1e6, 1e6), 10000, 0, 10000, 0, 0, "", 0}, false)
+	keeper.AddOrder(OrderInfo{NewNewOrderMsg(addr, "3", Side.BUY, "ABC-000_BNB", 2e6, 2e6), 10000, 0, 10000, 0, 0, "", 0}, false)
+	keeper.AddOrder(OrderInfo{NewNewOrderMsg(addr, "4", Side.BUY, "XYZ-000_BNB", 1e6, 2e6), 10000, 0, 10000, 0, 0, "", 0}, false)
+	keeper.AddOrder(OrderInfo{NewNewOrderMsg(addr, "5", Side.SELL, "ABC-000_BNB", 1e6, 1e8), 10000, 0, 10000, 0, 0, "", 0}, false)
+	keeper.AddOrder(OrderInfo{NewNewOrderMsg(addr, "6", Side.SELL, "ABC-000_BNB", 2e6, 2e8), 15000, 0, 15000, 0, 0, "", 0}, false)
+	keeper.AddOrder(OrderInfo{NewNewOrderMsg(addr, "7", Side.BUY, "XYZ-000_BNB", 2e6, 2e6), 20000, 0, 20000, 0, 0, "", 0}, false)
+	keeper.AddOrder(OrderInfo{NewNewOrderMsg(addr, "8", Side.SELL, "XYZ-000_BNB", 2e6, 2e6), 3000, 0, 3000, 0, 0, "", 0}, false)
+
+	acc.(types.NamedAccount).SetLockedCoins(sdk.Coins{
+		sdk.NewCoin("ABC-000", 3e8),
+		sdk.NewCoin("BNB", 11e4),
+		sdk.NewCoin("XYZ-000", 2e6),
+	}.Sort())
+	am.SetAccount(ctx, acc)
+
+	breathTime, _ := time.Parse(time.RFC3339, "2018-01-02T00:00:01Z")
+	keeper.MarkBreatheBlock(ctx, 5000, breathTime)
+	keeper.MarkBreatheBlock(ctx, 15000, breathTime.AddDate(0, 0, 27))
+
+	keeper.ExpireOrders(ctx, breathTime.AddDate(0, 0, 30), nil)
+	buys, sells := keeper.engines["ABC-000_BNB"].Book.GetAllLevels()
+	require.Len(t, buys, 2)
+	require.Len(t, sells, 2)
+	require.Len(t, sells[0].Orders, 1)
+	require.Equal(t, int64(1e8), sells[0].TotalLeavesQty())
+	require.Len(t, keeper.GetAllOrdersForPair("ABC-000_BNB"), 4)
+	buys, sells = keeper.engines["XYZ-000_BNB"].Book.GetAllLevels()
+	require.Len(t, buys, 2)
+	require.Len(t, sells, 0)
+	require.Len(t, buys[0].Orders, 1)
+	require.Equal(t, int64(2e6), buys[0].TotalLeavesQty())
+	require.Len(t, keeper.GetAllOrdersForPair("XYZ-000_BNB"), 2)
+	fees.Pool.Clear()
+	upgrade.Mgr.AddUpgradeHeight(upgrade.BEP67, 0)
+}
+
 func TestKeeper_DetermineLotSize(t *testing.T) {
 	assert := assert.New(t)
 	ctx, _, keeper := setup()
@@ -632,6 +681,42 @@ func TestKeeper_DetermineLotSize(t *testing.T) {
 	assert.Equal(int64(1e5), lotsize) // wma price of BNB/BTC-000 is between 1e7 and 1e8
 }
 
+func TestKeeper_DetermineLotSize_SupportBUSD(t *testing.T) {
+	assert := assert.New(t)
+	ctx, _, keeper := setup()
+
+	upgrade.Mgr.AddUpgradeHeight(upgrade.BEP70, -1)
+	keeper.SetBUSDSymbol("BUSD-BD1")
+
+	// no recentPrices recorded, use engine.LastTradePrice
+	pair1 := dextypes.NewTradingPairWithLotSize("BNB", "BUSD-BD1", 1e6, 1e5)
+	keeper.AddEngine(pair1)
+	pair2 := dextypes.NewTradingPairWithLotSize("AAA-000", "BUSD-BD1", 1e6, 1e7)
+	keeper.AddEngine(pair2)
+	lotsize := keeper.DetermineLotSize("BNB", "BUSD-BD1", 1e6)
+	assert.Equal(int64(1e5), lotsize)
+	lotsize = keeper.DetermineLotSize("BUSD-BD1", "BNB", 1e10)
+	assert.Equal(int64(1e3), lotsize)
+	lotsize = keeper.DetermineLotSize("AAA-000", "BUSD-BD1", 1e6)
+	assert.Equal(int64(1e5), lotsize)
+	lotsize = keeper.DetermineLotSize("BUSD-BD1", "AAA-000", 1e10)
+	assert.Equal(int64(1e3), lotsize)
+
+	// no trading yet, use list price
+	lotsize = keeper.DetermineLotSize("BBB-000", "BUSD-BD1", 1e8)
+	assert.Equal(int64(1e3), lotsize)
+
+	// store some recentPrices
+	keeper.StoreTradePrices(ctx.WithBlockHeight(1 * pricesStoreEvery))
+	keeper.engines[pair1.GetSymbol()].LastTradePrice = 1e8
+	keeper.engines[pair2.GetSymbol()].LastTradePrice = 1e8
+	keeper.StoreTradePrices(ctx.WithBlockHeight(2 * pricesStoreEvery))
+	lotsize = keeper.DetermineLotSize("AAA-000", "BUSD-BD1", 1e4)
+	assert.Equal(int64(1e6), lotsize) // wma price of AAA-000/BNB is between 1e7 and 1e8
+	lotsize = keeper.DetermineLotSize("BUSD-BD1", "AAA-000", 1e12)
+	assert.Equal(int64(1e5), lotsize) // wma price of BUSD-BD1/BNB is between 1e8 and 1e9
+}
+
 func TestKeeper_UpdateTickSizeAndLotSize(t *testing.T) {
 	assert := assert.New(t)
 	ctx, _, keeper := setup()
@@ -716,7 +801,7 @@ func TestOpenOrders_AfterMatch(t *testing.T) {
 	assert.Equal(1, len(res))
 
 	// match existing two orders
-	keeper.MatchAll(43, 86)
+	keeper.MatchSymbols(43, 86, false)
 
 	// after match, the original buy order's cumQty and latest updated fields should be updated
 	res = keeper.GetOpenOrders("NNB_BNB", zc)
@@ -750,7 +835,7 @@ func TestOpenOrders_AfterMatch(t *testing.T) {
 	assert.Equal(int64(88), res[0].LastUpdatedTimestamp)
 
 	// match existing two orders
-	keeper.MatchAll(44, 88)
+	keeper.MatchSymbols(44, 88, false)
 
 	// after match, all orders should be closed
 	res = keeper.GetOpenOrders("NNB_BNB", zc)
@@ -800,12 +885,12 @@ func TestKeeper_DelistTradingPair(t *testing.T) {
 	keeper.AddOrder(OrderInfo{msg, 42, 84, 42, 84, 0, "", 0}, false)
 	msg = NewNewOrderMsg(addr, "123462", Side.BUY, "XYZ-000_BNB", 4e6, 1e6)
 	keeper.AddOrder(OrderInfo{msg, 42, 84, 42, 84, 0, "", 0}, false)
-	assert.Equal(1, len(keeper.allOrders))
-	assert.Equal(9, len(keeper.allOrders["XYZ-000_BNB"]))
+	assert.Equal(1, len(keeper.GetAllOrders()))
+	assert.Equal(9, len(keeper.GetAllOrdersForPair("XYZ-000_BNB")))
 	assert.Equal(1, len(keeper.engines))
 
 	keeper.DelistTradingPair(ctx, "XYZ-000_BNB", nil)
-	assert.Equal(0, len(keeper.allOrders))
+	assert.Equal(0, len(keeper.GetAllOrders()))
 	assert.Equal(0, len(keeper.engines))
 
 	expectFees := types.NewFee(sdk.Coins{
@@ -815,6 +900,64 @@ func TestKeeper_DelistTradingPair(t *testing.T) {
 	require.Equal(t, expectFees, fees.Pool.BlockFees())
 }
 
+func TestKeeper_DelistMiniTradingPair(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	assert := assert.New(t)
+	ctx, am, keeper := setup()
+	fees.Pool.Clear()
+	keeper.FeeManager.UpdateConfig(NewTestFeeConfig())
+	_, acc := testutils.NewAccount(ctx, am, 0)
+	addr := acc.GetAddress()
+
+	tradingPair := dextypes.NewTradingPair("XYZ-000M", "BNB", 1e8)
+	keeper.PairMapper.AddTradingPair(ctx, tradingPair)
+	keeper.AddEngine(tradingPair)
+
+	acc.(types.NamedAccount).SetLockedCoins(sdk.Coins{
+		sdk.NewCoin("BNB", 11e4),
+		sdk.NewCoin("XYZ-000M", 4e4),
+	}.Sort())
+
+	acc.(types.NamedAccount).SetCoins(sdk.Coins{
+		sdk.NewCoin("XYZ-000M", 4e5),
+	}.Sort())
+
+	am.SetAccount(ctx, acc)
+
+	msg := NewNewOrderMsg(addr, "123456", Side.BUY, "XYZ-000M_BNB", 1e6, 1e6)
+	keeper.AddOrder(OrderInfo{msg, 42, 84, 42, 84, 0, "", 0}, false)
+	msg = NewNewOrderMsg(addr, "1234562", Side.BUY, "XYZ-000M_BNB", 1e6, 1e6)
+	keeper.AddOrder(OrderInfo{msg, 42, 84, 42, 84, 0, "", 0}, false)
+	msg = NewNewOrderMsg(addr, "123457", Side.BUY, "XYZ-000M_BNB", 2e6, 1e6)
+	keeper.AddOrder(OrderInfo{msg, 42, 84, 42, 84, 0, "", 0}, false)
+	msg = NewNewOrderMsg(addr, "123458", Side.BUY, "XYZ-000M_BNB", 3e6, 1e6)
+	keeper.AddOrder(OrderInfo{msg, 42, 84, 42, 84, 0, "", 0}, false)
+	msg = NewNewOrderMsg(addr, "123459", Side.SELL, "XYZ-000M_BNB", 5e6, 1e4)
+	keeper.AddOrder(OrderInfo{msg, 42, 84, 42, 84, 0, "", 0}, false)
+	msg = NewNewOrderMsg(addr, "123460", Side.SELL, "XYZ-000M_BNB", 6e6, 1e4)
+	keeper.AddOrder(OrderInfo{msg, 42, 84, 42, 84, 0, "", 0}, false)
+	msg = NewNewOrderMsg(addr, "1234602", Side.SELL, "XYZ-000M_BNB", 6e6, 1e4)
+	keeper.AddOrder(OrderInfo{msg, 42, 84, 42, 84, 0, "", 0}, false)
+	msg = NewNewOrderMsg(addr, "123461", Side.SELL, "XYZ-000M_BNB", 7e6, 1e4)
+	keeper.AddOrder(OrderInfo{msg, 42, 84, 42, 84, 0, "", 0}, false)
+	msg = NewNewOrderMsg(addr, "123462", Side.BUY, "XYZ-000M_BNB", 4e6, 1e6)
+	keeper.AddOrder(OrderInfo{msg, 42, 84, 42, 84, 0, "", 0}, false)
+	assert.Equal(1, len(keeper.GetAllOrders()))
+	assert.Equal(9, len(keeper.GetAllOrdersForPair("XYZ-000M_BNB")))
+	assert.Equal(1, len(keeper.engines))
+
+	keeper.DelistTradingPair(ctx, "XYZ-000M_BNB", nil)
+	assert.Equal(0, len(keeper.GetAllOrders()))
+	assert.Equal(0, len(keeper.engines))
+
+	expectFees := types.NewFee(sdk.Coins{
+		sdk.NewCoin("BNB", 10e4),
+		sdk.NewCoin("XYZ-000M", 4e5),
+	}.Sort(), types.FeeForProposer)
+	require.Equal(t, expectFees, fees.Pool.BlockFees())
+}
+
 //
 func TestKeeper_DelistTradingPair_Empty(t *testing.T) {
 	assert := assert.New(t)
@@ -826,12 +969,12 @@ func TestKeeper_DelistTradingPair_Empty(t *testing.T) {
 	keeper.PairMapper.AddTradingPair(ctx, tradingPair)
 	keeper.AddEngine(tradingPair)
 
-	assert.Equal(1, len(keeper.allOrders))
-	assert.Equal(0, len(keeper.allOrders["XYZ-001_BNB"]))
+	assert.Equal(1, len(keeper.GetAllOrders()))
+	assert.Equal(0, len(keeper.GetAllOrdersForPair("XYZ-001_BNB")))
 	assert.Equal(1, len(keeper.engines))
 
 	keeper.DelistTradingPair(ctx, "XYZ-001_BNB", nil)
-	assert.Equal(0, len(keeper.allOrders))
+	assert.Equal(0, len(keeper.GetAllOrders()))
 	assert.Equal(0, len(keeper.engines))
 
 	expectFees := types.NewFee(sdk.Coins(nil), types.ZeroFee)
@@ -890,3 +1033,114 @@ func TestKeeper_CanDelistTradingPair(t *testing.T) {
 	require.NotNil(t, err)
 	require.Contains(t, err.Error(), "trading pair BBB-000_AAA-000 should not exist before delisting BNB_BBB-000")
 }
+
+func TestKeeper_CanListTradingPair_SupportBUSD(t *testing.T) {
+	ctx, _, keeper := setup()
+	// before upgrade
+	err := keeper.CanListTradingPair(ctx, "AAA-000", "BUSD-BD1")
+	require.NotNil(t, err)
+	require.Contains(t, err.Error(), "token AAA-000 should be listed against BNB before against BUSD-BD1")
+
+	err = keeper.CanListTradingPair(ctx, "BUSD-BD1", "AAA-000")
+	require.NotNil(t, err)
+	require.Contains(t, err.Error(), "token BUSD-BD1 should be listed against BNB before against AAA-000")
+
+	// upgraded, but BNB-BUSD pair does not exist
+	upgrade.Mgr.AddUpgradeHeight(upgrade.BEP70, -1)
+	keeper.SetBUSDSymbol("BUSD-BD1")
+	err = keeper.CanListTradingPair(ctx, "AAA-000", "BUSD-BD1")
+	require.NotNil(t, err)
+	require.Contains(t, err.Error(), "token AAA-000 should be listed against BNB before against BUSD-BD1")
+
+	err = keeper.CanListTradingPair(ctx, "BUSD-BD1", "AAA-000")
+	require.NotNil(t, err)
+	require.Contains(t, err.Error(), "token BUSD-BD1 should be listed against BNB before against AAA-000")
+
+	//upgraded, BNB-BUSD pair does exist
+	err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair("BUSD-BD1", types.NativeTokenSymbol, 1e8))
+	require.Nil(t, err)
+
+	err = keeper.CanListTradingPair(ctx, "AAA-000", "BUSD-BD1")
+	require.Nil(t, err)
+
+	err = keeper.CanListTradingPair(ctx, "BUSD-BD1", "AAA-000")
+	require.Nil(t, err)
+
+	//upgraded, ABC-XYZ pair listing is still dependent on ABC-BNB and XYZ-BNB pairs
+	//BUSD-ABC or XYZ-BUSD pairs will be no help at this case
+	err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair("BUSD-BD1", "AAA-000", 1e8))
+	require.Nil(t, err)
+	err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair("BUSD-BD1", "XYZ-000", 1e8))
+	require.Nil(t, err)
+
+	err = keeper.CanListTradingPair(ctx, "AAA-000", "XYZ-000")
+	require.NotNil(t, err)
+	require.Contains(t, err.Error(), "token AAA-000 should be listed against BNB before against XYZ-000")
+
+	err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair(types.NativeTokenSymbol, "AAA-000", 1e8))
+	require.Nil(t, err)
+
+	err = keeper.CanListTradingPair(ctx, "AAA-000", "XYZ-000")
+	require.NotNil(t, err)
+	require.Contains(t, err.Error(), "token XYZ-000 should be listed against BNB before listing AAA-000 against XYZ-000")
+
+	err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair(types.NativeTokenSymbol, "XYZ-000", 1e8))
+	require.Nil(t, err)
+
+	err = keeper.CanListTradingPair(ctx, "AAA-000", "XYZ-000")
+	require.Nil(t, err)
+}
+
+func TestKeeper_CanDelistTradingPair_SupportBUSD(t *testing.T) {
+	ctx, _, keeper := setup()
+	err := keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair("AAA-000", "BUSD-BD1", 1e8))
+	err = keeper.CanDelistTradingPair(ctx, "AAA-000", "BUSD-BD1")
+	require.Nil(t, err)
+
+	err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair("BUSD-BD1", "AAA-000", 1e8))
+	err = keeper.CanDelistTradingPair(ctx, "BUSD-BD1", "AAA-000")
+	require.Nil(t, err)
+
+	// delisting AAA-XYZ will not depends on BUSD-AAA or BUSD-XYZ
+	err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair(types.NativeTokenSymbol, "AAA-000", 1e8))
+	err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair(types.NativeTokenSymbol, "XYZ-000", 1e8))
+	err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair("AAA-000", "XYZ-000", 1e8))
+	err = keeper.CanDelistTradingPair(ctx, "AAA-000", "XYZ-000")
+	require.Nil(t, err)
+}
+
+func TestKeeper_CanDelistMiniTradingPair_SupportBUSD(t *testing.T) {
+	ctx, _, keeper := setup()
+	err := keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair("AAA-000M", "BUSD-BD1", 1e8))
+	err = keeper.CanDelistTradingPair(ctx, "AAA-000M", "BUSD-BD1")
+	require.Nil(t, err)
+
+	err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair("BUSD-BD1", "AAA-000M", 1e8))
+	err = keeper.CanDelistTradingPair(ctx, "BUSD-BD1", "AAA-000M")
+	require.Nil(t, err)
+
+	// delisting AAA-XYZ will not depends on BUSD-AAA or BUSD-XYZ
+	err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair(types.NativeTokenSymbol, "AAA-000M", 1e8))
+	err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair(types.NativeTokenSymbol, "XYZ-000M", 1e8))
+	err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair("AAA-000M", "XYZ-000M", 1e8))
+	err = keeper.CanDelistTradingPair(ctx, "AAA-000M", "XYZ-000M")
+	require.Nil(t, err)
+}
+
+func TestKeeper_CanDelistMiniTradingPair(t *testing.T) {
+	ctx, _, keeper := setup()
+	err := keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair("AAA-000M", types.NativeTokenSymbol, 1e8))
+	err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair("AAA-000M", "BUSD-BD1", 1e8))
+	err = keeper.CanDelistTradingPair(ctx, "AAA-000M", types.NativeTokenSymbol)
+	err = keeper.CanDelistTradingPair(ctx, "AAA-000M", "BUSD-BD1")
+	require.Nil(t, err)
+}
+
+func setChainVersion() {
+	upgrade.Mgr.AddUpgradeHeight(upgrade.BEP8, -1)
+	upgrade.Mgr.AddUpgradeHeight(upgrade.BEP70, -1)
+}
+
+func resetChainVersion() {
+	upgrade.Mgr.Config.HeightMap = nil
+}
diff --git a/plugins/dex/order/mini_keeper.go b/plugins/dex/order/mini_keeper.go
new file mode 100644
index 000000000..60f9c3098
--- /dev/null
+++ b/plugins/dex/order/mini_keeper.go
@@ -0,0 +1,98 @@
+package order
+
+import (
+	sdk "github.com/cosmos/cosmos-sdk/types"
+
+	bnclog "github.com/binance-chain/node/common/log"
+	"github.com/binance-chain/node/common/upgrade"
+	dexUtils "github.com/binance-chain/node/plugins/dex/utils"
+)
+
+const (
+	defaultMiniBlockMatchInterval int = 16
+	defaultActiveMiniSymbolCount  int = 8
+)
+
+//order keeper for mini-token
+type MiniOrderKeeper struct {
+	BaseOrderKeeper
+	symbolSelector MiniSymbolSelector
+}
+
+var _ DexOrderKeeper = &MiniOrderKeeper{}
+
+// NewBEP2OrderKeeper - Returns the MiniToken orderKeeper
+func NewMiniOrderKeeper() DexOrderKeeper {
+	return &MiniOrderKeeper{
+		BaseOrderKeeper: NewBaseOrderKeeper("dexMiniKeeper"),
+		symbolSelector: MiniSymbolSelector{
+			make(map[string]uint32, 256),
+			make([]string, 0, 256),
+		},
+	}
+}
+
+//override
+func (kp *MiniOrderKeeper) support(pair string) bool {
+	if !sdk.IsUpgrade(upgrade.BEP8) {
+		return false
+	}
+	return dexUtils.IsMiniTokenTradingPair(pair)
+}
+
+//override
+func (kp *MiniOrderKeeper) supportUpgradeVersion() bool {
+	return sdk.IsUpgrade(upgrade.BEP8)
+}
+
+func (kp *MiniOrderKeeper) supportPairType(pairType SymbolPairType) bool {
+	return PairType.MINI == pairType
+}
+
+// override
+func (kp *MiniOrderKeeper) initOrders(symbol string) {
+	kp.allOrders[symbol] = map[string]*OrderInfo{}
+	kp.symbolSelector.addSymbolHash(symbol)
+}
+
+func (kp *MiniOrderKeeper) clearAfterMatch() {
+	kp.logger.Debug("clearAfterMatchMini...")
+	for _, symbol := range kp.symbolSelector.roundSelectedSymbols {
+		delete(kp.roundOrders, symbol)
+		delete(kp.roundIOCOrders, symbol)
+	}
+	kp.symbolSelector.clearRoundMatchSymbol()
+}
+
+func (kp *MiniOrderKeeper) iterateRoundSelectedPairs(iter func(string)) {
+	for _, symbol := range kp.symbolSelector.roundSelectedSymbols {
+		iter(symbol)
+	}
+}
+
+func (kp *MiniOrderKeeper) getRoundPairsNum() int {
+	return len(kp.symbolSelector.roundSelectedSymbols)
+}
+
+func (kp *MiniOrderKeeper) getRoundOrdersNum() int {
+	n := 0
+	kp.iterateRoundSelectedPairs(func(symbol string) {
+		n += len(kp.roundOrders[symbol])
+	})
+	return n
+}
+
+func (kp *MiniOrderKeeper) reloadOrder(symbol string, orderInfo *OrderInfo, height int64) {
+	kp.allOrders[symbol][orderInfo.Id] = orderInfo
+	//TODO confirm no round orders for mini symbol
+	if kp.collectOrderInfoForPublish {
+		if _, exists := kp.orderInfosForPub[orderInfo.Id]; !exists {
+			bnclog.Debug("add order to order changes map, during load snapshot, from active orders", "orderId", orderInfo.Id)
+			kp.orderInfosForPub[orderInfo.Id] = orderInfo
+		}
+	}
+}
+
+func (kp *MiniOrderKeeper) selectSymbolsToMatch(height int64, matchAllSymbols bool) []string {
+	return kp.symbolSelector.SelectSymbolsToMatch(kp.roundOrders, height, matchAllSymbols)
+}
diff --git a/plugins/dex/order/openOrders_test.go b/plugins/dex/order/openOrders_test.go
index 4865ab172..0c9c52444 100644
--- a/plugins/dex/order/openOrders_test.go
+++ b/plugins/dex/order/openOrders_test.go
@@ -20,7 +20,7 @@ var (
 	zc, _ = sdk.AccAddressFromBech32(ZcAddr)
 )
 
-func initKeeper() *Keeper {
+func initKeeper() *DexKeeper {
 	cdc := MakeCodec()
 	keeper := MakeKeeper(cdc)
 	return keeper
diff --git a/plugins/dex/order/order_keeper.go b/plugins/dex/order/order_keeper.go
new file mode 100644
index 000000000..02146f75c
--- /dev/null
+++ b/plugins/dex/order/order_keeper.go
@@ -0,0 +1,289 @@
+package order
+
+import (
+	"sync"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	tmlog "github.com/tendermint/tendermint/libs/log"
+
+	bnclog "github.com/binance-chain/node/common/log"
+	"github.com/binance-chain/node/common/utils"
+	me "github.com/binance-chain/node/plugins/dex/matcheng"
+	"github.com/binance-chain/node/plugins/dex/store"
+	dexUtils "github.com/binance-chain/node/plugins/dex/utils"
+)
+
+const (
+	numPricesStored  = 2000
+	pricesStoreEvery = 1000
+	minimalNumPrices = 500
+)
+
+type DexOrderKeeper interface {
+	initOrders(symbol string)
+	addOrder(symbol string, info OrderInfo, isRecovery bool)
+	reloadOrder(symbol string, orderInfo *OrderInfo, height int64)
+	removeOrder(dexKeeper *DexKeeper, id string, symbol string) (ord me.OrderPart, err error)
+	orderExists(symbol, id string) (OrderInfo, bool)
+	getOpenOrders(pair string, addr sdk.AccAddress) []store.OpenOrder
+	getAllOrders() map[string]map[string]*OrderInfo
+	deleteOrdersForPair(pair string)
+
+	iterateRoundSelectedPairs(func(string))
+	iterateAllOrders(func(symbol string, id string))
+
+	getRoundOrdersNum() int
+	getAllOrdersForPair(pair string) map[string]*OrderInfo
+	getRoundOrdersForPair(pair string) []string
+	getRoundIOCOrdersForPair(pair string) []string
+	clearAfterMatch()
+	selectSymbolsToMatch(height int64, matchAllSymbols bool) []string
+
+	// publish
+	enablePublish()
+	appendOrderChangeSync(change OrderChange)
+	getOrderChanges() OrderChanges
+	clearOrderChanges()
+	getOrderInfosForPub() OrderInfoForPublish
+	removeOrderInfosForPub(orderId string)
+
+	support(pair string) bool
+	supportUpgradeVersion() bool
+	supportPairType(pairType SymbolPairType) bool
+}
+
+// in the future, this may be distributed via Sharding
+type BaseOrderKeeper struct {
+	allOrders      map[string]map[string]*OrderInfo // symbol -> order ID -> order
+	roundOrders    map[string][]string              // limit to the total tx number in a block
+	roundIOCOrders map[string][]string
+
+	collectOrderInfoForPublish bool
+	orderChangesMtx            *sync.Mutex         // guard orderChanges and orderInfosForPub during PreDevlierTx (which is async)
+	orderChanges               OrderChanges        // order changed in this block, will be cleaned before matching for new block
+	orderInfosForPub           OrderInfoForPublish // for publication usage
+
+	logger tmlog.Logger
+}
+
+func NewBaseOrderKeeper(moduleName string) BaseOrderKeeper {
+	logger := bnclog.With("module", moduleName)
+	return BaseOrderKeeper{
+		// need to init the nested map when a new symbol added.
+		allOrders:      make(map[string]map[string]*OrderInfo, 256),
+		roundOrders:    make(map[string][]string, 256),
+		roundIOCOrders: make(map[string][]string, 256),
+
+		collectOrderInfoForPublish: false, // default to false, need a explicit set if needed
+		orderChangesMtx:            &sync.Mutex{},
+		orderChanges:               make(OrderChanges, 0),
+		orderInfosForPub:           make(OrderInfoForPublish),
+		logger:                     logger,
+	}
+}
+
+func (kp *BaseOrderKeeper) addOrder(symbol string, info OrderInfo, isRecovery bool) {
+	if kp.collectOrderInfoForPublish {
+		change := OrderChange{info.Id, Ack, "", nil}
+		// deliberately not add this message to orderChanges
+		if !isRecovery {
+			kp.orderChanges = append(kp.orderChanges, change)
+		}
+		kp.logger.Debug("add order to order changes map", "orderId", info.Id, "isRecovery", isRecovery)
+		kp.orderInfosForPub[info.Id] = &info
+	}
+
+	kp.allOrders[symbol][info.Id] = &info
+	kp.addRoundOrders(symbol, info)
+}
+
+func (kp *BaseOrderKeeper) addRoundOrders(symbol string, info OrderInfo) {
+	if ids, ok := kp.roundOrders[symbol]; ok {
+		kp.roundOrders[symbol] = append(ids, info.Id)
+	} else {
+		newIds := make([]string, 0, 16)
+		kp.roundOrders[symbol] = append(newIds, info.Id)
+	}
+	if info.TimeInForce == TimeInForce.IOC {
+		kp.roundIOCOrders[symbol] = append(kp.roundIOCOrders[symbol], info.Id)
+	}
+}
+
+func (kp *BaseOrderKeeper) orderExists(symbol, id string) (OrderInfo, bool) {
+	if orders, ok := kp.allOrders[symbol]; ok {
+		if msg, ok := orders[id]; ok {
+			return *msg, ok
+		}
+	}
+	return OrderInfo{}, false
+}
+
+func (kp *BaseOrderKeeper) removeOrder(dexKeeper *DexKeeper, id string, symbol string) (ord me.OrderPart, err error) {
+	ordMsg, ok := kp.orderExists(symbol, id)
+	if !ok {
+		return me.OrderPart{}, orderNotFound(symbol, id)
+	}
+	eng, ok := dexKeeper.engines[symbol]
+	if !ok {
+		return me.OrderPart{}, orderNotFound(symbol, id)
+	}
+	delete(kp.allOrders[symbol], id)
+	return eng.Book.RemoveOrder(id, ordMsg.Side, ordMsg.Price)
+}
+
+func (kp *BaseOrderKeeper) deleteOrdersForPair(pair string) {
+	delete(kp.allOrders, pair)
+}
+
+func (kp *BaseOrderKeeper) getOpenOrders(pair string, addr sdk.AccAddress) []store.OpenOrder {
+	openOrders := make([]store.OpenOrder, 0)
+
+	for _, order := range kp.allOrders[pair] {
+		if string(order.Sender.Bytes()) == string(addr.Bytes()) {
+			openOrders = append(
+				openOrders,
+				store.OpenOrder{
+					order.Id,
+					pair,
+					utils.Fixed8(order.Price),
+					utils.Fixed8(order.Quantity),
+					utils.Fixed8(order.CumQty),
+					order.CreatedHeight,
+					order.CreatedTimestamp,
+					order.LastUpdatedHeight,
+					order.LastUpdatedTimestamp,
+				})
+		}
+	}
+
+	return openOrders
+}
+
+func (kp *BaseOrderKeeper) getAllOrders() map[string]map[string]*OrderInfo {
+	return kp.allOrders
+}
+
+func (kp *BaseOrderKeeper) clearOrderChanges() {
+	kp.orderChanges = kp.orderChanges[:0]
+}
+
+func (kp *BaseOrderKeeper) enablePublish() {
+	kp.collectOrderInfoForPublish = true
+}
+
+func (kp *BaseOrderKeeper) appendOrderChangeSync(change OrderChange) {
+	kp.orderChangesMtx.Lock()
+	kp.orderChanges = append(kp.orderChanges, change)
+	kp.orderChangesMtx.Unlock()
+}
+
+func (kp *BaseOrderKeeper) getOrderChanges() OrderChanges {
+	return kp.orderChanges
+}
+
+func (kp *BaseOrderKeeper) getOrderInfosForPub() OrderInfoForPublish {
+	return kp.orderInfosForPub
+}
+
+func (kp *BaseOrderKeeper) removeOrderInfosForPub(orderId string) {
+	delete(kp.orderInfosForPub, orderId)
+}
+
+func (kp *BaseOrderKeeper) getRoundOrdersForPair(pair string) []string {
+	return kp.roundOrders[pair]
+}
+
+func (kp *BaseOrderKeeper) getRoundIOCOrdersForPair(pair string) []string {
+	return kp.roundIOCOrders[pair]
+}
+
+func (kp *BaseOrderKeeper) getAllOrdersForPair(pair string) map[string]*OrderInfo {
+	return kp.allOrders[pair]
+}
+
+func (kp *BaseOrderKeeper) iterateAllOrders(iter func(string, string)) {
+	for symbol, orders := range kp.allOrders {
+		for orderId := range orders {
+			iter(symbol, orderId)
+		}
+	}
+}
+
+//------  BEP2OrderKeeper methods -----
+var _ DexOrderKeeper = &BEP2OrderKeeper{}
+
+type BEP2OrderKeeper struct {
+	BaseOrderKeeper
+	symbolSelector BEP2SymbolSelector
+}
+
+// NewBEP2OrderKeeper - Returns the BEP2OrderKeeper
+func NewBEP2OrderKeeper() DexOrderKeeper {
+	return &BEP2OrderKeeper{
+		BaseOrderKeeper: NewBaseOrderKeeper("BEP2OrderKeeper"),
+		symbolSelector:  BEP2SymbolSelector{},
+	}
+}
+
+func (kp *BEP2OrderKeeper) support(pair string) bool {
+	if !sdk.IsUpgrade(sdk.BEP8) {
+		return true
+	}
+	return !dexUtils.IsMiniTokenTradingPair(pair)
+}
+
+func (kp *BEP2OrderKeeper) supportUpgradeVersion() bool {
+	return true
+}
+
+func (kp *BEP2OrderKeeper) supportPairType(pairType SymbolPairType) bool {
+	return PairType.BEP2 == pairType
+}
+
+func (kp *BEP2OrderKeeper) initOrders(symbol string) {
+	kp.allOrders[symbol] = map[string]*OrderInfo{}
+}
+
+func (kp *BEP2OrderKeeper) clearAfterMatch() {
+	kp.logger.Debug("clearAfterMatchBEP2...")
+	kp.roundOrders = make(map[string][]string, 256)
+	kp.roundIOCOrders = make(map[string][]string, 256)
+}
+
+func (kp *BEP2OrderKeeper) iterateRoundSelectedPairs(iter func(string)) {
+	for symbol := range kp.roundOrders {
+		iter(symbol)
+	}
+}
+
+func (kp *BEP2OrderKeeper) reloadOrder(symbol string, orderInfo *OrderInfo, height int64) {
+	kp.allOrders[symbol][orderInfo.Id] = orderInfo
+	if orderInfo.CreatedHeight == height {
+		kp.roundOrders[symbol] = append(kp.roundOrders[symbol], orderInfo.Id)
+		if orderInfo.TimeInForce == TimeInForce.IOC {
+			kp.roundIOCOrders[symbol] = append(kp.roundIOCOrders[symbol], orderInfo.Id)
+		}
+	}
+	if kp.collectOrderInfoForPublish {
+		if _, exists := kp.orderInfosForPub[orderInfo.Id]; !exists {
+			bnclog.Debug("add order to order changes map, during load snapshot, from active orders", "orderId", orderInfo.Id)
+			kp.orderInfosForPub[orderInfo.Id] = orderInfo
+		}
+	}
+}
+
+func (kp *BEP2OrderKeeper) getRoundPairsNum() int {
+	return len(kp.roundOrders)
+}
+
+func (kp *BEP2OrderKeeper) getRoundOrdersNum() int {
+	n := 0
+	for _, orders := range kp.roundOrders {
+		n += len(orders)
+	}
+	return n
+}
+
+func (kp *BEP2OrderKeeper) selectSymbolsToMatch(height int64, matchAllSymbols bool) []string {
+	return kp.symbolSelector.SelectSymbolsToMatch(kp.roundOrders, height, matchAllSymbols)
+}
diff --git a/plugins/dex/order/quickselect.go b/plugins/dex/order/quickselect.go
new file mode 100644
index 000000000..4ac89dca6
--- /dev/null
+++ b/plugins/dex/order/quickselect.go
@@ -0,0 +1,57 @@
+package order
+
+import "github.com/tendermint/tendermint/libs/common"
+
+//Find and return top K symbols with largest number of order.
+// The returned top K slice is not sorted. The input orderNums may be re-ordered in place.
+// If more than one symbols have same order numbers, these symbol will be selected by ascending alphabetical sequence.
+func findTopKLargest(orderNums []*SymbolWithOrderNumber, k int) []*SymbolWithOrderNumber {
+	if k >= len(orderNums) {
+		return orderNums
+	}
+	return quickselect(orderNums, 0, len(orderNums)-1, k)
+}
+
+func partition(orderNums []*SymbolWithOrderNumber, start, end int) int {
+	// move pivot to end
+	if end == start {
+		return start
+	}
+
+	pivot := common.RandIntn(end-start) + start
+	orderNums[end], orderNums[pivot] = orderNums[pivot], orderNums[end]
+	pivotValue := orderNums[end]
+	i := start
+	for j := start; j < end; j++ {
+		if compare(orderNums[j], pivotValue) {
+			orderNums[i], orderNums[j] = orderNums[j], orderNums[i]
+			i++
+		}
+	}
+	// move pivot to its sorted position
+	orderNums[i], orderNums[end] = orderNums[end], orderNums[i]
+	// return pivot index
+	return i
+}
+
+func compare(orderNumA *SymbolWithOrderNumber, orderNumB *SymbolWithOrderNumber) bool {
+	if orderNumA.numberOfOrders > orderNumB.numberOfOrders {
+		return true
+	} else if orderNumA.numberOfOrders == orderNumB.numberOfOrders {
+		return orderNumA.symbol < orderNumB.symbol
+	}
+	return false
+}
+
+func quickselect(orderNums []*SymbolWithOrderNumber, start, end, n int) []*SymbolWithOrderNumber {
+	// use last element as pivot
+	pivotIndex := partition(orderNums, start, end)
+
+	if n-1 == pivotIndex {
+		return orderNums[:pivotIndex+1]
+	} else if n-1 > pivotIndex {
+		return quickselect(orderNums, pivotIndex+1, end, n)
+	} else {
+		return quickselect(orderNums, start, pivotIndex-1, n)
+	}
+}
diff --git a/plugins/dex/order/quickselect_test.go b/plugins/dex/order/quickselect_test.go
new file mode 100644
index 000000000..c2f136dd3
--- /dev/null
+++ b/plugins/dex/order/quickselect_test.go
@@ -0,0 +1,119 @@
+package order
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"github.com/tendermint/tendermint/libs/common"
+)
+
+func Test_findKthLargest(t *testing.T) {
+	//A0 := &SymbolWithOrderNumber{"ABC-9UEM_BNB", 0}
+	A1 := &SymbolWithOrderNumber{"ABC-9UEM_BNB", 1}
+	A2 := &SymbolWithOrderNumber{"ABC-9UEM_BNB", 2}
+	A3 := &SymbolWithOrderNumber{"ABC-9UEM_BNB", 3}
+	A4 := &SymbolWithOrderNumber{"ABC-9UEM_BNB", 4}
+	A5 := &SymbolWithOrderNumber{"ABC-9UEM_BNB", 5}
+	A6 := &SymbolWithOrderNumber{"ABC-9UEM_BNB", 6}
+	//B2 := &SymbolWithOrderNumber{"b", 2}
+	B3 := &SymbolWithOrderNumber{"BAC-678M_BNB", 3}
+	B5 := &SymbolWithOrderNumber{"BAC-678M_BNB", 5}
+	//C3 := &SymbolWithOrderNumber{"c", 3}
+	C4 := &SymbolWithOrderNumber{"CUY-G42M_BNB", 4}
+	//D4 := &SymbolWithOrderNumber{"d", 4}
+	D3 := &SymbolWithOrderNumber{"DUY-765_BNB", 3}
+	//E5 := &SymbolWithOrderNumber{"e", 5}
+	E2 := &SymbolWithOrderNumber{"ETF-876_BNB", 2}
+	//F6 := &SymbolWithOrderNumber{"f", 6}
+	F1 := &SymbolWithOrderNumber{"FXM-987M_BNB", 1}
+
+	expected := []*SymbolWithOrderNumber{A3, A4, A5}
+	result := findTopKLargest([]*SymbolWithOrderNumber{A1, A2, A3, A4, A5}, 3)
+	assertResult(t, expected, result)
+
+	expected = []*SymbolWithOrderNumber{A3, A4, A5}
+	result = findTopKLargest([]*SymbolWithOrderNumber{A5, A3, A3, A1, A4}, 3)
+	assertResult(t, expected, result)
+
+	expected = []*SymbolWithOrderNumber{A3, A4, A5}
+	result = findTopKLargest([]*SymbolWithOrderNumber{A5, B3, A3, A1, A4}, 3)
+	assertResult(t, expected, result)
+
+	expected = []*SymbolWithOrderNumber{A6, A5}
+	result = findTopKLargest([]*SymbolWithOrderNumber{A3, A2, A1, A5, A6, A4}, 2)
+	assertResult(t, expected, result)
+
+	expected = []*SymbolWithOrderNumber{A6, C4, B5}
+	result = findTopKLargest([]*SymbolWithOrderNumber{D3, E2, F1, B5, A6, C4}, 3)
+	assertResult(t, expected, result)
+
+	expected = []*SymbolWithOrderNumber{D3, E2, F1, B5, A6, C4}
+	result = findTopKLargest([]*SymbolWithOrderNumber{D3, E2, F1, B5, A6, C4}, 6)
+	assertResult(t, expected, result)
+
+	expected = []*SymbolWithOrderNumber{D3, E2, F1, B5, A6, C4}
+	result = findTopKLargest([]*SymbolWithOrderNumber{D3, E2, F1, B5, A6, C4}, 7)
+	assertResult(t, expected, result)
+}
+
+func Test_findKthLargest_SameNumber(t *testing.T) {
+	A0 := &SymbolWithOrderNumber{"ABC-9UEM_BNB", 0}
+	A1 := &SymbolWithOrderNumber{"ABC-9UEM_BNB", 1}
+	A2 := &SymbolWithOrderNumber{"ABC-9UEM_BNB", 2}
+	B2 := &SymbolWithOrderNumber{"BAC-678M_BNB", 2}
+	C1 := &SymbolWithOrderNumber{"CUY-G42M_BNB", 1}
+	C2 := &SymbolWithOrderNumber{"CUY-G42M_BNB", 2}
+	E2 := &SymbolWithOrderNumber{"ETF-876_BNB", 2}
+	F2 := &SymbolWithOrderNumber{"FXM-987M_BNB", 2}
+
+	assert := assert.New(t)
+
+	expected := []*SymbolWithOrderNumber{A2, B2, E2}
+	result := findTopKLargest([]*SymbolWithOrderNumber{F2, E2, A2, B2, C1}, 3)
+	assertResult(t, expected, result)
+
+	expected = []*SymbolWithOrderNumber{B2, A2, C2}
+	result = findTopKLargest([]*SymbolWithOrderNumber{A0, A1, A2, B2, C1, C2}, 3)
+	assertResult(t, expected, result)
+
+	expected = []*SymbolWithOrderNumber{A2}
+	result = findTopKLargest([]*SymbolWithOrderNumber{A0, A1, A2, B2, C1, C2}, 1)
+	assertResult(t, expected, result)
+
+	expected = []*SymbolWithOrderNumber{A1, A2, B2, C2}
+	result = findTopKLargest([]*SymbolWithOrderNumber{A0, A1, A2, B2, C1, C2}, 4)
+	assert.Equal(4, len(result))
+	assertResult(t, expected, result)
+
+	expected = []*SymbolWithOrderNumber{A1, A2, B2, C1, C2}
+	result = findTopKLargest([]*SymbolWithOrderNumber{A0, A1, A2, B2, C1, C2}, 5)
+	assert.Equal(5, len(result))
+	assertResult(t, expected, result)
+}
+
+func Benchmark_findTopKLargest(b *testing.B) {
+	const size = 10000
+	origin := make([]*SymbolWithOrderNumber, size)
+	for i := 0; i < b.N; i++ {
+		b.StopTimer()
+		for i := 0; i < size; i++ {
+			origin[i] = &SymbolWithOrderNumber{symbol: string(common.RandBytes(10)), numberOfOrders: int(common.RandIntn(size / 10))}
+		}
+		b.StartTimer()
+		findTopKLargest(origin, size/2)
+	}
+}
+
+func assertResult(t *testing.T, expected []*SymbolWithOrderNumber, actual []*SymbolWithOrderNumber) {
+	var s string
+	for _, x := range actual {
+		s += fmt.Sprintf("%v,", *x)
+	}
+	t.Logf(s)
+	require.Equal(t, len(expected), len(actual))
+	for _, ele := range expected {
+		require.Contains(t, actual, ele, "Expected contains %v, but doesn't exist", ele)
+	}
+}
diff --git a/plugins/dex/order/symbol_selector.go b/plugins/dex/order/symbol_selector.go
new file mode 100644
index 000000000..91ae115de
--- /dev/null
+++ b/plugins/dex/order/symbol_selector.go
@@ -0,0 +1,90 @@
+package order
+
+import (
+	"hash/crc32"
+)
+
+type SymbolSelector interface {
+	SelectSymbolsToMatch(roundOrders map[string][]string, height int64, matchAllSymbols bool) []string
+}
+
+var _ SymbolSelector = &BEP2SymbolSelector{}
+
+type BEP2SymbolSelector struct{}
+
+func (bss *BEP2SymbolSelector) SelectSymbolsToMatch(roundOrders map[string][]string, height int64, matchAllSymbols bool) []string {
+	size := len(roundOrders)
+	if size == 0 {
+		return make([]string, 0)
+	}
+	symbolsToMatch := make([]string, 0, len(roundOrders))
+	for symbol := range roundOrders {
+		symbolsToMatch = append(symbolsToMatch, symbol)
+	}
+	return symbolsToMatch
+}
+
+type MiniSymbolSelector struct {
+	symbolsHash          map[string]uint32 //mini token pairs -> hash value for Round-Robin
+	roundSelectedSymbols []string          //mini token pairs to match in this round
+}
+
+var _ SymbolSelector = &MiniSymbolSelector{}
+
+func (mss *MiniSymbolSelector) addSymbolHash(symbol string) {
+	mss.symbolsHash[symbol] = crc32.ChecksumIEEE([]byte(symbol))
+}
+
+func (mss *MiniSymbolSelector) clearRoundMatchSymbol() {
+	mss.roundSelectedSymbols = make([]string, 0)
+}
+
+func (mss *MiniSymbolSelector) SelectSymbolsToMatch(roundOrders map[string][]string, height int64, matchAllSymbols bool) []string {
+	size := len(roundOrders)
+	if size == 0 {
+		return make([]string, 0)
+	}
+	symbolsToMatch := make([]string, 0, len(roundOrders))
+	if matchAllSymbols {
+		for symbol := range roundOrders {
+			symbolsToMatch = append(symbolsToMatch, symbol)
+		}
+	} else {
+		mss.selectMiniSymbolsToMatch(roundOrders, height, func(miniSymbols map[string]struct{}) {
+			for symbol := range miniSymbols {
+				symbolsToMatch = append(symbolsToMatch, symbol)
+			}
+		})
+	}
+	mss.roundSelectedSymbols = symbolsToMatch
+	return symbolsToMatch
+}
+
+func (mss *MiniSymbolSelector) selectMiniSymbolsToMatch(roundOrders map[string][]string, height int64, postSelect func(map[string]struct{})) {
+	symbolsToMatch := make(map[string]struct{}, 256)
+	mss.selectActiveMiniSymbols(symbolsToMatch, roundOrders, defaultActiveMiniSymbolCount)
+	mss.selectMiniSymbolsRoundRobin(symbolsToMatch, height, defaultMiniBlockMatchInterval)
+	postSelect(symbolsToMatch)
+}
+
+func (mss *MiniSymbolSelector) selectActiveMiniSymbols(symbolsToMatch map[string]struct{}, roundOrdersMini map[string][]string, k int) {
+	//use quick select to select top k symbols
+	symbolOrderNumsSlice := make([]*SymbolWithOrderNumber, 0, len(roundOrdersMini))
+	for symbol, orders := range roundOrdersMini {
+		symbolOrderNumsSlice = append(symbolOrderNumsSlice, &SymbolWithOrderNumber{symbol, len(orders)})
+	}
+	topKSymbolOrderNums := findTopKLargest(symbolOrderNumsSlice, k)
+
+	for _, selected := range topKSymbolOrderNums {
+		symbolsToMatch[selected.symbol] = struct{}{}
+	}
+}
+
+func (mss *MiniSymbolSelector) selectMiniSymbolsRoundRobin(symbolsToMatch map[string]struct{}, height int64, matchInterval int) {
+	m := height % int64(matchInterval)
+	for symbol, symbolHash := range mss.symbolsHash {
+		if int64(symbolHash%uint32(matchInterval)) == m {
+			symbolsToMatch[symbol] = struct{}{}
+		}
+	}
+}
diff --git a/plugins/dex/order/transfer.go b/plugins/dex/order/transfer.go
index 9b116c996..60692759d 100644
--- a/plugins/dex/order/transfer.go
+++ b/plugins/dex/order/transfer.go
@@ -167,6 +167,7 @@ func transferFromOrderRemoved(ord me.OrderPart, ordMsg OrderInfo, tranEventType
 		outAsset:   unlockAsset,
 		out:        unlock,
 		unlock:     unlock,
+		Symbol:     ordMsg.Symbol,
 	}
 }
 
diff --git a/plugins/dex/order/types.go b/plugins/dex/order/types.go
index a2317a633..d3cbefbff 100644
--- a/plugins/dex/order/types.go
+++ b/plugins/dex/order/types.go
@@ -121,6 +121,12 @@ type ExpireHolder struct {
 	OrderId string
 	Reason  ChangeType
 	Fee     string
+	Symbol  string
+}
+
+type SymbolWithOrderNumber struct {
+	symbol         string
+	numberOfOrders int
 }
 
 type FeeHolder map[string]*types.Fee
diff --git a/plugins/dex/plugin.go b/plugins/dex/plugin.go
index 33e8c1238..42a6d161a 100644
--- a/plugins/dex/plugin.go
+++ b/plugins/dex/plugin.go
@@ -6,37 +6,39 @@ import (
 	"time"
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
-	"github.com/cosmos/cosmos-sdk/x/auth"
 	"github.com/cosmos/cosmos-sdk/x/gov"
 
 	"github.com/binance-chain/node/app/pub"
 	bnclog "github.com/binance-chain/node/common/log"
 	app "github.com/binance-chain/node/common/types"
 	"github.com/binance-chain/node/plugins/dex/utils"
-	tkstore "github.com/binance-chain/node/plugins/tokens/store"
+	"github.com/binance-chain/node/plugins/tokens"
 )
 
-const AbciQueryPrefix = "dex"
+const DexAbciQueryPrefix = "dex"
+const DexMiniAbciQueryPrefix = "dex-mini"
 const DelayedDaysForDelist = 3
 
 // InitPlugin initializes the dex plugin.
 func InitPlugin(
-	appp app.ChainApp, keeper *DexKeeper, tokenMapper tkstore.Mapper, accMapper auth.AccountKeeper, govKeeper gov.Keeper,
+	appp app.ChainApp, dexKeeper *DexKeeper, tokenMapper tokens.Mapper, govKeeper gov.Keeper,
 ) {
-	cdc := appp.GetCodec()
 
 	// add msg handlers
-	for route, handler := range Routes(cdc, keeper, tokenMapper, accMapper, govKeeper) {
+	for route, handler := range Routes(dexKeeper, tokenMapper, govKeeper) {
 		appp.GetRouter().AddRoute(route, handler)
 	}
 
 	// add abci handlers
-	handler := createQueryHandler(keeper)
-	appp.RegisterQueryHandler(AbciQueryPrefix, handler)
+	dexHandler := createQueryHandler(dexKeeper, DexAbciQueryPrefix)
+	appp.RegisterQueryHandler(DexAbciQueryPrefix, dexHandler)
+	//dex mini handler
+	dexMiniHandler := createQueryHandler(dexKeeper, DexMiniAbciQueryPrefix)
+	appp.RegisterQueryHandler(DexMiniAbciQueryPrefix, dexMiniHandler)
 }
 
-func createQueryHandler(keeper *DexKeeper) app.AbciQueryHandler {
-	return createAbciQueryHandler(keeper)
+func createQueryHandler(keeper *DexKeeper, abciQueryPrefix string) app.AbciQueryHandler {
+	return createAbciQueryHandler(keeper, abciQueryPrefix)
 }
 
 // EndBreatheBlock processes the breathe block lifecycle event.
@@ -50,7 +52,7 @@ func EndBreatheBlock(ctx sdk.Context, dexKeeper *DexKeeper, govKeeper gov.Keeper
 	dexKeeper.UpdateTickSizeAndLotSize(ctx)
 
 	logger.Info("Expire stale orders")
-	if dexKeeper.CollectOrderInfoForPublish {
+	if dexKeeper.ShouldPublishOrder() {
 		pub.ExpireOrdersForPublish(dexKeeper, ctx, blockTime)
 	} else {
 		dexKeeper.ExpireOrders(ctx, blockTime, nil)
@@ -78,7 +80,7 @@ func delistTradingPairs(ctx sdk.Context, govKeeper gov.Keeper, dexKeeper *DexKee
 			continue
 		}
 
-		if dexKeeper.CollectOrderInfoForPublish {
+		if dexKeeper.ShouldPublishOrder() {
 			pub.DelistTradingPairForPublish(ctx, dexKeeper, symbol)
 		} else {
 			dexKeeper.DelistTradingPair(ctx, symbol, nil)
@@ -115,7 +117,6 @@ func getSymbolsToDelist(ctx sdk.Context, govKeeper gov.Keeper, blockTime time.Ti
 			if timeToDelist.Before(blockTime) {
 				symbol := utils.Assets2TradingPair(strings.ToUpper(delistParam.BaseAssetSymbol), strings.ToUpper(delistParam.QuoteAssetSymbol))
 				symbols = append(symbols, symbol)
-
 				// update proposal delisted status
 				delistParam.IsExecuted = true
 				bz, err := json.Marshal(delistParam)
diff --git a/plugins/dex/route.go b/plugins/dex/route.go
index 6153bb452..2b9eeb9df 100644
--- a/plugins/dex/route.go
+++ b/plugins/dex/route.go
@@ -2,20 +2,17 @@ package dex
 
 import (
 	sdk "github.com/cosmos/cosmos-sdk/types"
-	"github.com/cosmos/cosmos-sdk/x/auth"
 	"github.com/cosmos/cosmos-sdk/x/gov"
 
 	"github.com/binance-chain/node/plugins/dex/list"
 	"github.com/binance-chain/node/plugins/dex/order"
 	"github.com/binance-chain/node/plugins/tokens"
-	"github.com/binance-chain/node/wire"
 )
 
 // Routes exports dex message routes
-func Routes(cdc *wire.Codec, dexKeeper *DexKeeper, tokenMapper tokens.Mapper,
-	accKeeper auth.AccountKeeper, govKeeper gov.Keeper) map[string]sdk.Handler {
+func Routes(dexKeeper *DexKeeper, tokenMapper tokens.Mapper, govKeeper gov.Keeper) map[string]sdk.Handler {
 	routes := make(map[string]sdk.Handler)
-	orderHandler := order.NewHandler(cdc, dexKeeper, accKeeper)
+	orderHandler := order.NewHandler(dexKeeper)
 	routes[order.RouteNewOrder] = orderHandler
 	routes[order.RouteCancelOrder] = orderHandler
 	routes[list.Route] = list.NewHandler(dexKeeper, tokenMapper, govKeeper)
diff --git a/plugins/dex/store/codec.go b/plugins/dex/store/codec.go
index 301cf7aca..364cdf7c8 100644
--- a/plugins/dex/store/codec.go
+++ b/plugins/dex/store/codec.go
@@ -10,7 +10,9 @@ import (
 
 // queryOrderBook queries the store for the serialized order book for a given pair.
 func queryOrderBook(cdc *wire.Codec, ctx context.CLIContext, pair string, levels int) (*[]byte, error) {
-	bz, err := ctx.Query(fmt.Sprintf("dex/orderbook/%s/%d", pair, levels), nil)
+	var path string
+	path = fmt.Sprintf("dex/orderbook/%s/%d", pair, levels)
+	bz, err := ctx.Query(path, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -41,7 +43,9 @@ func GetOrderBook(cdc *wire.Codec, ctx context.CLIContext, pair string, levels i
 }
 
 func queryOpenOrders(cdc *wire.Codec, ctx context.CLIContext, pair string, addr string) (*[]byte, error) {
-	if bz, err := ctx.Query(fmt.Sprintf("dex/openorders/%s/%s", pair, addr), nil); err != nil {
+	var path string
+	path = fmt.Sprintf("dex/openorders/%s/%s", pair, addr)
+	if bz, err := ctx.Query(path, nil); err != nil {
 		return nil, err
 	} else {
 		return &bz, nil
diff --git a/plugins/dex/store/mapper.go b/plugins/dex/store/mapper.go
index 2d799cc1e..5919f0a4a 100644
--- a/plugins/dex/store/mapper.go
+++ b/plugins/dex/store/mapper.go
@@ -44,12 +44,16 @@ func NewTradingPairMapper(cdc *wire.Codec, key sdk.StoreKey) TradingPairMapper {
 
 func (m mapper) AddTradingPair(ctx sdk.Context, pair types.TradingPair) error {
 	baseAsset := pair.BaseAssetSymbol
-	if err := cmn.ValidateMapperTokenSymbol(baseAsset); err != nil {
-		return err
-	}
 	quoteAsset := pair.QuoteAssetSymbol
-	if err := cmn.ValidateMapperTokenSymbol(quoteAsset); err != nil {
-		return err
+	if !cmn.IsValidMiniTokenSymbol(baseAsset) {
+		if err := cmn.ValidateTokenSymbol(baseAsset); err != nil {
+			return err
+		}
+	}
+	if !cmn.IsValidMiniTokenSymbol(quoteAsset) {
+		if err := cmn.ValidateTokenSymbol(quoteAsset); err != nil {
+			return err
+		}
 	}
 
 	tradeSymbol := dexUtils.Assets2TradingPair(strings.ToUpper(baseAsset), strings.ToUpper(quoteAsset))
diff --git a/plugins/dex/store/types.go b/plugins/dex/store/types.go
index ff83d02c8..2824b90a9 100644
--- a/plugins/dex/store/types.go
+++ b/plugins/dex/store/types.go
@@ -6,8 +6,9 @@ import (
 
 // OrderBook represents an order book at the current point block height, which is included in its struct.
 type OrderBook struct {
-	Height int64
-	Levels []OrderBookLevel
+	Height       int64
+	Levels       []OrderBookLevel
+	PendingMatch bool
 }
 
 // OrderBookLevel represents a single order book level.
diff --git a/plugins/dex/store/utils.go b/plugins/dex/store/utils.go
index 221b258ca..3f39a594b 100644
--- a/plugins/dex/store/utils.go
+++ b/plugins/dex/store/utils.go
@@ -12,20 +12,16 @@ func ValidatePairSymbol(symbol string) error {
 	if len(symbol) == 0 {
 		return errors.New("symbol pair must not be empty")
 	}
-	// TokenSymbolMaxLen: BTC00000
-	// TokenSymbolTxHashSuffixLen: 000
-	// + 2: ".B"
-	// * 2: BTC00000.B, ETH00000.B
-	// + 3: 2x `-` and 1x `_`
-	if len(symbol) > ((types.TokenSymbolMaxLen+types.TokenSymbolTxHashSuffixLen+2)*2)+3 {
-		return errors.New("symbol pair is too long")
-	}
+
 	tokenSymbols := strings.SplitN(strings.ToUpper(symbol), "_", 2)
 	if len(tokenSymbols) != 2 {
 		return errors.New("invalid symbol: trading pair must contain an underscore ('_')")
 	}
 	for _, tokenSymbol := range tokenSymbols {
-		if err := types.ValidateMapperTokenSymbol(tokenSymbol); err != nil {
+		if types.IsValidMiniTokenSymbol(tokenSymbol) {
+			continue
+		}
+		if err := types.ValidateTokenSymbol(tokenSymbol); err != nil {
 			return err
 		}
 	}
diff --git a/plugins/dex/utils/pair.go b/plugins/dex/utils/pair.go
index ec106ce48..c28525748 100644
--- a/plugins/dex/utils/pair.go
+++ b/plugins/dex/utils/pair.go
@@ -6,6 +6,7 @@ import (
 	"math/big"
 	"strings"
 
+	"github.com/binance-chain/node/common/types"
 	"github.com/binance-chain/node/common/utils"
 )
 
@@ -83,3 +84,11 @@ func TradingPair2AssetsSafe(symbol string) (baseAsset, quoteAsset string) {
 func Assets2TradingPair(baseAsset, quoteAsset string) (symbol string) {
 	return fmt.Sprintf("%s_%s", baseAsset, quoteAsset)
 }
+
+func IsMiniTokenTradingPair(symbol string) bool {
+	baseAsset, quoteAsset, err := TradingPair2Assets(symbol)
+	if err != nil {
+		return false
+	}
+	return types.IsMiniTokenSymbol(baseAsset) || types.IsMiniTokenSymbol(quoteAsset)
+}
diff --git a/plugins/dex/wire.go b/plugins/dex/wire.go
index cf5c3f784..e0900b5e0 100644
--- a/plugins/dex/wire.go
+++ b/plugins/dex/wire.go
@@ -18,6 +18,8 @@ func RegisterWire(cdc *wire.Codec) {
 	cdc.RegisterConcrete(list.ListMsg{}, "dex/ListMsg", nil)
 	cdc.RegisterConcrete(types.TradingPair{}, "dex/TradingPair", nil)
 
+	cdc.RegisterConcrete(list.ListMiniMsg{}, "dex/ListMiniMsg", nil)
+
 	cdc.RegisterConcrete(order.FeeConfig{}, "dex/FeeConfig", nil)
 	cdc.RegisterConcrete(order.OrderBookSnapshot{}, "dex/OrderBookSnapshot", nil)
 	cdc.RegisterConcrete(order.ActiveOrders{}, "dex/ActiveOrders", nil)
diff --git a/plugins/param/genesis.go b/plugins/param/genesis.go
index c8cde3b0c..27f10ce16 100644
--- a/plugins/param/genesis.go
+++ b/plugins/param/genesis.go
@@ -52,6 +52,12 @@ const (
 	FeeRateNative      = 400
 	IOCExpireFee       = 25e3
 	IOCExpireFeeNative = 5e3
+
+	//MiniToken fee
+	TinyIssueFee   = 2e8
+	MiniIssueFee   = 4e8
+	MiniSetUriFee  = 37500
+	MiniListingFee = 10e8
 )
 
 var DefaultGenesisState = param.GenesisState{
diff --git a/plugins/param/plugin.go b/plugins/param/plugin.go
index bc6e369d3..101973ed3 100644
--- a/plugins/param/plugin.go
+++ b/plugins/param/plugin.go
@@ -19,6 +19,7 @@ import (
 	"github.com/binance-chain/node/plugins/tokens/burn"
 	"github.com/binance-chain/node/plugins/tokens/freeze"
 	"github.com/binance-chain/node/plugins/tokens/issue"
+	miniURI "github.com/binance-chain/node/plugins/tokens/seturi"
 	"github.com/binance-chain/node/plugins/tokens/swap"
 	"github.com/binance-chain/node/plugins/tokens/timelock"
 )
@@ -60,6 +61,15 @@ func RegisterUpgradeBeginBlocker(paramHub *ParamHub) {
 		}
 		paramHub.UpdateFeeParams(ctx, swapFeeParams)
 	})
+	upgrade.Mgr.RegisterBeginBlocker(upgrade.BEP8, func(ctx sdk.Context) {
+		miniTokenFeeParams := []param.FeeParam{
+			&param.FixedFeeParams{MsgType: issue.IssueTinyMsgType, Fee: TinyIssueFee, FeeFor: types.FeeForAll},
+			&param.FixedFeeParams{MsgType: issue.IssueMiniMsgType, Fee: MiniIssueFee, FeeFor: types.FeeForAll},
+			&param.FixedFeeParams{MsgType: miniURI.SetURIMsg{}.Type(), Fee: MiniSetUriFee, FeeFor: types.FeeForProposer},
+			&param.FixedFeeParams{MsgType: list.ListMiniMsg{}.Type(), Fee: MiniListingFee, FeeFor: types.FeeForAll},
+		}
+		paramHub.UpdateFeeParams(ctx, miniTokenFeeParams)
+	})
 }
 
 func EndBreatheBlock(ctx sdk.Context, paramHub *ParamHub) {
@@ -92,5 +102,9 @@ func init() {
 		swap.DepositHTLT:                  fees.FixedFeeCalculatorGen,
 		swap.ClaimHTLT:                    fees.FixedFeeCalculatorGen,
 		swap.RefundHTLT:                   fees.FixedFeeCalculatorGen,
+		issue.IssueTinyMsgType:            fees.FixedFeeCalculatorGen,
+		issue.IssueMiniMsgType:            fees.FixedFeeCalculatorGen,
+		miniURI.SetURIRoute:               fees.FixedFeeCalculatorGen,
+		list.ListMiniMsg{}.Type():         fees.FixedFeeCalculatorGen,
 	}
 }
diff --git a/plugins/param/types/types.go b/plugins/param/types/types.go
index ce39679f3..4acab6873 100644
--- a/plugins/param/types/types.go
+++ b/plugins/param/types/types.go
@@ -40,6 +40,11 @@ var (
 		"depositHTLT": {},
 		"claimHTLT":   {},
 		"refundHTLT":  {},
+
+		"tinyIssueMsg":     {},
+		"miniIssueMsg":     {},
+		"miniTokensSetURI": {},
+		"dexListMini":      {},
 	}
 
 	ValidTransferFeeMsgTypes = map[string]struct{}{
diff --git a/plugins/tokens/abci.go b/plugins/tokens/abci.go
index 3b8b7a470..e850195ad 100644
--- a/plugins/tokens/abci.go
+++ b/plugins/tokens/abci.go
@@ -9,13 +9,23 @@ import (
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
 
-	app "github.com/binance-chain/node/common/types"
+	"github.com/binance-chain/node/common/types"
 )
 
-func createAbciQueryHandler(mapper Mapper) app.AbciQueryHandler {
-	return func(app app.ChainApp, req abci.RequestQuery, path []string) (res *abci.ResponseQuery) {
+func createAbciQueryHandler(mapper Mapper, prefix string) types.AbciQueryHandler {
+	queryPrefix := prefix
+	var isMini bool
+	switch queryPrefix {
+	case abciQueryPrefix:
+		isMini = false
+	case miniAbciQueryPrefix:
+		isMini = true
+	default:
+		isMini = false
+	}
+	return func(app types.ChainApp, req abci.RequestQuery, path []string) (res *abci.ResponseQuery) {
 		// expects at least two query path segments.
-		if path[0] != abciQueryPrefix || len(path) < 2 {
+		if path[0] != queryPrefix || len(path) < 2 {
 			return nil
 		}
 		switch path[1] {
@@ -25,7 +35,7 @@ func createAbciQueryHandler(mapper Mapper) app.AbciQueryHandler {
 					Code: uint32(sdk.CodeUnknownRequest),
 					Log: fmt.Sprintf(
 						"%s %s query requires a symbol path arg",
-						abciQueryPrefix, path[1]),
+						queryPrefix, path[1]),
 				}
 			}
 			ctx := app.GetContextForCheckState()
@@ -36,31 +46,14 @@ func createAbciQueryHandler(mapper Mapper) app.AbciQueryHandler {
 					Log:  "empty symbol not permitted",
 				}
 			}
-			token, err := mapper.GetToken(ctx, symbol)
-			if err != nil {
-				return &abci.ResponseQuery{
-					Code: uint32(sdk.CodeInternal),
-					Log:  err.Error(),
-				}
-			}
-			bz, err := app.GetCodec().MarshalBinaryLengthPrefixed(token)
-			if err != nil {
-				return &abci.ResponseQuery{
-					Code: uint32(sdk.CodeInternal),
-					Log:  err.Error(),
-				}
-			}
-			return &abci.ResponseQuery{
-				Code:  uint32(sdk.ABCICodeOK),
-				Value: bz,
-			}
+			return queryAndMarshallToken(app, mapper, ctx, symbol)
 		case "list": // args: ["tokens", "list", <offset>, <limit>, <showZeroSupplyTokens>]
 			if len(path) < 4 {
 				return &abci.ResponseQuery{
 					Code: uint32(sdk.CodeUnknownRequest),
 					Log: fmt.Sprintf(
 						"%s %s query requires offset and limit path segments",
-						abciQueryPrefix, path[1]),
+						queryPrefix, path[1]),
 				}
 			}
 			showZeroSupplyTokens := false
@@ -68,7 +61,9 @@ func createAbciQueryHandler(mapper Mapper) app.AbciQueryHandler {
 				showZeroSupplyTokens = true
 			}
 			ctx := app.GetContextForCheckState()
-			tokens := mapper.GetTokenList(ctx, showZeroSupplyTokens)
+
+			tokens := mapper.GetTokenList(ctx, showZeroSupplyTokens, isMini)
+
 			offset, err := strconv.Atoi(path[2])
 			if err != nil || offset < 0 || offset >= len(tokens) {
 				return &abci.ResponseQuery{
@@ -93,9 +88,24 @@ func createAbciQueryHandler(mapper Mapper) app.AbciQueryHandler {
 					Log:  "malformed range",
 				}
 			}
-			bz, err := app.GetCodec().MarshalBinaryLengthPrefixed(
-				tokens[offset:end],
-			)
+			var bz []byte
+			if isMini {
+				miniTokens := make([]*types.MiniToken, end-offset)
+				for i, token := range tokens[offset:end] {
+					miniTokens[i] = token.(*types.MiniToken)
+				}
+				bz, err = app.GetCodec().MarshalBinaryLengthPrefixed(
+					miniTokens,
+				)
+			} else {
+				bep2Tokens := make([]*types.Token, end-offset)
+				for i, token := range tokens[offset:end] {
+					bep2Tokens[i] = token.(*types.Token)
+				}
+				bz, err = app.GetCodec().MarshalBinaryLengthPrefixed(
+					bep2Tokens,
+				)
+			}
 			if err != nil {
 				return &abci.ResponseQuery{
 					Code: uint32(sdk.CodeInternal),
@@ -111,8 +121,33 @@ func createAbciQueryHandler(mapper Mapper) app.AbciQueryHandler {
 				Code: uint32(sdk.ABCICodeOK),
 				Info: fmt.Sprintf(
 					"Unknown `%s` query path: %v",
-					abciQueryPrefix, path),
+					queryPrefix, path),
 			}
 		}
 	}
 }
+
+func queryAndMarshallToken(app types.ChainApp, mapper Mapper, ctx sdk.Context, symbol string) *abci.ResponseQuery {
+	var bz []byte
+	var err error
+	var token types.IToken
+
+	token, err = mapper.GetToken(ctx, symbol)
+	if err != nil {
+		return &abci.ResponseQuery{
+			Code: uint32(sdk.CodeInternal),
+			Log:  err.Error(),
+		}
+	}
+	bz, err = app.GetCodec().MarshalBinaryLengthPrefixed(token)
+	if err != nil {
+		return &abci.ResponseQuery{
+			Code: uint32(sdk.CodeInternal),
+			Log:  err.Error(),
+		}
+	}
+	return &abci.ResponseQuery{
+		Code:  uint32(sdk.ABCICodeOK),
+		Value: bz,
+	}
+}
diff --git a/plugins/tokens/abci_test.go b/plugins/tokens/abci_test.go
index 542ab4b22..e949c03cd 100644
--- a/plugins/tokens/abci_test.go
+++ b/plugins/tokens/abci_test.go
@@ -26,8 +26,8 @@ var (
 	addr         = sdk.AccAddress(pk.Address())
 	token1Ptr, _ = common.NewToken("XXX", "XXX-000", 10000000000, addr, false)
 	token2Ptr, _ = common.NewToken("XXY", "XXY-000", 10000000000, addr, false)
-	token1       = *token1Ptr
-	token2       = *token2Ptr
+	token1       = token1Ptr
+	token2       = token2Ptr
 )
 
 func Test_Tokens_ABCI_GetInfo_Success(t *testing.T) {
@@ -53,7 +53,7 @@ func Test_Tokens_ABCI_GetInfo_Success(t *testing.T) {
 	}
 
 	assert.True(t, sdk.ABCICodeType(res.Code).IsOK())
-	assert.Equal(t, token1, actual)
+	assert.Equal(t, *token1, actual)
 }
 
 func Test_Tokens_ABCI_GetInfo_Error_NotFound(t *testing.T) {
@@ -115,7 +115,7 @@ func Test_Tokens_ABCI_GetTokens_Success(t *testing.T) {
 
 	assert.True(t, sdk.ABCICodeType(res.Code).IsOK())
 	assert.Equal(t, []common.Token{
-		token1, token2,
+		*token1, *token2,
 	}, actual)
 }
 
@@ -147,7 +147,7 @@ func Test_Tokens_ABCI_GetTokens_Success_WithOffset(t *testing.T) {
 
 	assert.True(t, sdk.ABCICodeType(res.Code).IsOK())
 	assert.Equal(t, []common.Token{
-		token2,
+		*token2,
 	}, actual)
 }
 
@@ -179,7 +179,7 @@ func Test_Tokens_ABCI_GetTokens_Success_WithLimit(t *testing.T) {
 
 	assert.True(t, sdk.ABCICodeType(res.Code).IsOK())
 	assert.Equal(t, []common.Token{
-		token1,
+		*token1,
 	}, actual)
 }
 
diff --git a/plugins/tokens/burn/handler.go b/plugins/tokens/burn/handler.go
index 2bd715183..fe05a1905 100644
--- a/plugins/tokens/burn/handler.go
+++ b/plugins/tokens/burn/handler.go
@@ -1,6 +1,7 @@
 package burn
 
 import (
+	"fmt"
 	"reflect"
 	"strings"
 
@@ -8,6 +9,7 @@ import (
 	"github.com/cosmos/cosmos-sdk/x/bank"
 
 	"github.com/binance-chain/node/common/log"
+	common "github.com/binance-chain/node/common/types"
 	"github.com/binance-chain/node/plugins/tokens/store"
 )
 
@@ -16,7 +18,6 @@ func NewHandler(tokenMapper store.Mapper, keeper bank.Keeper) sdk.Handler {
 		if msg, ok := msg.(BurnMsg); ok {
 			return handleBurnToken(ctx, tokenMapper, keeper, msg)
 		}
-
 		errMsg := "Unrecognized msg type: " + reflect.TypeOf(msg).Name()
 		return sdk.ErrUnknownRequest(errMsg).Result()
 	}
@@ -33,18 +34,27 @@ func handleBurnToken(ctx sdk.Context, tokenMapper store.Mapper, keeper bank.Keep
 	}
 
 	if !token.IsOwner(msg.From) {
-		logger.Info("burn token failed", "reason", "not token's owner", "from", msg.From, "owner", token.Owner)
+		logger.Info("burn token failed", "reason", "not token's owner", "from", msg.From, "owner", token.GetOwner())
 		return sdk.ErrUnauthorized("only the owner of the token can burn the token").Result()
 	}
 
-	coins := keeper.GetCoins(ctx, token.Owner)
-	if coins.AmountOf(symbol) < burnAmount ||
-		token.TotalSupply.ToInt64() < burnAmount {
+	coins := keeper.GetCoins(ctx, token.GetOwner())
+	balance := coins.AmountOf(symbol)
+	if balance < burnAmount ||
+		token.GetTotalSupply().ToInt64() < burnAmount {
 		logger.Info("burn token failed", "reason", "no enough tokens to burn")
 		return sdk.ErrInsufficientCoins("do not have enough token to burn").Result()
 	}
 
-	_, _, sdkError := keeper.SubtractCoins(ctx, token.Owner, sdk.Coins{{
+	if common.IsMiniTokenSymbol(symbol) {
+		if burnAmount < common.MiniTokenMinExecutionAmount && balance != burnAmount {
+			logger.Info("burn token failed", "reason", "burn amount doesn't reach the min amount")
+			return sdk.ErrInvalidCoins(fmt.Sprintf("burn amount is too small, the min amount is %d or total free balance",
+				common.MiniTokenMinExecutionAmount)).Result()
+		}
+	}
+
+	_, _, sdkError := keeper.SubtractCoins(ctx, token.GetOwner(), sdk.Coins{{
 		Denom:  symbol,
 		Amount: burnAmount,
 	}})
@@ -53,7 +63,7 @@ func handleBurnToken(ctx sdk.Context, tokenMapper store.Mapper, keeper bank.Keep
 		return sdkError.Result()
 	}
 
-	newTotalSupply := token.TotalSupply.ToInt64() - burnAmount
+	newTotalSupply := token.GetTotalSupply().ToInt64() - burnAmount
 	err = tokenMapper.UpdateTotalSupply(ctx, symbol, newTotalSupply)
 	if err != nil {
 		logger.Error("burn token failed", "reason", "update total supply failed: "+err.Error())
diff --git a/plugins/tokens/burn/handler_test.go b/plugins/tokens/burn/handler_test.go
new file mode 100644
index 000000000..f38447ea6
--- /dev/null
+++ b/plugins/tokens/burn/handler_test.go
@@ -0,0 +1,159 @@
+package burn
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/cosmos/cosmos-sdk/baseapp"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/cosmos/cosmos-sdk/x/auth"
+	"github.com/cosmos/cosmos-sdk/x/bank"
+
+	abci "github.com/tendermint/tendermint/abci/types"
+	"github.com/tendermint/tendermint/libs/log"
+
+	"github.com/binance-chain/node/common/testutils"
+	"github.com/binance-chain/node/common/types"
+	"github.com/binance-chain/node/common/upgrade"
+	"github.com/binance-chain/node/plugins/tokens/issue"
+	"github.com/binance-chain/node/plugins/tokens/store"
+	"github.com/binance-chain/node/wire"
+)
+
+func setup() (sdk.Context, sdk.Handler, sdk.Handler, auth.AccountKeeper, store.Mapper) {
+	ms, capKey1, capKey2 := testutils.SetupMultiStoreForUnitTest()
+	cdc := wire.NewCodec()
+	cdc.RegisterInterface((*types.IToken)(nil), nil)
+	cdc.RegisterConcrete(&types.Token{}, "bnbchain/Token", nil)
+	cdc.RegisterConcrete(&types.MiniToken{}, "bnbchain/MiniToken", nil)
+	tokenMapper := store.NewMapper(cdc, capKey1)
+	accountKeeper := auth.NewAccountKeeper(cdc, capKey2, types.ProtoAppAccount)
+	bankKeeper := bank.NewBaseKeeper(accountKeeper)
+	handler := NewHandler(tokenMapper, bankKeeper)
+	tokenHandler := issue.NewHandler(tokenMapper, bankKeeper)
+
+	accountStore := ms.GetKVStore(capKey2)
+	accountStoreCache := auth.NewAccountStoreCache(cdc, accountStore, 10)
+	ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid", Height: 1},
+		sdk.RunTxModeDeliver, log.NewNopLogger()).
+		WithAccountCache(auth.NewAccountCache(accountStoreCache))
+	return ctx, handler, tokenHandler, accountKeeper, tokenMapper
+}
+
+func setChainVersion() {
+	upgrade.Mgr.AddUpgradeHeight(upgrade.BEP8, -1)
+}
+
+func resetChainVersion() {
+	upgrade.Mgr.Config.HeightMap = nil
+}
+
+func TestHandleBurnMini(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	ctx, handler, miniIssueHandler, accountKeeper, tokenMapper := setup()
+	_, acc := testutils.NewAccount(ctx, accountKeeper, 100e8)
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "000")
+	msg := issue.NewIssueMiniMsg(acc.GetAddress(), "New BNB", "NNB", 10000e8, false, "http://www.xyz.com/nnb.json")
+	sdkResult := miniIssueHandler(ctx, msg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+
+	token, err := tokenMapper.GetToken(ctx, "NNB-000M")
+	require.NoError(t, err)
+	expectedToken := types.NewMiniToken("New BNB", "NNB", "NNB-000M", 2, 10000e8, acc.GetAddress(), false, "http://www.xyz.com/nnb.json")
+	require.Equal(t, expectedToken, token)
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "002")
+	burnMsg := NewMsg(acc.GetAddress(), "NNB-000M", 10001e8+1)
+	sdkResult = handler(ctx, burnMsg)
+	require.Equal(t, false, sdkResult.Code.IsOK())
+	require.Contains(t, sdkResult.Log, "do not have enough token to burn")
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "002")
+	burnMsg = NewMsg(acc.GetAddress(), "NNB-000M", 9999e8+1)
+	sdkResult = handler(ctx, burnMsg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+
+	token, err = tokenMapper.GetToken(ctx, "NNB-000M")
+	require.NoError(t, err)
+	expectedToken = types.NewMiniToken("New BNB", "NNB", "NNB-000M", 2, 1e8-1, acc.GetAddress(), false, "http://www.xyz.com/nnb.json")
+	require.Equal(t, expectedToken, token)
+
+	account := accountKeeper.GetAccount(ctx, msg.From).(types.NamedAccount)
+	amount := account.GetCoins().AmountOf("NNB-000M")
+	require.Equal(t, int64(1e8-1), amount)
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "002")
+	burnMsg = NewMsg(acc.GetAddress(), "NNB-000M", 1e8-2)
+	sdkResult = handler(ctx, burnMsg)
+	require.Equal(t, false, sdkResult.Code.IsOK())
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "002")
+	burnMsg = NewMsg(acc.GetAddress(), "NNB-000M", 1e8-1)
+	sdkResult = handler(ctx, burnMsg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+
+	token, err = tokenMapper.GetToken(ctx, "NNB-000M")
+	require.NoError(t, err)
+	expectedToken = types.NewMiniToken("New BNB", "NNB", "NNB-000M", 2, 0, acc.GetAddress(), false, "http://www.xyz.com/nnb.json")
+	require.Equal(t, expectedToken, token)
+
+	_, acc2 := testutils.NewAccount(ctx, accountKeeper, 100e8)
+	ctx = ctx.WithValue(baseapp.TxHashKey, "002")
+	burnMsg = NewMsg(acc2.GetAddress(), "NNB-000M", 1e8)
+	sdkResult = handler(ctx, burnMsg)
+	require.Equal(t, false, sdkResult.Code.IsOK())
+	require.Contains(t, sdkResult.Log, "only the owner of the token can burn the token")
+
+	account = accountKeeper.GetAccount(ctx, msg.From).(types.NamedAccount)
+	amount = account.GetCoins().AmountOf("NNB-000M")
+	require.Equal(t, int64(0), amount)
+}
+
+func TestHandleBurn(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	ctx, handler, issueHandler, accountKeeper, tokenMapper := setup()
+	_, acc := testutils.NewAccount(ctx, accountKeeper, 100e8)
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "000")
+	msg := issue.NewIssueMsg(acc.GetAddress(), "New BNB", "NNB", 10000e8, false)
+	sdkResult := issueHandler(ctx, msg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+
+	_, err := tokenMapper.GetToken(ctx, "NNB-000M")
+	require.NotNil(t, err)
+	require.Contains(t, err.Error(), "token(NNB-000M) not found")
+
+	token, err := tokenMapper.GetToken(ctx, "NNB-000")
+	require.Equal(t, true, sdkResult.Code.IsOK())
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "002")
+	burnMsg := NewMsg(acc.GetAddress(), "NNB-000", 10001e8)
+	sdkResult = handler(ctx, burnMsg)
+	require.Equal(t, false, sdkResult.Code.IsOK())
+	require.Contains(t, sdkResult.Log, "do not have enough token to burn")
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "002")
+	burnMsg = NewMsg(acc.GetAddress(), "NNB-000", 9999e8+1)
+	sdkResult = handler(ctx, burnMsg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+
+	token, err = tokenMapper.GetToken(ctx, "NNB-000")
+	require.NoError(t, err)
+	expectedToken, err := types.NewToken("New BNB", "NNB-000", 1e8-1, acc.GetAddress(), false)
+	require.Equal(t, expectedToken, token)
+
+	account := accountKeeper.GetAccount(ctx, msg.From).(types.NamedAccount)
+	amount := account.GetCoins().AmountOf("NNB-000")
+	require.Equal(t, int64(1e8-1), amount)
+
+	_, acc2 := testutils.NewAccount(ctx, accountKeeper, 100e8)
+	ctx = ctx.WithValue(baseapp.TxHashKey, "002")
+	burnMsg = NewMsg(acc2.GetAddress(), "NNB-000", 1e8)
+	sdkResult = handler(ctx, burnMsg)
+	require.Equal(t, false, sdkResult.Code.IsOK())
+	require.Contains(t, sdkResult.Log, "only the owner of the token can burn the token")
+}
diff --git a/plugins/tokens/burn/msg.go b/plugins/tokens/burn/msg.go
index 176d12851..884ca6266 100644
--- a/plugins/tokens/burn/msg.go
+++ b/plugins/tokens/burn/msg.go
@@ -7,6 +7,7 @@ import (
 	sdk "github.com/cosmos/cosmos-sdk/types"
 
 	"github.com/binance-chain/node/common/types"
+	"github.com/binance-chain/node/common/upgrade"
 )
 
 // TODO: "route expressions can only contain alphanumeric characters", we need to change the cosmos sdk to support slash
@@ -40,8 +41,15 @@ func (msg BurnMsg) GetSigners() []sdk.AccAddress           { return []sdk.AccAdd
 // ValidateBasic does a simple validation check that
 // doesn't require access to any other information.
 func (msg BurnMsg) ValidateBasic() sdk.Error {
+	if sdk.IsUpgrade(upgrade.BEP8) && types.IsValidMiniTokenSymbol(msg.Symbol) {
+		if msg.Amount <= 0 {
+			return sdk.ErrInsufficientFunds("amount should be more than 0")
+		}
+		return nil
+	}
+	// if BEP8 not upgraded, we rely on `ValidateTokenSymbol` rejecting the MiniToken.
 	// expect all msgs that reference a token after issue to use the suffixed form (e.g. "BNB-ABC")
-	err := types.ValidateMapperTokenSymbol(msg.Symbol)
+	err := types.ValidateTokenSymbol(msg.Symbol)
 	if err != nil {
 		return sdk.ErrInvalidCoins(err.Error())
 	}
diff --git a/plugins/tokens/client/cli/commands.go b/plugins/tokens/client/cli/commands.go
index f0c6cc093..67de8c4f6 100644
--- a/plugins/tokens/client/cli/commands.go
+++ b/plugins/tokens/client/cli/commands.go
@@ -51,7 +51,15 @@ func AddCommands(cmd *cobra.Command, cdc *wire.Codec) {
 		client.PostCommands(MultiSendCmd(cdc))...,
 	)
 
+	tokenCmd.AddCommand(
+		client.PostCommands(
+			issueMiniTokenCmd(cmdr),
+			issueTinyTokenCmd(cmdr),
+			setTokenURICmd(cmdr))...,
+	)
+
 	tokenCmd.AddCommand(client.LineBreak)
 
 	cmd.AddCommand(tokenCmd)
+
 }
diff --git a/plugins/tokens/client/cli/helper.go b/plugins/tokens/client/cli/helper.go
index dc33e121e..07fd494f3 100644
--- a/plugins/tokens/client/cli/helper.go
+++ b/plugins/tokens/client/cli/helper.go
@@ -1,6 +1,7 @@
 package commands
 
 import (
+	"bytes"
 	"errors"
 	"strconv"
 	"strings"
@@ -15,6 +16,8 @@ import (
 	"github.com/binance-chain/node/wire"
 )
 
+const miniTokenKeyPrefix = "mini"
+
 type Commander struct {
 	Cdc *wire.Codec
 }
@@ -30,9 +33,11 @@ func (c Commander) checkAndSendTx(cmd *cobra.Command, args []string, builder msg
 	}
 
 	symbol := viper.GetString(flagSymbol)
-	err = types.ValidateMapperTokenSymbol(symbol)
-	if err != nil {
-		return err
+	if !types.IsValidMiniTokenSymbol(symbol) {
+		err = types.ValidateTokenSymbol(symbol)
+		if err != nil {
+			return err
+		}
 	}
 
 	symbol = strings.ToUpper(symbol)
@@ -60,3 +65,18 @@ func parseAmount(amountStr string) (int64, error) {
 
 	return amount, nil
 }
+
+func validateTokenURI(uri string) error {
+	if len(uri) > 2048 {
+		return errors.New("uri cannot be longer than 2048 characters")
+	}
+	return nil
+}
+
+func calcMiniTokenKey(symbol string) []byte {
+	var buf bytes.Buffer
+	buf.WriteString(miniTokenKeyPrefix)
+	buf.WriteString(":")
+	buf.WriteString(symbol)
+	return buf.Bytes()
+}
diff --git a/plugins/tokens/client/cli/info.go b/plugins/tokens/client/cli/info.go
index 625ab4395..b662dea5c 100644
--- a/plugins/tokens/client/cli/info.go
+++ b/plugins/tokens/client/cli/info.go
@@ -18,7 +18,7 @@ import (
 func getTokenInfoCmd(cmdr Commander) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "info <symbol>",
-		Short: "Query token info",
+		Short: "Query token/mini-token info",
 		RunE:  cmdr.runGetToken,
 	}
 
@@ -34,7 +34,12 @@ func (c Commander) runGetToken(cmd *cobra.Command, args []string) error {
 		return errors.New("you must provide the symbol")
 	}
 
-	key := []byte(strings.ToUpper(symbol))
+	var key []byte
+	if types.IsMiniTokenSymbol(symbol) {
+		key = calcMiniTokenKey(strings.ToUpper(symbol))
+	} else {
+		key = []byte(strings.ToUpper(symbol))
+	}
 
 	res, err := ctx.QueryStore(key, common.TokenStoreName)
 	if err != nil {
@@ -47,14 +52,14 @@ func (c Commander) runGetToken(cmd *cobra.Command, args []string) error {
 	}
 
 	// decode the value
-	token := new(types.Token)
+	var token types.IToken
 	err = c.Cdc.UnmarshalBinaryBare(res, &token)
 	if err != nil {
 		return err
 	}
 
 	// print out the toke info
-	output, err := wire.MarshalJSONIndent(c.Cdc, token)
+	output, err := wire.MarshalJSONIndent(c.Cdc, &token)
 	if err != nil {
 		return err
 	}
diff --git a/plugins/tokens/client/cli/issue.go b/plugins/tokens/client/cli/issue.go
index a60c6f72f..222e51a8b 100644
--- a/plugins/tokens/client/cli/issue.go
+++ b/plugins/tokens/client/cli/issue.go
@@ -1,6 +1,8 @@
 package commands
 
 import (
+	"strings"
+
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
@@ -57,7 +59,7 @@ func (c Commander) issueToken(cmd *cobra.Command, args []string) error {
 	}
 
 	symbol := viper.GetString(flagSymbol)
-	err = types.ValidateIssueMsgTokenSymbol(symbol)
+	err = types.ValidateIssueSymbol(symbol)
 	if err != nil {
 		return err
 	}
@@ -83,15 +85,22 @@ func (c Commander) mintToken(cmd *cobra.Command, args []string) error {
 	}
 
 	symbol := viper.GetString(flagSymbol)
-	err = types.ValidateMapperTokenSymbol(symbol)
-	if err != nil {
-		return err
-	}
-
 	amount := viper.GetInt64(flagAmount)
-	err = checkSupplyAmount(amount)
-	if err != nil {
-		return err
+
+	if types.IsValidMiniTokenSymbol(strings.ToUpper(symbol)) {
+		err = checkMiniTokenSupplyAmount(amount)
+		if err != nil {
+			return err
+		}
+	} else {
+		err = types.ValidateTokenSymbol(symbol)
+		if err != nil {
+			return err
+		}
+		err = checkSupplyAmount(amount)
+		if err != nil {
+			return err
+		}
 	}
 
 	msg := issue.NewMintMsg(from, symbol, amount)
@@ -104,3 +113,10 @@ func checkSupplyAmount(amount int64) error {
 	}
 	return nil
 }
+func checkMiniTokenSupplyAmount(amount int64) error {
+	if amount <= types.MiniTokenMinExecutionAmount || amount > types.MiniTokenSupplyUpperBound {
+		return errors.New("invalid supply amount")
+	}
+
+	return nil
+}
diff --git a/plugins/tokens/client/cli/issue_mini.go b/plugins/tokens/client/cli/issue_mini.go
new file mode 100644
index 000000000..80190062c
--- /dev/null
+++ b/plugins/tokens/client/cli/issue_mini.go
@@ -0,0 +1,80 @@
+package commands
+
+import (
+	"fmt"
+
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+	"github.com/spf13/viper"
+
+	"github.com/binance-chain/node/common/client"
+	"github.com/binance-chain/node/common/types"
+	"github.com/binance-chain/node/plugins/tokens/issue"
+)
+
+const (
+	flagTokenUri = "token-uri"
+)
+
+func issueMiniTokenCmd(cmdr Commander) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "issue-mini",
+		Short: "issue a new mini-token",
+		RunE:  cmdr.issueMiniToken,
+	}
+
+	cmd.Flags().String(flagTokenName, "", "name of the new token")
+	cmd.Flags().StringP(flagSymbol, "s", "", "symbol of the new token")
+	cmd.Flags().Int64P(flagTotalSupply, "n", 0, "total supply of the new token")
+	cmd.Flags().Bool(flagMintable, false, "whether the token can be minted")
+	cmd.Flags().String(flagTokenUri, "", "uri of the token information")
+	cmd.MarkFlagRequired(flagTotalSupply)
+	return cmd
+}
+
+func (c Commander) issueMiniToken(cmd *cobra.Command, args []string) error {
+	cliCtx, txBldr := client.PrepareCtx(c.Cdc)
+	from, err := cliCtx.GetFromAddress()
+	if err != nil {
+		return err
+	}
+
+	name := viper.GetString(flagTokenName)
+	if len(name) == 0 {
+		return errors.New("you must provide the name of the token")
+	}
+
+	symbol := viper.GetString(flagSymbol)
+	err = types.ValidateIssueMiniSymbol(symbol)
+	if err != nil {
+		return err
+	}
+
+	supply := viper.GetInt64(flagTotalSupply)
+	err = checkMiniSupplyAmount(supply, int8(types.MiniRangeType))
+	if err != nil {
+		return err
+	}
+
+	mintable := viper.GetBool(flagMintable)
+
+	tokenURI := viper.GetString(flagTokenUri)
+	err = validateTokenURI(tokenURI)
+	if err != nil {
+		return err
+	}
+
+	// build message
+	msg := issue.NewIssueMiniMsg(from, name, symbol, supply, mintable, tokenURI)
+	return client.SendOrPrintTx(cliCtx, txBldr, msg)
+}
+
+func checkMiniSupplyAmount(amount int64, tokenType int8) error {
+	if amount <= types.MiniTokenMinExecutionAmount || amount > types.MiniTokenSupplyUpperBound {
+		return errors.New("invalid supply amount")
+	}
+	if amount > types.SupplyRangeType(tokenType).UpperBound() {
+		return errors.New(fmt.Sprintf("supply amount cannot exceed max supply amount of %s - %d", types.SupplyRangeType(tokenType).String(), types.SupplyRangeType(tokenType).UpperBound()))
+	}
+	return nil
+}
diff --git a/plugins/tokens/client/cli/issue_tiny.go b/plugins/tokens/client/cli/issue_tiny.go
new file mode 100644
index 000000000..b0fb3a9e5
--- /dev/null
+++ b/plugins/tokens/client/cli/issue_tiny.go
@@ -0,0 +1,64 @@
+package commands
+
+import (
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+	"github.com/spf13/viper"
+
+	"github.com/binance-chain/node/common/client"
+	"github.com/binance-chain/node/common/types"
+	"github.com/binance-chain/node/plugins/tokens/issue"
+)
+
+func issueTinyTokenCmd(cmdr Commander) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "issue-tiny",
+		Short: "issue a new tiny-token",
+		RunE:  cmdr.issueTinyToken,
+	}
+
+	cmd.Flags().String(flagTokenName, "", "name of the new token")
+	cmd.Flags().StringP(flagSymbol, "s", "", "symbol of the new token")
+	cmd.Flags().Int64P(flagTotalSupply, "n", 0, "total supply of the new token")
+	cmd.Flags().Bool(flagMintable, false, "whether the token can be minted")
+	cmd.Flags().String(flagTokenUri, "", "uri of the token information")
+	cmd.MarkFlagRequired(flagTotalSupply)
+	return cmd
+}
+
+func (c Commander) issueTinyToken(cmd *cobra.Command, args []string) error {
+	cliCtx, txBldr := client.PrepareCtx(c.Cdc)
+	from, err := cliCtx.GetFromAddress()
+	if err != nil {
+		return err
+	}
+
+	name := viper.GetString(flagTokenName)
+	if len(name) == 0 {
+		return errors.New("you must provide the name of the token")
+	}
+
+	symbol := viper.GetString(flagSymbol)
+	err = types.ValidateIssueMiniSymbol(symbol)
+	if err != nil {
+		return err
+	}
+
+	supply := viper.GetInt64(flagTotalSupply)
+	err = checkMiniSupplyAmount(supply, int8(types.TinyRangeType))
+	if err != nil {
+		return err
+	}
+
+	mintable := viper.GetBool(flagMintable)
+
+	tokenURI := viper.GetString(flagTokenUri)
+	err = validateTokenURI(tokenURI)
+	if err != nil {
+		return err
+	}
+
+	// build message
+	msg := issue.NewIssueTinyMsg(from, name, symbol, supply, mintable, tokenURI)
+	return client.SendOrPrintTx(cliCtx, txBldr, msg)
+}
diff --git a/plugins/tokens/client/cli/seturi_mini.go b/plugins/tokens/client/cli/seturi_mini.go
new file mode 100644
index 000000000..b92bb496c
--- /dev/null
+++ b/plugins/tokens/client/cli/seturi_mini.go
@@ -0,0 +1,43 @@
+package commands
+
+import (
+	"github.com/binance-chain/node/common/client"
+	"github.com/binance-chain/node/common/types"
+	"github.com/binance-chain/node/plugins/tokens/seturi"
+	"github.com/spf13/cobra"
+	"github.com/spf13/viper"
+)
+
+func setTokenURICmd(cmdr Commander) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "set-uri-mini --symbol {symbol} --uri {token uri} --from {token issuer address}",
+		Short: "set token URI of mini-token",
+		RunE:  cmdr.setTokenURI,
+	}
+
+	cmd.Flags().StringP(flagSymbol, "s", "", "symbol of the mini-token")
+	cmd.Flags().String(flagTokenUri, "", "uri of the token information")
+
+	return cmd
+}
+
+func (c Commander) setTokenURI(cmd *cobra.Command, args []string) error {
+	cliCtx, txBldr := client.PrepareCtx(c.Cdc)
+	from, err := cliCtx.GetFromAddress()
+	if err != nil {
+		return err
+	}
+	symbol := viper.GetString(flagSymbol)
+	err = types.ValidateMiniTokenSymbol(symbol)
+	if err != nil {
+		return err
+	}
+	tokenURI := viper.GetString(flagTokenUri)
+	err = validateTokenURI(tokenURI)
+	if err != nil {
+		return err
+	}
+
+	msg := seturi.NewSetUriMsg(from, symbol, tokenURI)
+	return client.SendOrPrintTx(cliCtx, txBldr, msg)
+}
diff --git a/plugins/tokens/client/rest/gettoken.go b/plugins/tokens/client/rest/gettoken.go
index 38378e9a9..19f7443c6 100644
--- a/plugins/tokens/client/rest/gettoken.go
+++ b/plugins/tokens/client/rest/gettoken.go
@@ -14,18 +14,29 @@ import (
 	"github.com/binance-chain/node/wire"
 )
 
-func getTokenInfo(ctx context.CLIContext, cdc *wire.Codec, symbol string) (*types.Token, error) {
-	bz, err := ctx.Query(fmt.Sprintf("tokens/info/%s", symbol), nil)
+func getTokenInfo(ctx context.CLIContext, cdc *wire.Codec, symbol string, isMini bool) (types.IToken, error) {
+	var abciPrefix string
+	if isMini {
+		abciPrefix = "mini-tokens"
+	} else {
+		abciPrefix = "tokens"
+	}
+	bz, err := ctx.Query(fmt.Sprintf("%s/info/%s", abciPrefix, symbol), nil)
+	if err != nil {
+		return nil, err
+	}
+
+	var token types.IToken
+	err = cdc.UnmarshalBinaryLengthPrefixed(bz, token)
 	if err != nil {
+		fmt.Println(err)
 		return nil, err
 	}
-	var token types.Token
-	err = cdc.UnmarshalBinaryLengthPrefixed(bz, &token)
-	return &token, nil
+	return token, nil
 }
 
 // GetTokenReqHandler creates an http request handler to get info for an individual token
-func GetTokenReqHandler(cdc *wire.Codec, ctx context.CLIContext) http.HandlerFunc {
+func GetTokenReqHandler(cdc *wire.Codec, ctx context.CLIContext, isMini bool) http.HandlerFunc {
 	type params struct {
 		symbol string
 	}
@@ -55,7 +66,7 @@ func GetTokenReqHandler(cdc *wire.Codec, ctx context.CLIContext) http.HandlerFun
 			return
 		}
 
-		token, err := getTokenInfo(ctx, cdc, params.symbol)
+		token, err := getTokenInfo(ctx, cdc, params.symbol, isMini)
 		if err != nil {
 			throw(w, http.StatusInternalServerError, err)
 			return
diff --git a/plugins/tokens/client/rest/gettokens.go b/plugins/tokens/client/rest/gettokens.go
index 51af8d5f0..93253f436 100644
--- a/plugins/tokens/client/rest/gettokens.go
+++ b/plugins/tokens/client/rest/gettokens.go
@@ -17,18 +17,28 @@ const maxTokensLimit = 1000
 const defaultTokensLimit = 100
 const defaultTokensOffset = 0
 
-func listAllTokens(ctx context.CLIContext, cdc *wire.Codec, offset int, limit int, showZeroSupplyTokens bool) ([]types.Token, error) {
-	bz, err := ctx.Query(fmt.Sprintf("tokens/list/%d/%d/%s", offset, limit, strconv.FormatBool(showZeroSupplyTokens)), nil)
+func listAllTokens(ctx context.CLIContext, cdc *wire.Codec, offset int, limit int, showZeroSupplyTokens bool, isMini bool) (interface{}, error) {
+	var abciPrefix string
+	if isMini {
+		abciPrefix = "mini-tokens"
+	} else {
+		abciPrefix = "tokens"
+	}
+	bz, err := ctx.Query(fmt.Sprintf("%s/list/%d/%d/%s", abciPrefix, offset, limit, strconv.FormatBool(showZeroSupplyTokens)), nil)
 	if err != nil {
 		return nil, err
 	}
-	tokens := make([]types.Token, 0)
+
+	tokens := make([]types.IToken, 0)
 	err = cdc.UnmarshalBinaryLengthPrefixed(bz, &tokens)
+	if err != nil {
+		return nil, err
+	}
 	return tokens, nil
 }
 
 // GetTokensReqHandler creates an http request handler to get the list of tokens in the token mapper
-func GetTokensReqHandler(cdc *wire.Codec, ctx context.CLIContext) http.HandlerFunc {
+func GetTokensReqHandler(cdc *wire.Codec, ctx context.CLIContext, isMini bool) http.HandlerFunc {
 	type params struct {
 		limit                int
 		offset               int
@@ -88,7 +98,7 @@ func GetTokensReqHandler(cdc *wire.Codec, ctx context.CLIContext) http.HandlerFu
 			params.limit = maxTokensLimit
 		}
 
-		tokens, err := listAllTokens(ctx, cdc, params.offset, params.limit, params.showZeroSupplyTokens)
+		tokens, err := listAllTokens(ctx, cdc, params.offset, params.limit, params.showZeroSupplyTokens, isMini)
 		if err != nil {
 			throw(w, http.StatusInternalServerError, err)
 			return
diff --git a/plugins/tokens/freeze/handler.go b/plugins/tokens/freeze/handler.go
index 50b41971e..89bb0be0b 100644
--- a/plugins/tokens/freeze/handler.go
+++ b/plugins/tokens/freeze/handler.go
@@ -1,6 +1,7 @@
 package freeze
 
 import (
+	"fmt"
 	"reflect"
 	"strings"
 
@@ -33,11 +34,20 @@ func handleFreezeToken(ctx sdk.Context, tokenMapper store.Mapper, accKeeper auth
 	symbol := strings.ToUpper(msg.Symbol)
 	logger := log.With("module", "token", "symbol", symbol, "amount", freezeAmount, "addr", msg.From)
 	coins := keeper.GetCoins(ctx, msg.From)
-	if coins.AmountOf(symbol) < freezeAmount {
+	balance := coins.AmountOf(symbol)
+	if balance < freezeAmount {
 		logger.Info("freeze token failed", "reason", "no enough free tokens to freeze")
 		return sdk.ErrInsufficientCoins("do not have enough token to freeze").Result()
 	}
 
+	if common.IsMiniTokenSymbol(symbol) {
+		if msg.Amount < common.MiniTokenMinExecutionAmount && balance != freezeAmount {
+			logger.Info("freeze token failed", "reason", "freeze amount doesn't reach the min amount")
+			return sdk.ErrInvalidCoins(fmt.Sprintf("freeze amount is too small, the min amount is %d or total account balance",
+				common.MiniTokenMinExecutionAmount)).Result()
+		}
+	}
+
 	account := accKeeper.GetAccount(ctx, msg.From).(common.NamedAccount)
 	newFrozenTokens := account.GetFrozenCoins().Plus(sdk.Coins{{Denom: symbol, Amount: freezeAmount}})
 	newFreeTokens := account.GetCoins().Minus(sdk.Coins{{Denom: symbol, Amount: freezeAmount}})
@@ -52,6 +62,7 @@ func handleUnfreezeToken(ctx sdk.Context, tokenMapper store.Mapper, accKeeper au
 	unfreezeAmount := msg.Amount
 	symbol := strings.ToUpper(msg.Symbol)
 	logger := log.With("module", "token", "symbol", symbol, "amount", unfreezeAmount, "addr", msg.From)
+
 	account := accKeeper.GetAccount(ctx, msg.From).(common.NamedAccount)
 	frozenAmount := account.GetFrozenCoins().AmountOf(symbol)
 	if frozenAmount < unfreezeAmount {
@@ -59,6 +70,14 @@ func handleUnfreezeToken(ctx sdk.Context, tokenMapper store.Mapper, accKeeper au
 		return sdk.ErrInsufficientCoins("do not have enough token to unfreeze").Result()
 	}
 
+	if common.IsMiniTokenSymbol(symbol) {
+		if unfreezeAmount < common.MiniTokenMinExecutionAmount && frozenAmount != unfreezeAmount {
+			logger.Info("unfreeze token failed", "reason", "unfreeze amount doesn't reach the min amount")
+			return sdk.ErrInvalidCoins(fmt.Sprintf("freeze amount is too small, the min amount is %d or total frozen balance",
+				common.MiniTokenMinExecutionAmount)).Result()
+		}
+	}
+
 	newFrozenTokens := account.GetFrozenCoins().Minus(sdk.Coins{{Denom: symbol, Amount: unfreezeAmount}})
 	newFreeTokens := account.GetCoins().Plus(sdk.Coins{{Denom: symbol, Amount: unfreezeAmount}})
 	account.SetFrozenCoins(newFrozenTokens)
diff --git a/plugins/tokens/freeze/handler_test.go b/plugins/tokens/freeze/handler_test.go
new file mode 100644
index 000000000..eac894eff
--- /dev/null
+++ b/plugins/tokens/freeze/handler_test.go
@@ -0,0 +1,209 @@
+package freeze
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/cosmos/cosmos-sdk/baseapp"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/cosmos/cosmos-sdk/x/auth"
+	"github.com/cosmos/cosmos-sdk/x/bank"
+
+	abci "github.com/tendermint/tendermint/abci/types"
+	"github.com/tendermint/tendermint/libs/log"
+
+	"github.com/binance-chain/node/common/testutils"
+	"github.com/binance-chain/node/common/types"
+	"github.com/binance-chain/node/common/upgrade"
+	"github.com/binance-chain/node/plugins/tokens/issue"
+	"github.com/binance-chain/node/plugins/tokens/store"
+	"github.com/binance-chain/node/wire"
+)
+
+func setup() (sdk.Context, sdk.Handler, sdk.Handler, auth.AccountKeeper, store.Mapper) {
+	ms, capKey1, capKey2 := testutils.SetupMultiStoreForUnitTest()
+	cdc := wire.NewCodec()
+	cdc.RegisterInterface((*types.IToken)(nil), nil)
+	cdc.RegisterConcrete(&types.Token{}, "bnbchain/Token", nil)
+	cdc.RegisterConcrete(&types.MiniToken{}, "bnbchain/MiniToken", nil)
+	tokenMapper := store.NewMapper(cdc, capKey1)
+	//app.AccountKeeper = auth.NewAccountKeeper(cdc, common.AccountStoreKey, types.ProtoAppAccount)
+	accountKeeper := auth.NewAccountKeeper(cdc, capKey2, types.ProtoAppAccount)
+	bankKeeper := bank.NewBaseKeeper(accountKeeper)
+	handler := NewHandler(tokenMapper, accountKeeper, bankKeeper)
+	tokenHandler := issue.NewHandler(tokenMapper, bankKeeper)
+
+	accountStore := ms.GetKVStore(capKey2)
+	accountStoreCache := auth.NewAccountStoreCache(cdc, accountStore, 10)
+	ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid", Height: 1},
+		sdk.RunTxModeDeliver, log.NewNopLogger()).
+		WithAccountCache(auth.NewAccountCache(accountStoreCache))
+	return ctx, handler, tokenHandler, accountKeeper, tokenMapper
+}
+
+func setChainVersion() {
+	upgrade.Mgr.AddUpgradeHeight(upgrade.BEP8, -1)
+}
+
+func resetChainVersion() {
+	upgrade.Mgr.Config.HeightMap = nil
+}
+
+func TestHandleFreezeMini(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	ctx, handler, miniIssueHandler, accountKeeper, tokenMapper := setup()
+	_, acc := testutils.NewAccount(ctx, accountKeeper, 100e8)
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "000")
+	msg := issue.NewIssueMiniMsg(acc.GetAddress(), "New BNB", "NNB", 10000e8, false, "http://www.xyz.com/nnb.json")
+	sdkResult := miniIssueHandler(ctx, msg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+
+	token, err := tokenMapper.GetToken(ctx, "NNB-000M")
+	require.NoError(t, err)
+	expectedToken := types.NewMiniToken("New BNB", "NNB", "NNB-000M", 2, 10000e8, acc.GetAddress(), false, "http://www.xyz.com/nnb.json")
+	require.Equal(t, expectedToken, token)
+
+	account := accountKeeper.GetAccount(ctx, msg.From).(types.NamedAccount)
+	amount := account.GetCoins().AmountOf("NNB-000M")
+	frozenAmount := account.GetFrozenCoins().AmountOf("NNB-000M")
+	require.Equal(t, int64(10000e8), amount)
+	require.Equal(t, int64(0), frozenAmount)
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "002")
+	freezeMsg := NewFreezeMsg(acc.GetAddress(), "NNB-000M", 10000e8+1)
+	sdkResult = handler(ctx, freezeMsg)
+	require.Equal(t, false, sdkResult.Code.IsOK())
+	require.Contains(t, sdkResult.Log, "do not have enough token to freeze")
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "002")
+	freezeMsg = NewFreezeMsg(acc.GetAddress(), "NNB-000M", 1e8-1)
+	sdkResult = handler(ctx, freezeMsg)
+	require.Equal(t, false, sdkResult.Code.IsOK())
+	require.Contains(t, sdkResult.Log, "freeze amount is too small")
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "002")
+	freezeMsg = NewFreezeMsg(acc.GetAddress(), "NNB-000M", 9999e8+1)
+	sdkResult = handler(ctx, freezeMsg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+
+	account = accountKeeper.GetAccount(ctx, msg.From).(types.NamedAccount)
+	amount = account.GetCoins().AmountOf("NNB-000M")
+	frozenAmount = account.GetFrozenCoins().AmountOf("NNB-000M")
+	require.Equal(t, int64(1e8-1), amount)
+	require.Equal(t, int64(9999e8+1), frozenAmount)
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "002")
+	freezeMsg = NewFreezeMsg(acc.GetAddress(), "NNB-000M", 1e8-1)
+	sdkResult = handler(ctx, freezeMsg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+
+	account = accountKeeper.GetAccount(ctx, msg.From).(types.NamedAccount)
+	amount = account.GetCoins().AmountOf("NNB-000M")
+	frozenAmount = account.GetFrozenCoins().AmountOf("NNB-000M")
+	require.Equal(t, int64(0), amount)
+	require.Equal(t, int64(10000e8), frozenAmount)
+
+	token, err = tokenMapper.GetToken(ctx, "NNB-000M")
+	require.NoError(t, err)
+	expectedToken = types.NewMiniToken("New BNB", "NNB", "NNB-000M", 2, 10000e8, acc.GetAddress(), false, "http://www.xyz.com/nnb.json")
+	require.Equal(t, expectedToken, token)
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "003")
+	unfreezeMsg := NewUnfreezeMsg(acc.GetAddress(), "NNB-000M", 1e8-1)
+	sdkResult = handler(ctx, unfreezeMsg)
+	require.Equal(t, false, sdkResult.Code.IsOK())
+	require.Contains(t, sdkResult.Log, "freeze amount is too small")
+
+	unfreezeMsg = NewUnfreezeMsg(acc.GetAddress(), "NNB-000M", 1e8)
+	sdkResult = handler(ctx, unfreezeMsg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+	account = accountKeeper.GetAccount(ctx, msg.From).(types.NamedAccount)
+	amount = account.GetCoins().AmountOf("NNB-000M")
+	frozenAmount = account.GetFrozenCoins().AmountOf("NNB-000M")
+	require.Equal(t, int64(1e8), amount)
+	require.Equal(t, int64(9999e8), frozenAmount)
+
+	unfreezeMsg = NewUnfreezeMsg(acc.GetAddress(), "NNB-000M", 9999e8-1)
+	sdkResult = handler(ctx, unfreezeMsg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+	account = accountKeeper.GetAccount(ctx, msg.From).(types.NamedAccount)
+	amount = account.GetCoins().AmountOf("NNB-000M")
+	frozenAmount = account.GetFrozenCoins().AmountOf("NNB-000M")
+	require.Equal(t, int64(10000e8-1), amount)
+	require.Equal(t, int64(1), frozenAmount)
+
+	unfreezeMsg = NewUnfreezeMsg(acc.GetAddress(), "NNB-000M", 1)
+	sdkResult = handler(ctx, unfreezeMsg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+	account = accountKeeper.GetAccount(ctx, msg.From).(types.NamedAccount)
+	amount = account.GetCoins().AmountOf("NNB-000M")
+	frozenAmount = account.GetFrozenCoins().AmountOf("NNB-000M")
+	require.Equal(t, int64(10000e8), amount)
+	require.Equal(t, int64(0), frozenAmount)
+
+}
+
+func TestHandleFreeze(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	ctx, handler, issueHandler, accountKeeper, tokenMapper := setup()
+	_, acc := testutils.NewAccount(ctx, accountKeeper, 100e8)
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "000")
+	msg := issue.NewIssueMsg(acc.GetAddress(), "New BNB", "NNB", 10000e8, false)
+	sdkResult := issueHandler(ctx, msg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+
+	_, err := tokenMapper.GetToken(ctx, "NNB-000M")
+	require.NotNil(t, err)
+	require.Contains(t, err.Error(), "token(NNB-000M) not found")
+
+	account := accountKeeper.GetAccount(ctx, msg.From).(types.NamedAccount)
+	amount := account.GetCoins().AmountOf("NNB-000")
+	frozenAmount := account.GetFrozenCoins().AmountOf("NNB-000")
+	require.Equal(t, int64(10000e8), amount)
+	require.Equal(t, int64(0), frozenAmount)
+
+	_, err = tokenMapper.GetToken(ctx, "NNB-000")
+	require.Equal(t, true, sdkResult.Code.IsOK())
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "002")
+	freezeMsg := NewFreezeMsg(acc.GetAddress(), "NNB-000", 10001e8)
+	sdkResult = handler(ctx, freezeMsg)
+	require.Equal(t, false, sdkResult.Code.IsOK())
+	require.Contains(t, sdkResult.Log, "do not have enough token to freeze")
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "002")
+	freezeMsg = NewFreezeMsg(acc.GetAddress(), "NNB-000", 9999e8+1)
+	sdkResult = handler(ctx, freezeMsg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+
+	account = accountKeeper.GetAccount(ctx, msg.From).(types.NamedAccount)
+	amount = account.GetCoins().AmountOf("NNB-000")
+	frozenAmount = account.GetFrozenCoins().AmountOf("NNB-000")
+	require.Equal(t, int64(1e8-1), amount)
+	require.Equal(t, int64(9999e8+1), frozenAmount)
+
+	token, err := tokenMapper.GetToken(ctx, "NNB-000")
+	require.NoError(t, err)
+	expectedToken, err := types.NewToken("New BNB", "NNB-000", 10000e8, acc.GetAddress(), false)
+	require.Equal(t, expectedToken, token)
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "003")
+	unfreezeMsg := NewUnfreezeMsg(acc.GetAddress(), "NNB-000", 1)
+	sdkResult = handler(ctx, unfreezeMsg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+
+	unfreezeMsg = NewUnfreezeMsg(acc.GetAddress(), "NNB-000", 9999e8)
+	sdkResult = handler(ctx, unfreezeMsg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+	account = accountKeeper.GetAccount(ctx, msg.From).(types.NamedAccount)
+	amount = account.GetCoins().AmountOf("NNB-000")
+	frozenAmount = account.GetFrozenCoins().AmountOf("NNB-000")
+	require.Equal(t, int64(10000e8), amount)
+	require.Equal(t, int64(0), frozenAmount)
+
+}
diff --git a/plugins/tokens/freeze/msg.go b/plugins/tokens/freeze/msg.go
index 4884af8cd..59ee7c7ea 100644
--- a/plugins/tokens/freeze/msg.go
+++ b/plugins/tokens/freeze/msg.go
@@ -7,6 +7,7 @@ import (
 	sdk "github.com/cosmos/cosmos-sdk/types"
 
 	"github.com/binance-chain/node/common/types"
+	"github.com/binance-chain/node/common/upgrade"
 )
 
 // TODO: "route expressions can only contain alphanumeric characters", we need to change the cosmos sdk to support slash
@@ -40,8 +41,15 @@ func (msg FreezeMsg) GetSigners() []sdk.AccAddress           { return []sdk.AccA
 // ValidateBasic does a simple validation check that
 // doesn't require access to any other information.
 func (msg FreezeMsg) ValidateBasic() sdk.Error {
+	if sdk.IsUpgrade(upgrade.BEP8) && types.IsValidMiniTokenSymbol(msg.Symbol) {
+		if msg.Amount <= 0 {
+			return sdk.ErrInsufficientFunds("amount should be more than 0")
+		}
+		return nil
+	}
+	// if BEP8 not upgraded, we rely on `ValidateTokenSymbol` rejecting the MiniToken.
 	// expect all msgs that reference a token after issue to use the suffixed form (e.g. "BNB-ABC")
-	err := types.ValidateMapperTokenSymbol(msg.Symbol)
+	err := types.ValidateTokenSymbol(msg.Symbol)
 	if err != nil {
 		return sdk.ErrInvalidCoins(err.Error())
 	}
@@ -82,8 +90,15 @@ func (msg UnfreezeMsg) GetInvolvedAddresses() []sdk.AccAddress { return msg.GetS
 func (msg UnfreezeMsg) GetSigners() []sdk.AccAddress           { return []sdk.AccAddress{msg.From} }
 
 func (msg UnfreezeMsg) ValidateBasic() sdk.Error {
+	if sdk.IsUpgrade(upgrade.BEP8) && types.IsValidMiniTokenSymbol(msg.Symbol) {
+		if msg.Amount <= 0 {
+			return sdk.ErrInsufficientFunds("amount should be more than 0")
+		}
+		return nil
+	}
+	// if BEP8 not upgraded, we rely on `ValidateTokenSymbol` rejecting the MiniToken.
 	// expect all msgs that reference a token after issue to use the suffixed form (e.g. "BNB-ABC")
-	err := types.ValidateMapperTokenSymbol(msg.Symbol)
+	err := types.ValidateTokenSymbol(msg.Symbol)
 	if err != nil {
 		return sdk.ErrInvalidCoins(err.Error())
 	}
diff --git a/plugins/tokens/genesis.go b/plugins/tokens/genesis.go
index 0e7524326..473821e89 100644
--- a/plugins/tokens/genesis.go
+++ b/plugins/tokens/genesis.go
@@ -44,7 +44,7 @@ func InitGenesis(ctx sdk.Context, tokenMapper store.Mapper, coinKeeper bank.Keep
 		if err != nil {
 			panic(err)
 		}
-		err = tokenMapper.NewToken(ctx, *token)
+		err = tokenMapper.NewToken(ctx, token)
 		if err != nil {
 			panic(err)
 		}
diff --git a/plugins/tokens/issue/handler.go b/plugins/tokens/issue/handler.go
index 8b836880d..c784cc6da 100644
--- a/plugins/tokens/issue/handler.go
+++ b/plugins/tokens/issue/handler.go
@@ -10,6 +10,7 @@ import (
 	"github.com/cosmos/cosmos-sdk/baseapp"
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	"github.com/cosmos/cosmos-sdk/x/bank"
+	tmlog "github.com/tendermint/tendermint/libs/log"
 
 	"github.com/binance-chain/node/common/log"
 	"github.com/binance-chain/node/common/types"
@@ -25,6 +26,10 @@ func NewHandler(tokenMapper store.Mapper, keeper bank.Keeper) sdk.Handler {
 			return handleIssueToken(ctx, tokenMapper, keeper, msg)
 		case MintMsg:
 			return handleMintToken(ctx, tokenMapper, keeper, msg)
+		case IssueMiniMsg:
+			return handleIssueMiniToken(ctx, tokenMapper, keeper, msg)
+		case IssueTinyMsg:
+			return handleIssueTinyToken(ctx, tokenMapper, keeper, msg)
 		default:
 			errMsg := "Unrecognized msg type: " + reflect.TypeOf(msg).Name()
 			return sdk.ErrUnknownRequest(errMsg).Result()
@@ -36,29 +41,15 @@ func handleIssueToken(ctx sdk.Context, tokenMapper store.Mapper, bankKeeper bank
 	errLogMsg := "issue token failed"
 	symbol := strings.ToUpper(msg.Symbol)
 	logger := log.With("module", "token", "symbol", symbol, "name", msg.Name, "total_supply", msg.TotalSupply, "issuer", msg.From)
-	var suffix string
-
-	// TxHashKey is set in BaseApp's runMsgs
-	txHash := ctx.Value(baseapp.TxHashKey)
-	if txHashStr, ok := txHash.(string); ok {
-		if len(txHashStr) >= types.TokenSymbolTxHashSuffixLen {
-			suffix = txHashStr[:types.TokenSymbolTxHashSuffixLen]
-		} else {
-			logger.Error(errLogMsg,
-				"reason", fmt.Sprintf("%s on Context had a length of %d, expected >= %d",
-					baseapp.TxHashKey, len(txHashStr), types.TokenSymbolTxHashSuffixLen))
-			return sdk.ErrInternal(fmt.Sprintf("unable to get the %s from Context", baseapp.TxHashKey)).Result()
-		}
-	} else {
-		logger.Error(errLogMsg,
-			"reason", fmt.Sprintf("%s on Context is not a string as expected", baseapp.TxHashKey))
+	suffix, err := getTokenSuffix(ctx)
+	if err != nil {
+		logger.Error(errLogMsg, "reason", err.Error())
 		return sdk.ErrInternal(fmt.Sprintf("unable to get the %s from Context", baseapp.TxHashKey)).Result()
 	}
 
 	// the symbol is suffixed with the first n bytes of the tx hash
 	symbol = fmt.Sprintf("%s-%s", symbol, suffix)
-
-	if exists := tokenMapper.Exists(ctx, symbol); exists {
+	if exists := tokenMapper.ExistsBEP2(ctx, symbol); exists {
 		logger.Info(errLogMsg, "reason", "already exists")
 		return sdk.ErrInvalidCoins(fmt.Sprintf("symbol(%s) already exists", msg.Symbol)).Result()
 	}
@@ -68,35 +59,10 @@ func handleIssueToken(ctx sdk.Context, tokenMapper store.Mapper, bankKeeper bank
 		logger.Error(errLogMsg, "reason", "create token failed: "+err.Error())
 		return sdk.ErrInternal(fmt.Sprintf("unable to create token struct: %s", err.Error())).Result()
 	}
-
-	if err := tokenMapper.NewToken(ctx, *token); err != nil {
-		logger.Error(errLogMsg, "reason", "add token failed: "+err.Error())
-		return sdk.ErrInvalidCoins(err.Error()).Result()
-	}
-
-	if _, _, sdkError := bankKeeper.AddCoins(ctx, token.Owner,
-		sdk.Coins{{
-			Denom:  token.Symbol,
-			Amount: token.TotalSupply.ToInt64(),
-		}}); sdkError != nil {
-		logger.Error(errLogMsg, "reason", "update balance failed: "+sdkError.Error())
-		return sdkError.Result()
-	}
-
-	serialized, err := json.Marshal(token)
-	if err != nil {
-		logger.Error(errLogMsg, "reason", "fatal! unable to json serialize token: "+err.Error())
-		panic(err) // fatal, the sky is falling in goland
-	}
-
-	logger.Info("finished issuing token")
-
-	return sdk.Result{
-		Data: serialized,
-		Log:  fmt.Sprintf("Issued %s", token.Symbol),
-	}
+	return issue(ctx, logger, tokenMapper, bankKeeper, token)
 }
 
+//Mint MiniToken is also handled by this function
 func handleMintToken(ctx sdk.Context, tokenMapper store.Mapper, bankKeeper bank.Keeper, msg MintMsg) sdk.Result {
 	symbol := strings.ToUpper(msg.Symbol)
 	logger := log.With("module", "token", "symbol", symbol, "amount", msg.Amount, "minter", msg.From)
@@ -108,7 +74,7 @@ func handleMintToken(ctx sdk.Context, tokenMapper store.Mapper, bankKeeper bank.
 		return sdk.ErrInvalidCoins(fmt.Sprintf("symbol(%s) does not exist", msg.Symbol)).Result()
 	}
 
-	if !token.Mintable {
+	if !token.IsMintable() {
 		logger.Info(errLogMsg, "reason", "token cannot be minted")
 		return sdk.ErrInvalidCoins(fmt.Sprintf("token(%s) cannot be minted", msg.Symbol)).Result()
 	}
@@ -118,22 +84,33 @@ func handleMintToken(ctx sdk.Context, tokenMapper store.Mapper, bankKeeper bank.
 		return sdk.ErrUnauthorized(fmt.Sprintf("only the owner can mint token %s", msg.Symbol)).Result()
 	}
 
-	// use minus to prevent overflow
-	if msg.Amount > common.TokenMaxTotalSupply-token.TotalSupply.ToInt64() {
-		logger.Info(errLogMsg, "reason", "exceed the max total supply")
-		return sdk.ErrInvalidCoins(fmt.Sprintf("mint amount is too large, the max total supply is %ds",
-			common.TokenMaxTotalSupply)).Result()
+	if common.IsMiniTokenSymbol(symbol) {
+		miniToken := token.(*types.MiniToken)
+		// use minus to prevent overflow
+		if msg.Amount > miniToken.TokenType.UpperBound()-miniToken.TotalSupply.ToInt64() {
+			logger.Info(errLogMsg, "reason", "total supply exceeds the max total supply")
+			return sdk.ErrInvalidCoins(fmt.Sprintf("mint amount is too large, the max total supply is %d",
+				miniToken.TokenType.UpperBound())).Result()
+		}
+	} else {
+		// use minus to prevent overflow
+		if msg.Amount > common.TokenMaxTotalSupply-token.GetTotalSupply().ToInt64() {
+			logger.Info(errLogMsg, "reason", "exceed the max total supply")
+			return sdk.ErrInvalidCoins(fmt.Sprintf("mint amount is too large, the max total supply is %ds",
+				common.TokenMaxTotalSupply)).Result()
+		}
 	}
-	newTotalSupply := token.TotalSupply.ToInt64() + msg.Amount
+
+	newTotalSupply := token.GetTotalSupply().ToInt64() + msg.Amount
 	err = tokenMapper.UpdateTotalSupply(ctx, symbol, newTotalSupply)
 	if err != nil {
 		logger.Error(errLogMsg, "reason", "update total supply failed: "+err.Error())
 		return sdk.ErrInternal(fmt.Sprintf("update total supply failed")).Result()
 	}
 
-	_, _, sdkError := bankKeeper.AddCoins(ctx, token.Owner,
+	_, _, sdkError := bankKeeper.AddCoins(ctx, token.GetOwner(),
 		sdk.Coins{{
-			Denom:  token.Symbol,
+			Denom:  token.GetSymbol(),
 			Amount: msg.Amount,
 		}})
 	if sdkError != nil {
@@ -146,3 +123,51 @@ func handleMintToken(ctx sdk.Context, tokenMapper store.Mapper, bankKeeper bank.
 		Data: []byte(strconv.FormatInt(newTotalSupply, 10)),
 	}
 }
+
+func issue(ctx sdk.Context, logger tmlog.Logger, tokenMapper store.Mapper, bankKeeper bank.Keeper, token common.IToken) sdk.Result {
+	errLogMsg := "issue token failed"
+	if err := tokenMapper.NewToken(ctx, token); err != nil {
+		logger.Error(errLogMsg, "reason", "add token failed: "+err.Error())
+		return sdk.ErrInvalidCoins(err.Error()).Result()
+	}
+
+	if _, _, sdkError := bankKeeper.AddCoins(ctx, token.GetOwner(),
+		sdk.Coins{{
+			Denom:  token.GetSymbol(),
+			Amount: token.GetTotalSupply().ToInt64(),
+		}}); sdkError != nil {
+		logger.Error(errLogMsg, "reason", "update balance failed: "+sdkError.Error())
+		return sdkError.Result()
+	}
+
+	serialized, err := json.Marshal(token)
+	if err != nil {
+		logger.Error(errLogMsg, "reason", "fatal! unable to json serialize token: "+err.Error())
+		panic(err) // fatal, the sky is falling in goland
+	}
+
+	logger.Info("finished issuing token")
+
+	return sdk.Result{
+		Data: serialized,
+		Log:  fmt.Sprintf("Issued %s", token.GetSymbol()),
+	}
+}
+
+func getTokenSuffix(ctx sdk.Context) (suffix string, err error) {
+	// TxHashKey is set in BaseApp's runMsgs
+	txHash := ctx.Value(baseapp.TxHashKey)
+	if txHashStr, ok := txHash.(string); ok {
+		if len(txHashStr) >= types.TokenSymbolTxHashSuffixLen {
+			suffix = txHashStr[:types.TokenSymbolTxHashSuffixLen]
+			return suffix, nil
+		} else {
+			err = fmt.Errorf("%s on Context had a length of %d, expected >= %d",
+				baseapp.TxHashKey, len(txHashStr), types.TokenSymbolTxHashSuffixLen)
+			return "", err
+		}
+	} else {
+		err = fmt.Errorf("%s on Context is not a string as expected", baseapp.TxHashKey)
+		return "", err
+	}
+}
diff --git a/plugins/tokens/issue/handler_mini.go b/plugins/tokens/issue/handler_mini.go
new file mode 100644
index 000000000..55b21ec12
--- /dev/null
+++ b/plugins/tokens/issue/handler_mini.go
@@ -0,0 +1,55 @@
+package issue
+
+import (
+	"fmt"
+	"strings"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/cosmos/cosmos-sdk/x/bank"
+
+	"github.com/binance-chain/node/common/log"
+	common "github.com/binance-chain/node/common/types"
+	"github.com/binance-chain/node/plugins/tokens/store"
+)
+
+func handleIssueMiniToken(ctx sdk.Context, tokenMapper store.Mapper, bankKeeper bank.Keeper, msg IssueMiniMsg) sdk.Result {
+	errLogMsg := "issue miniToken failed"
+	origSymbol := strings.ToUpper(msg.Symbol)
+	logger := log.With("module", "mini-token", "symbol", origSymbol, "name", msg.Name, "total_supply", msg.TotalSupply, "issuer", msg.From)
+
+	suffix, err := getTokenSuffix(ctx)
+	if err != nil {
+		logger.Error(errLogMsg, "reason", err.Error())
+	}
+	suffix += common.MiniTokenSymbolMSuffix
+	symbol := fmt.Sprintf("%s-%s", origSymbol, suffix)
+
+	if exists := tokenMapper.ExistsMini(ctx, symbol); exists {
+		logger.Info(errLogMsg, "reason", "already exists")
+		return sdk.ErrInvalidCoins(fmt.Sprintf("symbol(%s) already exists", msg.Symbol)).Result()
+	}
+
+	token := common.NewMiniToken(msg.Name, origSymbol, symbol, common.MiniRangeType, msg.TotalSupply, msg.From, msg.Mintable, msg.TokenURI)
+	return issue(ctx, logger, tokenMapper, bankKeeper, token)
+}
+
+func handleIssueTinyToken(ctx sdk.Context, tokenMapper store.Mapper, bankKeeper bank.Keeper, msg IssueTinyMsg) sdk.Result {
+	errLogMsg := "issue tinyToken failed"
+	origSymbol := strings.ToUpper(msg.Symbol)
+	logger := log.With("module", "mini-token", "symbol", origSymbol, "name", msg.Name, "total_supply", msg.TotalSupply, "issuer", msg.From)
+
+	suffix, err := getTokenSuffix(ctx)
+	if err != nil {
+		logger.Error(errLogMsg, "reason", err.Error())
+	}
+	suffix += common.MiniTokenSymbolMSuffix
+	symbol := fmt.Sprintf("%s-%s", origSymbol, suffix)
+
+	if exists := tokenMapper.ExistsMini(ctx, symbol); exists {
+		logger.Info(errLogMsg, "reason", "already exists")
+		return sdk.ErrInvalidCoins(fmt.Sprintf("symbol(%s) already exists", msg.Symbol)).Result()
+	}
+
+	token := common.NewMiniToken(msg.Name, origSymbol, symbol, common.TinyRangeType, msg.TotalSupply, msg.From, msg.Mintable, msg.TokenURI)
+	return issue(ctx, logger, tokenMapper, bankKeeper, token)
+}
diff --git a/plugins/tokens/issue/handler_mini_test.go b/plugins/tokens/issue/handler_mini_test.go
new file mode 100644
index 000000000..a1e1f0abd
--- /dev/null
+++ b/plugins/tokens/issue/handler_mini_test.go
@@ -0,0 +1,140 @@
+package issue
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/cosmos/cosmos-sdk/baseapp"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/cosmos/cosmos-sdk/x/auth"
+	"github.com/cosmos/cosmos-sdk/x/bank"
+
+	abci "github.com/tendermint/tendermint/abci/types"
+	"github.com/tendermint/tendermint/libs/log"
+
+	"github.com/binance-chain/node/common/testutils"
+	"github.com/binance-chain/node/common/types"
+	"github.com/binance-chain/node/plugins/tokens/store"
+	"github.com/binance-chain/node/wire"
+)
+
+func setupMini() (sdk.Context, sdk.Handler, auth.AccountKeeper, store.Mapper) {
+	ms, capKey1, capKey2 := testutils.SetupMultiStoreForUnitTest()
+	cdc := wire.NewCodec()
+	cdc.RegisterInterface((*types.IToken)(nil), nil)
+	cdc.RegisterConcrete(&types.Token{}, "bnbchain/Token", nil)
+	cdc.RegisterConcrete(&types.MiniToken{}, "bnbchain/MiniToken", nil)
+	tokenMapper := store.NewMapper(cdc, capKey1)
+	accountKeeper := auth.NewAccountKeeper(cdc, capKey2, auth.ProtoBaseAccount)
+	bankKeeper := bank.NewBaseKeeper(accountKeeper)
+	handler := NewHandler(tokenMapper, bankKeeper)
+
+	accountStore := ms.GetKVStore(capKey2)
+	accountStoreCache := auth.NewAccountStoreCache(cdc, accountStore, 10)
+	ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid", Height: 1},
+		sdk.RunTxModeDeliver, log.NewNopLogger()).
+		WithAccountCache(auth.NewAccountCache(accountStoreCache))
+	return ctx, handler, accountKeeper, tokenMapper
+}
+
+func TestHandleIssueMiniToken(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	ctx, handler, accountKeeper, tokenMapper := setupMini()
+	_, acc := testutils.NewAccount(ctx, accountKeeper, 100e8)
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "000")
+	msg := NewIssueTinyMsg(acc.GetAddress(), "New BNB", "NNB", 10000e8+100, false, "http://www.xyz.com/nnb.json")
+	sdkResult := msg.ValidateBasic().Result()
+	require.Equal(t, false, sdkResult.Code.IsOK())
+	require.Contains(t, sdkResult.Log, fmt.Sprintf("total supply should be between %d and %d", types.MiniTokenMinExecutionAmount, types.TinyRangeType.UpperBound()))
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "000")
+	msg = NewIssueTinyMsg(acc.GetAddress(), "New BNB", "NNB", 10000e8, false, "http://www.xyz.com/nnb.json")
+	sdkResult = handler(ctx, msg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+
+	token, err := tokenMapper.GetToken(ctx, "NNB-000M")
+	require.NoError(t, err)
+	expectedToken := types.NewMiniToken("New BNB", "NNB", "NNB-000M", 1, 10000e8, acc.GetAddress(), false, "http://www.xyz.com/nnb.json")
+	require.Equal(t, expectedToken, token)
+
+	sdkResult = handler(ctx, msg)
+	require.Contains(t, sdkResult.Log, "symbol(NNB) already exists")
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "002")
+	msgMini := NewIssueMiniMsg(acc.GetAddress(), "New BB", "NBB", 1000000e8+100, false, "http://www.xyz.com/nnb.json")
+	sdkResult = msgMini.ValidateBasic().Result()
+	require.Equal(t, false, sdkResult.Code.IsOK())
+	require.Contains(t, sdkResult.Log, fmt.Sprintf("total supply should be between %d and %d", types.MiniTokenMinExecutionAmount, types.MiniRangeType.UpperBound()))
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "002")
+	msgMini = NewIssueMiniMsg(acc.GetAddress(), "New BB", "NBB", 10000e8+100, false, "http://www.xyz.com/nnb.json")
+	sdkResult = handler(ctx, msgMini)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+
+	token, err = tokenMapper.GetToken(ctx, "NBB-002M")
+	require.NoError(t, err)
+	expectedToken = types.NewMiniToken("New BB", "NBB", "NBB-002M", 2, 10000e8+100, acc.GetAddress(), false, "http://www.xyz.com/nnb.json")
+	require.Equal(t, expectedToken, token)
+}
+
+func TestHandleMintMiniToken(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	ctx, handler, accountKeeper, tokenMapper := setup()
+	_, acc := testutils.NewAccount(ctx, accountKeeper, 100e8)
+	mintMsg := NewMintMsg(acc.GetAddress(), "NNB-000M", 1001e8)
+	sdkResult := handler(ctx, mintMsg)
+	require.Contains(t, sdkResult.Log, "symbol(NNB-000M) does not exist")
+
+	issueMsg := NewIssueTinyMsg(acc.GetAddress(), "New BNB", "NNB", 9000e8, true, "http://www.xyz.com/nnb.json")
+	ctx = ctx.WithValue(baseapp.TxHashKey, "000")
+	sdkResult = handler(ctx, issueMsg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+
+	sdkResult = handler(ctx, mintMsg)
+	require.Equal(t, false, sdkResult.Code.IsOK())
+	require.Contains(t, sdkResult.Log, fmt.Sprintf("mint amount is too large, the max total supply is %d", types.TinyRangeType.UpperBound()))
+
+	token, err := tokenMapper.GetToken(ctx, "NNB-000M")
+	require.NoError(t, err)
+	expectedToken := types.NewMiniToken("New BNB", "NNB", "NNB-000M", 1, 9000e8, acc.GetAddress(), true, "http://www.xyz.com/nnb.json")
+	require.Equal(t, expectedToken, token)
+
+	_, err = tokenMapper.GetToken(ctx, "NNB-000")
+	require.NotNil(t, err)
+	require.Contains(t, err.Error(), "token(NNB-000) not found")
+
+	sdkResult = handler(ctx, mintMsg)
+	require.Equal(t, false, sdkResult.Code.IsOK())
+	require.Contains(t, sdkResult.Log, "mint amount is too large")
+
+	validMintMsg := NewMintMsg(acc.GetAddress(), "NNB-000M", 1000e8)
+	sdkResult = handler(ctx, validMintMsg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+	token, err = tokenMapper.GetToken(ctx, "NNB-000M")
+	expectedToken = types.NewMiniToken("New BNB", "NNB", "NNB-000M", 1, 10000e8, acc.GetAddress(), true, "http://www.xyz.com/nnb.json")
+	require.Equal(t, expectedToken, token)
+
+	_, acc2 := testutils.NewAccount(ctx, accountKeeper, 100e8)
+	invalidMintMsg := NewMintMsg(acc2.GetAddress(), "NNB-000M", 100e8)
+	sdkResult = handler(ctx, invalidMintMsg)
+	require.Contains(t, sdkResult.Log, "only the owner can mint token NNB")
+
+	// issue a non-mintable token
+	issueMsg = NewIssueTinyMsg(acc.GetAddress(), "New BNB2", "NNB2", 9000e8, false, "http://www.xyz.com/nnb.json")
+	ctx = ctx.WithValue(baseapp.TxHashKey, "000")
+	sdkResult = handler(ctx, issueMsg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+
+	mintMsg = NewMintMsg(acc.GetAddress(), "NNB2-000M", 1000e8)
+	sdkResult = handler(ctx, mintMsg)
+	require.Contains(t, sdkResult.Log, "token(NNB2-000M) cannot be minted")
+
+	// mint native token
+	invalidMintMsg = NewMintMsg(acc.GetAddress(), "BNB", 10000e8)
+	require.Contains(t, invalidMintMsg.ValidateBasic().Error(), "cannot mint native token")
+}
diff --git a/plugins/tokens/issue/handler_test.go b/plugins/tokens/issue/handler_test.go
index e5a4cfc7b..7ef4277c2 100644
--- a/plugins/tokens/issue/handler_test.go
+++ b/plugins/tokens/issue/handler_test.go
@@ -15,18 +15,21 @@ import (
 
 	"github.com/binance-chain/node/common/testutils"
 	"github.com/binance-chain/node/common/types"
+	"github.com/binance-chain/node/common/upgrade"
 	"github.com/binance-chain/node/plugins/tokens/store"
 	"github.com/binance-chain/node/wire"
 )
 
 func setup() (sdk.Context, sdk.Handler, auth.AccountKeeper, store.Mapper) {
-	ms, capKey1, capKey2 := testutils.SetupMultiStoreForUnitTest()
+	ms, capKey1, capKey2, _ := testutils.SetupThreeMultiStoreForUnitTest()
 	cdc := wire.NewCodec()
+	cdc.RegisterInterface((*types.IToken)(nil), nil)
+	cdc.RegisterConcrete(&types.Token{}, "bnbchain/Token", nil)
+	cdc.RegisterConcrete(&types.MiniToken{}, "bnbchain/MiniToken", nil)
 	tokenMapper := store.NewMapper(cdc, capKey1)
 	accountKeeper := auth.NewAccountKeeper(cdc, capKey2, auth.ProtoBaseAccount)
 	bankKeeper := bank.NewBaseKeeper(accountKeeper)
 	handler := NewHandler(tokenMapper, bankKeeper)
-
 	accountStore := ms.GetKVStore(capKey2)
 	accountStoreCache := auth.NewAccountStoreCache(cdc, accountStore, 10)
 	ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid", Height: 1},
@@ -35,6 +38,14 @@ func setup() (sdk.Context, sdk.Handler, auth.AccountKeeper, store.Mapper) {
 	return ctx, handler, accountKeeper, tokenMapper
 }
 
+func setChainVersion() {
+	upgrade.Mgr.AddUpgradeHeight(upgrade.BEP8, -1)
+}
+
+func resetChainVersion() {
+	upgrade.Mgr.Config.HeightMap = nil
+}
+
 func TestHandleIssueToken(t *testing.T) {
 	ctx, handler, accountKeeper, tokenMapper := setup()
 	_, acc := testutils.NewAccount(ctx, accountKeeper, 100e8)
@@ -46,7 +57,7 @@ func TestHandleIssueToken(t *testing.T) {
 	token, err := tokenMapper.GetToken(ctx, "NNB-000")
 	require.NoError(t, err)
 	expectedToken, err := types.NewToken("New BNB", "NNB-000", 100000e8, acc.GetAddress(), false)
-	require.Equal(t, *expectedToken, token)
+	require.Equal(t, expectedToken, token)
 
 	sdkResult = handler(ctx, msg)
 	require.Contains(t, sdkResult.Log, "symbol(NNB) already exists")
@@ -70,7 +81,7 @@ func TestHandleMintToken(t *testing.T) {
 	token, err := tokenMapper.GetToken(ctx, "NNB-000")
 	require.NoError(t, err)
 	expectedToken, err := types.NewToken("New BNB", "NNB-000", 110000e8, acc.GetAddress(), true)
-	require.Equal(t, *expectedToken, token)
+	require.Equal(t, expectedToken, token)
 
 	invalidMintMsg := NewMintMsg(acc.GetAddress(), "NNB-000", types.TokenMaxTotalSupply)
 	sdkResult = handler(ctx, invalidMintMsg)
diff --git a/plugins/tokens/issue/msg.go b/plugins/tokens/issue/msg.go
index 9a454c66f..412cd17f5 100644
--- a/plugins/tokens/issue/msg.go
+++ b/plugins/tokens/issue/msg.go
@@ -7,6 +7,7 @@ import (
 	sdk "github.com/cosmos/cosmos-sdk/types"
 
 	"github.com/binance-chain/node/common/types"
+	"github.com/binance-chain/node/common/upgrade"
 )
 
 // TODO: "route expressions can only contain alphanumeric characters", we need to change the cosmos sdk to support slash
@@ -46,7 +47,7 @@ func (msg IssueMsg) ValidateBasic() sdk.Error {
 		return sdk.ErrInvalidAddress("sender address cannot be empty")
 	}
 
-	if err := types.ValidateIssueMsgTokenSymbol(msg.Symbol); err != nil {
+	if err := types.ValidateIssueSymbol(msg.Symbol); err != nil {
 		return sdk.ErrInvalidCoins(err.Error())
 	}
 
@@ -96,7 +97,15 @@ func (msg MintMsg) ValidateBasic() sdk.Error {
 		return sdk.ErrInvalidAddress("sender address cannot be empty")
 	}
 
-	if err := types.ValidateMapperTokenSymbol(msg.Symbol); err != nil {
+	if sdk.IsUpgrade(upgrade.BEP8) && types.IsValidMiniTokenSymbol(msg.Symbol) {
+		if msg.Amount < types.MiniTokenMinExecutionAmount {
+			return sdk.ErrInvalidCoins(fmt.Sprintf("mint amount should be no less than %d", types.MiniTokenMinExecutionAmount))
+		}
+		return nil
+	}
+
+	// if BEP8 not upgraded, we rely on `ValidateTokenSymbol` rejecting the MiniToken.
+	if err := types.ValidateTokenSymbol(msg.Symbol); err != nil {
 		return sdk.ErrInvalidCoins(err.Error())
 	}
 
diff --git a/plugins/tokens/issue/msg_mini.go b/plugins/tokens/issue/msg_mini.go
new file mode 100644
index 000000000..ba322b0f6
--- /dev/null
+++ b/plugins/tokens/issue/msg_mini.go
@@ -0,0 +1,80 @@
+package issue
+
+import (
+	"encoding/json"
+	"fmt"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+
+	"github.com/binance-chain/node/common/types"
+)
+
+// TODO: "route expressions can only contain alphanumeric characters", we need to change the cosmos sdk to support slash
+// const Route  = "tokens/issue"
+const (
+	IssueMiniMsgType = "miniIssueMsg" //For max total supply in range 2
+)
+
+var _ sdk.Msg = IssueMiniMsg{}
+
+type IssueMiniMsg struct {
+	From        sdk.AccAddress `json:"from"`
+	Name        string         `json:"name"`
+	Symbol      string         `json:"symbol"`
+	TotalSupply int64          `json:"total_supply"`
+	Mintable    bool           `json:"mintable"`
+	TokenURI    string         `json:"token_uri"`
+}
+
+func NewIssueMiniMsg(from sdk.AccAddress, name, symbol string, supply int64, mintable bool, tokenURI string) IssueMiniMsg {
+	return IssueMiniMsg{
+		From:        from,
+		Name:        name,
+		Symbol:      symbol,
+		TotalSupply: supply,
+		Mintable:    mintable,
+		TokenURI:    tokenURI,
+	}
+}
+
+// ValidateBasic does a simple validation check that
+// doesn't require access to any other information.
+func (msg IssueMiniMsg) ValidateBasic() sdk.Error {
+	if msg.From == nil {
+		return sdk.ErrInvalidAddress("sender address cannot be empty")
+	}
+
+	if err := types.ValidateIssueMiniSymbol(msg.Symbol); err != nil {
+		return sdk.ErrInvalidCoins(err.Error())
+	}
+
+	if len(msg.Name) == 0 || len(msg.Name) > maxTokenNameLength {
+		return sdk.ErrInvalidCoins(fmt.Sprintf("token name should have 1 ~ %v characters", maxTokenNameLength))
+	}
+
+	if len(msg.TokenURI) > types.MaxTokenURILength {
+		return sdk.ErrInvalidCoins(fmt.Sprintf("token seturi should not exceed %v characters", types.MaxTokenURILength))
+	}
+
+	if msg.TotalSupply < types.MiniTokenMinExecutionAmount || msg.TotalSupply > types.MiniRangeType.UpperBound() {
+		return sdk.ErrInvalidCoins(fmt.Sprintf("total supply should be between %d and %d", types.MiniTokenMinExecutionAmount, types.MiniRangeType.UpperBound()))
+	}
+
+	return nil
+}
+
+// Implements IssueMiniMsg.
+func (msg IssueMiniMsg) Route() string                { return Route }
+func (msg IssueMiniMsg) Type() string                 { return IssueMiniMsgType }
+func (msg IssueMiniMsg) String() string               { return fmt.Sprintf("IssueMiniMsg{%#v}", msg) }
+func (msg IssueMiniMsg) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.From} }
+func (msg IssueMiniMsg) GetSignBytes() []byte {
+	b, err := json.Marshal(msg) // XXX: ensure some canonical form
+	if err != nil {
+		panic(err)
+	}
+	return b
+}
+func (msg IssueMiniMsg) GetInvolvedAddresses() []sdk.AccAddress {
+	return msg.GetSigners()
+}
diff --git a/plugins/tokens/issue/msg_tiny.go b/plugins/tokens/issue/msg_tiny.go
new file mode 100644
index 000000000..eda4ac925
--- /dev/null
+++ b/plugins/tokens/issue/msg_tiny.go
@@ -0,0 +1,80 @@
+package issue
+
+import (
+	"encoding/json"
+	"fmt"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+
+	"github.com/binance-chain/node/common/types"
+)
+
+// TODO: "route expressions can only contain alphanumeric characters", we need to change the cosmos sdk to support slash
+// const Route  = "tokens/issue"
+const (
+	IssueTinyMsgType = "tinyIssueMsg"
+)
+
+var _ sdk.Msg = IssueTinyMsg{}
+
+type IssueTinyMsg struct {
+	From        sdk.AccAddress `json:"from"`
+	Name        string         `json:"name"`
+	Symbol      string         `json:"symbol"`
+	TotalSupply int64          `json:"total_supply"`
+	Mintable    bool           `json:"mintable"`
+	TokenURI    string         `json:"token_uri"`
+}
+
+func NewIssueTinyMsg(from sdk.AccAddress, name, symbol string, supply int64, mintable bool, tokenURI string) IssueTinyMsg {
+	return IssueTinyMsg{
+		From:        from,
+		Name:        name,
+		Symbol:      symbol,
+		TotalSupply: supply,
+		Mintable:    mintable,
+		TokenURI:    tokenURI,
+	}
+}
+
+// ValidateBasic does a simple validation check that
+// doesn't require access to any other information.
+func (msg IssueTinyMsg) ValidateBasic() sdk.Error {
+	if msg.From == nil {
+		return sdk.ErrInvalidAddress("sender address cannot be empty")
+	}
+
+	if err := types.ValidateIssueMiniSymbol(msg.Symbol); err != nil {
+		return sdk.ErrInvalidCoins(err.Error())
+	}
+
+	if len(msg.Name) == 0 || len(msg.Name) > maxTokenNameLength {
+		return sdk.ErrInvalidCoins(fmt.Sprintf("token name should have 1 ~ %v characters", maxTokenNameLength))
+	}
+
+	if len(msg.TokenURI) > types.MaxTokenURILength {
+		return sdk.ErrInvalidCoins(fmt.Sprintf("token seturi should not exceed %v characters", types.MaxTokenURILength))
+	}
+
+	if msg.TotalSupply < types.MiniTokenMinExecutionAmount || msg.TotalSupply > types.TinyRangeType.UpperBound() {
+		return sdk.ErrInvalidCoins(fmt.Sprintf("total supply should be between %d and %d", types.MiniTokenMinExecutionAmount, types.TinyRangeType.UpperBound()))
+	}
+
+	return nil
+}
+
+// Implements IssueTinyMsg.
+func (msg IssueTinyMsg) Route() string                { return Route }
+func (msg IssueTinyMsg) Type() string                 { return IssueTinyMsgType }
+func (msg IssueTinyMsg) String() string               { return fmt.Sprintf("IssueTinyMsg{%#v}", msg) }
+func (msg IssueTinyMsg) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.From} }
+func (msg IssueTinyMsg) GetSignBytes() []byte {
+	b, err := json.Marshal(msg) // XXX: ensure some canonical form
+	if err != nil {
+		panic(err)
+	}
+	return b
+}
+func (msg IssueTinyMsg) GetInvolvedAddresses() []sdk.AccAddress {
+	return msg.GetSigners()
+}
diff --git a/plugins/tokens/plugin.go b/plugins/tokens/plugin.go
index b41bc6b32..c54c52bf9 100644
--- a/plugins/tokens/plugin.go
+++ b/plugins/tokens/plugin.go
@@ -15,6 +15,7 @@ import (
 )
 
 const abciQueryPrefix = "tokens"
+const miniAbciQueryPrefix = "mini-tokens"
 
 // InitPlugin initializes the plugin.
 func InitPlugin(
@@ -26,12 +27,14 @@ func InitPlugin(
 	}
 
 	// add abci handlers
-	handler := createQueryHandler(mapper)
-	appp.RegisterQueryHandler(abciQueryPrefix, handler)
+	tokenHandler := createQueryHandler(mapper, abciQueryPrefix)
+	miniTokenHandler := createQueryHandler(mapper, miniAbciQueryPrefix)
+	appp.RegisterQueryHandler(abciQueryPrefix, tokenHandler)
+	appp.RegisterQueryHandler(miniAbciQueryPrefix, miniTokenHandler)
 }
 
-func createQueryHandler(mapper Mapper) app.AbciQueryHandler {
-	return createAbciQueryHandler(mapper)
+func createQueryHandler(mapper Mapper, queryPrefix string) app.AbciQueryHandler {
+	return createAbciQueryHandler(mapper, queryPrefix)
 }
 
 // EndBreatheBlock processes the breathe block lifecycle event.
diff --git a/plugins/tokens/route.go b/plugins/tokens/route.go
index a65c3ac70..11ae1f784 100644
--- a/plugins/tokens/route.go
+++ b/plugins/tokens/route.go
@@ -8,6 +8,7 @@ import (
 	"github.com/binance-chain/node/plugins/tokens/burn"
 	"github.com/binance-chain/node/plugins/tokens/freeze"
 	"github.com/binance-chain/node/plugins/tokens/issue"
+	"github.com/binance-chain/node/plugins/tokens/seturi"
 	"github.com/binance-chain/node/plugins/tokens/store"
 	"github.com/binance-chain/node/plugins/tokens/swap"
 	"github.com/binance-chain/node/plugins/tokens/timelock"
@@ -21,5 +22,6 @@ func Routes(tokenMapper store.Mapper, accKeeper auth.AccountKeeper, keeper bank.
 	routes[freeze.FreezeRoute] = freeze.NewHandler(tokenMapper, accKeeper, keeper)
 	routes[timelock.MsgRoute] = timelock.NewHandler(timeLockKeeper)
 	routes[swap.AtomicSwapRoute] = swap.NewHandler(swapKeeper)
+	routes[seturi.SetURIRoute] = seturi.NewHandler(tokenMapper)
 	return routes
 }
diff --git a/plugins/tokens/seturi/handler.go b/plugins/tokens/seturi/handler.go
new file mode 100644
index 000000000..fd2a599cc
--- /dev/null
+++ b/plugins/tokens/seturi/handler.go
@@ -0,0 +1,60 @@
+package seturi
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+
+	"github.com/binance-chain/node/common/log"
+	common "github.com/binance-chain/node/common/types"
+	"github.com/binance-chain/node/plugins/tokens/store"
+)
+
+func NewHandler(tokenMapper store.Mapper) sdk.Handler {
+	return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
+		switch msg := msg.(type) {
+		case SetURIMsg:
+			return handleSetURI(ctx, tokenMapper, msg)
+		default:
+			errMsg := "Unrecognized msg type: " + reflect.TypeOf(msg).Name()
+			return sdk.ErrUnknownRequest(errMsg).Result()
+		}
+	}
+}
+
+func handleSetURI(ctx sdk.Context, tokenMapper store.Mapper, msg SetURIMsg) sdk.Result {
+	symbol := strings.ToUpper(msg.Symbol)
+	logger := log.With("module", "mini-token", "symbol", symbol, "tokenURI", msg.TokenURI, "from", msg.From)
+
+	errLogMsg := "set token URI failed"
+	token, err := tokenMapper.GetToken(ctx, symbol)
+	if err != nil {
+		logger.Info(errLogMsg, "reason", "symbol not exist")
+		return sdk.ErrInvalidCoins(fmt.Sprintf("symbol(%s) does not exist", msg.Symbol)).Result()
+	}
+
+	if !token.IsOwner(msg.From) {
+		logger.Info(errLogMsg, "reason", "not the token owner")
+		return sdk.ErrUnauthorized(fmt.Sprintf("only the owner can mint token %s", msg.Symbol)).Result()
+	}
+
+	if len(msg.TokenURI) < 1 {
+		return sdk.ErrInvalidCoins(fmt.Sprintf("token uri should not be empty")).Result()
+	}
+
+	if len(msg.TokenURI) > common.MaxTokenURILength {
+		return sdk.ErrInvalidCoins(fmt.Sprintf("token uri should not exceed %v characters", common.MaxTokenURILength)).Result()
+	}
+	err = tokenMapper.UpdateMiniTokenURI(ctx, symbol, msg.TokenURI)
+	if err != nil {
+		logger.Error(errLogMsg, "reason", "update token uri failed: "+err.Error())
+		return sdk.ErrInternal(fmt.Sprintf("update token uri failed")).Result()
+	}
+
+	logger.Info("finished update token uri")
+	return sdk.Result{
+		Data: []byte(msg.TokenURI),
+	}
+}
diff --git a/plugins/tokens/seturi/handler_test.go b/plugins/tokens/seturi/handler_test.go
new file mode 100644
index 000000000..b008dd540
--- /dev/null
+++ b/plugins/tokens/seturi/handler_test.go
@@ -0,0 +1,91 @@
+package seturi
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/cosmos/cosmos-sdk/baseapp"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/cosmos/cosmos-sdk/x/auth"
+	"github.com/cosmos/cosmos-sdk/x/bank"
+
+	abci "github.com/tendermint/tendermint/abci/types"
+	"github.com/tendermint/tendermint/libs/log"
+
+	"github.com/binance-chain/node/common/testutils"
+	"github.com/binance-chain/node/common/types"
+	"github.com/binance-chain/node/common/upgrade"
+	"github.com/binance-chain/node/plugins/tokens/issue"
+	"github.com/binance-chain/node/plugins/tokens/store"
+	"github.com/binance-chain/node/wire"
+)
+
+func setup() (sdk.Context, sdk.Handler, sdk.Handler, auth.AccountKeeper, store.Mapper) {
+	ms, capKey1, capKey2 := testutils.SetupMultiStoreForUnitTest()
+	cdc := wire.NewCodec()
+	cdc.RegisterInterface((*types.IToken)(nil), nil)
+	cdc.RegisterConcrete(&types.Token{}, "bnbchain/Token", nil)
+	cdc.RegisterConcrete(&types.MiniToken{}, "bnbchain/MiniToken", nil)
+	tokenMapper := store.NewMapper(cdc, capKey1)
+	accountKeeper := auth.NewAccountKeeper(cdc, capKey2, auth.ProtoBaseAccount)
+	handler := NewHandler(tokenMapper)
+
+	bankKeeper := bank.NewBaseKeeper(accountKeeper)
+	miniTokenHandler := issue.NewHandler(tokenMapper, bankKeeper)
+
+	accountStore := ms.GetKVStore(capKey2)
+	accountStoreCache := auth.NewAccountStoreCache(cdc, accountStore, 10)
+	ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid", Height: 1},
+		sdk.RunTxModeDeliver, log.NewNopLogger()).
+		WithAccountCache(auth.NewAccountCache(accountStoreCache))
+	return ctx, handler, miniTokenHandler, accountKeeper, tokenMapper
+}
+
+func setChainVersion() {
+	upgrade.Mgr.AddUpgradeHeight(upgrade.BEP8, -1)
+}
+
+func resetChainVersion() {
+	upgrade.Mgr.Config.HeightMap = nil
+}
+
+func TestHandleSetURI(t *testing.T) {
+	setChainVersion()
+	defer resetChainVersion()
+	ctx, handler, miniIssueHandler, accountKeeper, tokenMapper := setup()
+	_, acc := testutils.NewAccount(ctx, accountKeeper, 100e8)
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "000")
+	msg := issue.NewIssueMiniMsg(acc.GetAddress(), "New BNB", "NNB", 10000e8, false, "http://www.xyz.com/nnb.json")
+	sdkResult := miniIssueHandler(ctx, msg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+
+	token, err := tokenMapper.GetToken(ctx, "NNB-000M")
+	require.NoError(t, err)
+	expectedToken := types.NewMiniToken("New BNB", "NNB", "NNB-000M", 2, 10000e8, acc.GetAddress(), false, "http://www.xyz.com/nnb.json")
+	require.Equal(t, expectedToken, token)
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "002")
+	setUriMsg := NewSetUriMsg(acc.GetAddress(), "NBB", "http://www.123.com/nnb_new.json")
+	sdkResult = handler(ctx, setUriMsg)
+	require.Equal(t, false, sdkResult.Code.IsOK())
+	require.Contains(t, sdkResult.Log, "symbol(NBB) does not exist")
+
+	ctx = ctx.WithValue(baseapp.TxHashKey, "002")
+	setUriMsg = NewSetUriMsg(acc.GetAddress(), "NNB-000M", "http://www.123.com/nnb_new.json")
+	sdkResult = handler(ctx, setUriMsg)
+	require.Equal(t, true, sdkResult.Code.IsOK())
+
+	token, err = tokenMapper.GetToken(ctx, "NNB-000M")
+	require.NoError(t, err)
+	expectedToken = types.NewMiniToken("New BNB", "NNB", "NNB-000M", 2, 10000e8, acc.GetAddress(), false, "http://www.123.com/nnb_new.json")
+	require.Equal(t, expectedToken, token)
+
+	_, acc2 := testutils.NewAccount(ctx, accountKeeper, 100e8)
+	ctx = ctx.WithValue(baseapp.TxHashKey, "002")
+	setUriMsg = NewSetUriMsg(acc2.GetAddress(), "NNB-000M", "http://www.124.com/nnb_new.json")
+	sdkResult = handler(ctx, setUriMsg)
+	require.Equal(t, false, sdkResult.Code.IsOK())
+	require.Contains(t, sdkResult.Log, "only the owner can mint token")
+}
diff --git a/plugins/tokens/seturi/msg.go b/plugins/tokens/seturi/msg.go
new file mode 100644
index 000000000..c3e161a3d
--- /dev/null
+++ b/plugins/tokens/seturi/msg.go
@@ -0,0 +1,56 @@
+package seturi
+
+import (
+	"encoding/json"
+	"fmt"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+
+	"github.com/binance-chain/node/common/types"
+)
+
+const SetURIRoute = "miniTokensSetURI"
+
+var _ sdk.Msg = SetURIMsg{}
+
+type SetURIMsg struct {
+	From     sdk.AccAddress `json:"from"`
+	Symbol   string         `json:"symbol"`
+	TokenURI string         `json:"token_uri"`
+}
+
+func NewSetUriMsg(from sdk.AccAddress, symbol string, tokenURI string) SetURIMsg {
+	return SetURIMsg{
+		From:     from,
+		Symbol:   symbol,
+		TokenURI: tokenURI,
+	}
+}
+
+func (msg SetURIMsg) ValidateBasic() sdk.Error {
+	if msg.From == nil || len(msg.From) == 0 {
+		return sdk.ErrInvalidAddress("sender address cannot be empty")
+	}
+
+	if err := types.ValidateMiniTokenSymbol(msg.Symbol); err != nil {
+		return sdk.ErrInvalidCoins(err.Error())
+	}
+
+	return nil
+}
+
+// Implements MintMsg.
+func (msg SetURIMsg) Route() string                { return SetURIRoute }
+func (msg SetURIMsg) Type() string                 { return SetURIRoute }
+func (msg SetURIMsg) String() string               { return fmt.Sprintf("SetURI{%#v}", msg) }
+func (msg SetURIMsg) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.From} }
+func (msg SetURIMsg) GetSignBytes() []byte {
+	b, err := json.Marshal(msg) // XXX: ensure some canonical form
+	if err != nil {
+		panic(err)
+	}
+	return b
+}
+func (msg SetURIMsg) GetInvolvedAddresses() []sdk.AccAddress {
+	return msg.GetSigners()
+}
diff --git a/plugins/tokens/store/mapper.go b/plugins/tokens/store/mapper.go
index d1c27ffcd..932184cb0 100644
--- a/plugins/tokens/store/mapper.go
+++ b/plugins/tokens/store/mapper.go
@@ -1,6 +1,7 @@
 package store
 
 import (
+	"bytes"
 	"errors"
 	"fmt"
 	"strings"
@@ -15,6 +16,10 @@ import (
 )
 
 type Tokens []types.Token
+type MiniTokens []types.MiniToken
+type ITokens []types.IToken
+
+const miniTokenKeyPrefix = "mini:"
 
 func (t Tokens) GetSymbols() *[]string {
 	var symbols []string
@@ -25,13 +30,15 @@ func (t Tokens) GetSymbols() *[]string {
 }
 
 type Mapper interface {
-	NewToken(ctx sdk.Context, token types.Token) error
-	Exists(ctx sdk.Context, symbol string) bool
+	NewToken(ctx sdk.Context, token types.IToken) error
+	ExistsBEP2(ctx sdk.Context, symbol string) bool
+	ExistsMini(ctx sdk.Context, symbol string) bool
 	ExistsCC(ctx context.CLIContext, symbol string) bool
-	GetTokenList(ctx sdk.Context, showZeroSupplyTokens bool) Tokens
-	GetToken(ctx sdk.Context, symbol string) (types.Token, error)
+	GetTokenList(ctx sdk.Context, showZeroSupplyTokens bool, isMini bool) ITokens
+	GetToken(ctx sdk.Context, symbol string) (types.IToken, error)
 	// we do not provide the updateToken method
 	UpdateTotalSupply(ctx sdk.Context, symbol string, supply int64) error
+	UpdateMiniTokenURI(ctx sdk.Context, symbol string, uri string) error
 }
 
 var _ Mapper = mapper{}
@@ -48,38 +55,35 @@ func NewMapper(cdc *wire.Codec, key sdk.StoreKey) mapper {
 	}
 }
 
-func (m mapper) GetToken(ctx sdk.Context, symbol string) (types.Token, error) {
+func (m mapper) GetToken(ctx sdk.Context, symbol string) (types.IToken, error) {
 	store := ctx.KVStore(m.key)
-	key := []byte(strings.ToUpper(symbol))
+	var key []byte
+	if types.IsMiniTokenSymbol(symbol) {
+		key = m.calcMiniTokenKey(strings.ToUpper(symbol))
+	} else {
+		key = []byte(strings.ToUpper(symbol))
+	}
 
 	bz := store.Get(key)
 	if bz != nil {
 		return m.decodeToken(bz), nil
 	}
 
-	return types.Token{}, fmt.Errorf("token(%v) not found", symbol)
-}
-
-func (m mapper) GetTokenCC(ctx context.CLIContext, symbol string) (types.Token, error) {
-	key := []byte(strings.ToUpper(symbol))
-	bz, err := ctx.QueryStore(key, common.TokenStoreName)
-	if err != nil {
-		return types.Token{}, err
-	}
-	if bz != nil {
-		return m.decodeToken(bz), nil
-	}
-	return types.Token{}, fmt.Errorf("token(%v) not found", symbol)
+	return nil, fmt.Errorf("token(%v) not found", symbol)
 }
 
-func (m mapper) GetTokenList(ctx sdk.Context, showZeroSupplyTokens bool) Tokens {
-	var res Tokens
+func (m mapper) GetTokenList(ctx sdk.Context, showZeroSupplyTokens bool, isMini bool) ITokens {
+	var res ITokens
 	store := ctx.KVStore(m.key)
 	iter := store.Iterator(nil, nil)
 	defer iter.Close()
 	for ; iter.Valid(); iter.Next() {
+		isValid := isMini == bytes.HasPrefix(iter.Key(), []byte(miniTokenKeyPrefix))
+		if !isValid {
+			continue
+		}
 		token := m.decodeToken(iter.Value())
-		if !showZeroSupplyTokens && token.TotalSupply.ToInt64() == 0 {
+		if !showZeroSupplyTokens && token.GetTotalSupply().ToInt64() == 0 {
 			continue
 		}
 		res = append(res, token)
@@ -87,14 +91,32 @@ func (m mapper) GetTokenList(ctx sdk.Context, showZeroSupplyTokens bool) Tokens
 	return res
 }
 
-func (m mapper) Exists(ctx sdk.Context, symbol string) bool {
+func (m mapper) ExistsBEP2(ctx sdk.Context, symbol string) bool {
+	return m.exists(ctx, symbol, false)
+}
+
+func (m mapper) ExistsMini(ctx sdk.Context, symbol string) bool {
+	return m.exists(ctx, symbol, true)
+}
+
+func (m mapper) exists(ctx sdk.Context, symbol string, isMini bool) bool {
 	store := ctx.KVStore(m.key)
-	key := []byte(strings.ToUpper(symbol))
+	var key []byte
+	if isMini {
+		key = m.calcMiniTokenKey(strings.ToUpper(symbol))
+	} else {
+		key = []byte(strings.ToUpper(symbol))
+	}
 	return store.Has(key)
 }
 
 func (m mapper) ExistsCC(ctx context.CLIContext, symbol string) bool {
-	key := []byte(strings.ToUpper(symbol))
+	var key []byte
+	if types.IsMiniTokenSymbol(symbol) {
+		key = m.calcMiniTokenKey(strings.ToUpper(symbol))
+	} else {
+		key = []byte(strings.ToUpper(symbol))
+	}
 	bz, err := ctx.QueryStore(key, common.TokenStoreName)
 	if err != nil {
 		return false
@@ -105,12 +127,21 @@ func (m mapper) ExistsCC(ctx context.CLIContext, symbol string) bool {
 	return false
 }
 
-func (m mapper) NewToken(ctx sdk.Context, token types.Token) error {
-	symbol := token.Symbol
-	if err := types.ValidateToken(token); err != nil {
-		return err
+func (m mapper) NewToken(ctx sdk.Context, token types.IToken) error {
+	symbol := token.GetSymbol()
+	var key []byte
+	if types.IsMiniTokenSymbol(symbol) {
+		key = m.calcMiniTokenKey(strings.ToUpper(symbol))
+	} else {
+		if err := types.ValidateTokenSymbol(token.GetSymbol()); err != nil {
+			return err
+		}
+		if err := types.ValidateIssueSymbol(token.GetOrigSymbol()); err != nil {
+			return err
+		}
+		key = []byte(strings.ToUpper(symbol))
 	}
-	key := []byte(strings.ToUpper(symbol))
+
 	store := ctx.KVStore(m.key)
 	value := m.encodeToken(token)
 	store.Set(key, value)
@@ -121,8 +152,12 @@ func (m mapper) UpdateTotalSupply(ctx sdk.Context, symbol string, supply int64)
 	if len(symbol) == 0 {
 		return errors.New("symbol cannot be empty")
 	}
-
-	key := []byte(strings.ToUpper(symbol))
+	var key []byte
+	if types.IsMiniTokenSymbol(symbol) {
+		key = m.calcMiniTokenKey(strings.ToUpper(symbol))
+	} else {
+		key = []byte(strings.ToUpper(symbol))
+	}
 	store := ctx.KVStore(m.key)
 	bz := store.Get(key)
 	if bz == nil {
@@ -131,14 +166,14 @@ func (m mapper) UpdateTotalSupply(ctx sdk.Context, symbol string, supply int64)
 
 	toBeUpdated := m.decodeToken(bz)
 
-	if toBeUpdated.TotalSupply.ToInt64() != supply {
-		toBeUpdated.TotalSupply = utils.Fixed8(supply)
+	if toBeUpdated.GetTotalSupply().ToInt64() != supply {
+		toBeUpdated.SetTotalSupply(utils.Fixed8(supply))
 		store.Set(key, m.encodeToken(toBeUpdated))
 	}
 	return nil
 }
 
-func (m mapper) encodeToken(token types.Token) []byte {
+func (m mapper) encodeToken(token types.IToken) []byte {
 	bz, err := m.cdc.MarshalBinaryBare(token)
 	if err != nil {
 		panic(err)
@@ -146,10 +181,11 @@ func (m mapper) encodeToken(token types.Token) []byte {
 	return bz
 }
 
-func (m mapper) decodeToken(bz []byte) (token types.Token) {
+func (m mapper) decodeToken(bz []byte) types.IToken {
+	var token types.IToken
 	err := m.cdc.UnmarshalBinaryBare(bz, &token)
 	if err != nil {
 		panic(err)
 	}
-	return
+	return token
 }
diff --git a/plugins/tokens/store/mapper_mini.go b/plugins/tokens/store/mapper_mini.go
new file mode 100644
index 000000000..58a6dfe93
--- /dev/null
+++ b/plugins/tokens/store/mapper_mini.go
@@ -0,0 +1,52 @@
+package store
+
+import (
+	"bytes"
+	"errors"
+	"strings"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+
+	"github.com/binance-chain/node/common/types"
+)
+
+func (m mapper) UpdateMiniTokenURI(ctx sdk.Context, symbol string, uri string) error {
+	if len(symbol) == 0 {
+		return errors.New("symbol cannot be empty")
+	}
+
+	if len(uri) == 0 {
+		return errors.New("uri cannot be empty")
+	}
+
+	if len(uri) > 2048 {
+		return errors.New("uri length cannot be larger than 2048")
+	}
+
+	key := m.calcMiniTokenKey(strings.ToUpper(symbol))
+	store := ctx.KVStore(m.key)
+	bz := store.Get(key)
+	if bz == nil {
+		return errors.New("token does not exist")
+	}
+
+	decodedToken := m.decodeToken(bz)
+
+	toBeUpdated, ok := decodedToken.(*types.MiniToken)
+	if !ok {
+		return errors.New("token cannot be converted to MiniToken")
+	}
+
+	if toBeUpdated.TokenURI != uri {
+		toBeUpdated.TokenURI = uri
+		store.Set(key, m.encodeToken(toBeUpdated))
+	}
+	return nil
+}
+
+func (m mapper) calcMiniTokenKey(symbol string) []byte {
+	var buf bytes.Buffer
+	buf.WriteString(miniTokenKeyPrefix)
+	buf.WriteString(symbol)
+	return buf.Bytes()
+}
diff --git a/plugins/tokens/swap/handler_test.go b/plugins/tokens/swap/handler_test.go
index ff23198cc..e40159b3e 100644
--- a/plugins/tokens/swap/handler_test.go
+++ b/plugins/tokens/swap/handler_test.go
@@ -145,7 +145,7 @@ func TestHandleCreateAndClaimSwapForSingleChain(t *testing.T) {
 	_, acc2 := testutils.NewAccount(ctx, accKeeper, 10000e8)
 
 	acc2Coins := acc2.GetCoins()
-	acc2Coins = acc2Coins.Plus(sdk.Coins{sdk.Coin{"ABC", 1000000000000}})
+	acc2Coins = acc2Coins.Plus(sdk.Coins{sdk.Coin{"ABC-123", 1000000000000}})
 	_ = acc2.SetCoins(acc2Coins)
 	accKeeper.SetAccount(ctx, acc2)
 
@@ -169,7 +169,7 @@ func TestHandleCreateAndClaimSwapForSingleChain(t *testing.T) {
 	swapIDStr := strings.Replace(result.Log, "swapID: ", "", -1)
 	swapID, _ := hex.DecodeString(swapIDStr)
 
-	amountABC := sdk.Coins{sdk.Coin{"ABC", 100000000}}
+	amountABC := sdk.Coins{sdk.Coin{"ABC-123", 100000000}}
 	expectedIncome = "10000:BNB"
 	msg = NewDepositHTLTMsg(acc2.GetAddress(), amountABC, swapID)
 
@@ -205,12 +205,12 @@ func TestHandleCreateAndClaimSwapForSingleChain(t *testing.T) {
 	require.Equal(t, Completed, swap.Status)
 
 	acc1Acc := accKeeper.GetAccount(ctx, acc1.GetAddress())
-	require.Equal(t, amountABC[0].Amount, acc1Acc.GetCoins().AmountOf("ABC"))
+	require.Equal(t, amountABC[0].Amount, acc1Acc.GetCoins().AmountOf("ABC-123"))
 	require.Equal(t, amountBNB[0].Amount, acc1OrignalCoins.AmountOf("BNB")-acc1Acc.GetCoins().AmountOf("BNB"))
 
 	acc2Acc := accKeeper.GetAccount(ctx, acc2.GetAddress())
 	require.Equal(t, amountBNB[0].Amount, acc2Acc.GetCoins().AmountOf("BNB")-acc2OrignalCoins.AmountOf("BNB"))
-	require.Equal(t, amountABC[0].Amount, acc2OrignalCoins.AmountOf("ABC")-acc2Acc.GetCoins().AmountOf("ABC"))
+	require.Equal(t, amountABC[0].Amount, acc2OrignalCoins.AmountOf("ABC-123")-acc2Acc.GetCoins().AmountOf("ABC-123"))
 }
 
 func TestHandleCreateAndRefundSwapForSingleChain(t *testing.T) {
@@ -222,7 +222,7 @@ func TestHandleCreateAndRefundSwapForSingleChain(t *testing.T) {
 	_, acc2 := testutils.NewAccount(ctx, accKeeper, 10000e8)
 
 	acc2Coins := acc2.GetCoins()
-	acc2Coins = acc2Coins.Plus(sdk.Coins{sdk.Coin{"ABC", 1000000000000}})
+	acc2Coins = acc2Coins.Plus(sdk.Coins{sdk.Coin{"ABC-123", 1000000000000}})
 	_ = acc2.SetCoins(acc2Coins)
 	accKeeper.SetAccount(ctx, acc2)
 
@@ -246,7 +246,7 @@ func TestHandleCreateAndRefundSwapForSingleChain(t *testing.T) {
 	swapIDStr := strings.Replace(result.Log, "swapID: ", "", -1)
 	swapID, _ := hex.DecodeString(swapIDStr)
 
-	amountABC := sdk.Coins{sdk.Coin{"ABC", 100000000}}
+	amountABC := sdk.Coins{sdk.Coin{"ABC-123", 100000000}}
 	expectedIncome = "10000:BNB"
 	msg = NewDepositHTLTMsg(acc2.GetAddress(), amountABC, swapID)
 
diff --git a/plugins/tokens/swap/msg.go b/plugins/tokens/swap/msg.go
index d1706c96a..2fee4581e 100644
--- a/plugins/tokens/swap/msg.go
+++ b/plugins/tokens/swap/msg.go
@@ -5,6 +5,9 @@ import (
 	"fmt"
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
+
+	"github.com/binance-chain/node/common/types"
+	"github.com/binance-chain/node/common/upgrade"
 )
 
 const (
@@ -99,6 +102,13 @@ func (msg HTLTMsg) ValidateBasic() sdk.Error {
 	if msg.HeightSpan < MinimumHeightSpan || msg.HeightSpan > MaximumHeightSpan {
 		return ErrInvalidHeightSpan("The height span should be no less than 360 and no greater than 518400")
 	}
+
+	if sdk.IsUpgrade(upgrade.BEP8) {
+		symbolError := types.ValidateTokenSymbols(msg.Amount)
+		if symbolError != nil {
+			return sdk.ErrInvalidCoins(symbolError.Error())
+		}
+	}
 	return nil
 }
 
@@ -148,6 +158,12 @@ func (msg DepositHTLTMsg) ValidateBasic() sdk.Error {
 	if !msg.Amount.IsPositive() {
 		return sdk.ErrInvalidCoins("The swapped out coins must be positive")
 	}
+	if sdk.IsUpgrade(upgrade.BEP8) {
+		symbolError := types.ValidateTokenSymbols(msg.Amount)
+		if symbolError != nil {
+			return sdk.ErrInvalidCoins(symbolError.Error())
+		}
+	}
 	return nil
 }
 
diff --git a/plugins/tokens/timelock/msgs.go b/plugins/tokens/timelock/msgs.go
index 46fc46f8f..5a57e15e8 100644
--- a/plugins/tokens/timelock/msgs.go
+++ b/plugins/tokens/timelock/msgs.go
@@ -6,6 +6,8 @@ import (
 	"time"
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
+
+	"github.com/binance-chain/node/common/types"
 )
 
 const (
@@ -67,6 +69,13 @@ func (msg TimeLockMsg) ValidateBasic() sdk.Error {
 		return sdk.ErrInvalidCoins(msg.Amount.String())
 	}
 
+	if sdk.IsUpgrade(sdk.BEP8) {
+		symbolError := types.ValidateTokenSymbols(msg.Amount)
+		if symbolError != nil {
+			return sdk.ErrInvalidCoins(symbolError.Error())
+		}
+	}
+
 	return nil
 }
 
@@ -141,6 +150,12 @@ func (msg TimeRelockMsg) ValidateBasic() sdk.Error {
 		return ErrInvalidRelock(DefaultCodespace, fmt.Sprintf("nothing to update for time lock"))
 	}
 
+	if sdk.IsUpgrade(sdk.BEP8) {
+		symbolError := types.ValidateTokenSymbols(msg.Amount)
+		if symbolError != nil {
+			return sdk.ErrInvalidCoins(symbolError.Error())
+		}
+	}
 	return nil
 }
 
diff --git a/plugins/tokens/tokens.go b/plugins/tokens/tokens.go
index 29e93afe3..7a9547342 100644
--- a/plugins/tokens/tokens.go
+++ b/plugins/tokens/tokens.go
@@ -3,3 +3,4 @@ package tokens
 import "github.com/binance-chain/node/plugins/tokens/store"
 
 type Mapper = store.Mapper
+var NewMapper = store.NewMapper
diff --git a/plugins/tokens/wire.go b/plugins/tokens/wire.go
index af0df4740..f4ae7a1ff 100644
--- a/plugins/tokens/wire.go
+++ b/plugins/tokens/wire.go
@@ -4,6 +4,7 @@ import (
 	"github.com/binance-chain/node/plugins/tokens/burn"
 	"github.com/binance-chain/node/plugins/tokens/freeze"
 	"github.com/binance-chain/node/plugins/tokens/issue"
+	"github.com/binance-chain/node/plugins/tokens/seturi"
 	"github.com/binance-chain/node/plugins/tokens/swap"
 	"github.com/binance-chain/node/plugins/tokens/timelock"
 	"github.com/binance-chain/node/wire"
@@ -23,4 +24,7 @@ func RegisterWire(cdc *wire.Codec) {
 	cdc.RegisterConcrete(swap.DepositHTLTMsg{}, "tokens/DepositHTLTMsg", nil)
 	cdc.RegisterConcrete(swap.ClaimHTLTMsg{}, "tokens/ClaimHTLTMsg", nil)
 	cdc.RegisterConcrete(swap.RefundHTLTMsg{}, "tokens/RefundHTLTMsg", nil)
+	cdc.RegisterConcrete(issue.IssueMiniMsg{}, "tokens/IssueMiniMsg", nil)
+	cdc.RegisterConcrete(issue.IssueTinyMsg{}, "tokens/IssueTinyMsg", nil)
+	cdc.RegisterConcrete(seturi.SetURIMsg{}, "tokens/SetURIMsg", nil)
 }
diff --git a/version/version.go b/version/version.go
index 870d72b2a..c0c474da6 100644
--- a/version/version.go
+++ b/version/version.go
@@ -12,7 +12,7 @@ var (
 	Version string
 )
 
-const NodeVersion = "0.6.3-hf.2"
+const NodeVersion = "0.7"
 
 func init() {
 	Version = fmt.Sprintf("Binance Chain Release: %s;", NodeVersion)