feat_: accounts/saved addresses capacity related api endpoints added
Added endpoints: - `RemainingAccountCapacity` - `RemainingKeypairCapacity` - `RemainingWatchOnlyAccountCapacity` - `RmaininngCapacityForSavedAddresses`
This commit is contained in:
parent
39497c2bff
commit
2129a3f95c
|
@ -0,0 +1,8 @@
|
|||
package constants
|
||||
|
||||
const (
|
||||
MaxNumberOfAccounts = 20
|
||||
MaxNumberOfKeypairs = 5 // including the profile keypair
|
||||
MaxNumberOfWatchOnlyAccounts = 3
|
||||
MaxNumberOfSavedAddresses = 20
|
||||
)
|
|
@ -36,6 +36,14 @@ func (m *Messenger) GetSavedAddresses(ctx context.Context) ([]*wallet.SavedAddre
|
|||
return m.savedAddressesManager.GetSavedAddresses()
|
||||
}
|
||||
|
||||
func (m *Messenger) GetSavedAddressesPerMode(isTest bool) ([]*wallet.SavedAddress, error) {
|
||||
return m.savedAddressesManager.GetSavedAddressesPerMode(isTest)
|
||||
}
|
||||
|
||||
func (m *Messenger) RemainingCapacityForSavedAddresses(testnetMode bool) (int, error) {
|
||||
return m.savedAddressesManager.RemainingCapacityForSavedAddresses(testnetMode)
|
||||
}
|
||||
|
||||
func (m *Messenger) garbageCollectRemovedSavedAddresses() error {
|
||||
return m.savedAddressesManager.DeleteSoftRemovedSavedAddresses(uint64(time.Now().AddDate(0, 0, -30).Unix()))
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/constants"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
walletsettings "github.com/status-im/status-go/multiaccounts/settings_wallet"
|
||||
|
@ -781,3 +782,45 @@ func (m *Messenger) resolveAndSyncKeypairOrJustWalletAccount(keyUID string, addr
|
|||
chat.LastClockValue = clock
|
||||
return m.saveChat(chat)
|
||||
}
|
||||
|
||||
func (m *Messenger) RemainingAccountCapacity() (int, error) {
|
||||
accounts, err := m.settings.GetActiveAccounts()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
numOfAccountsWithoutChatAccount := 0
|
||||
if len(accounts) > 0 {
|
||||
numOfAccountsWithoutChatAccount = len(accounts) - 1
|
||||
}
|
||||
remainingCapacity := constants.MaxNumberOfAccounts - numOfAccountsWithoutChatAccount
|
||||
if remainingCapacity <= 0 {
|
||||
return 0, errors.New("no more accounts can be added")
|
||||
}
|
||||
|
||||
return remainingCapacity, nil
|
||||
}
|
||||
|
||||
func (m *Messenger) RemainingKeypairCapacity() (int, error) {
|
||||
keypairs, err := m.settings.GetActiveKeypairs()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
remainingCapacity := constants.MaxNumberOfKeypairs - len(keypairs)
|
||||
if remainingCapacity <= 0 {
|
||||
return 0, errors.New("no more keypairs can be added")
|
||||
}
|
||||
|
||||
return remainingCapacity, nil
|
||||
}
|
||||
|
||||
func (m *Messenger) RemainingWatchOnlyAccountCapacity() (int, error) {
|
||||
accounts, err := m.settings.GetActiveWatchOnlyAccounts()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
remainingCapacity := constants.MaxNumberOfWatchOnlyAccounts - len(accounts)
|
||||
if remainingCapacity <= 0 {
|
||||
return 0, errors.New("no more watch-only accounts can be added")
|
||||
}
|
||||
return remainingCapacity, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
package protocol
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/status-im/status-go/constants"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
func TestWalletSuite(t *testing.T) {
|
||||
suite.Run(t, new(WalletSuite))
|
||||
}
|
||||
|
||||
type WalletSuite struct {
|
||||
MessengerBaseTestSuite
|
||||
}
|
||||
|
||||
func (s *WalletSuite) TestRemainingCapacity() {
|
||||
profileKeypair := accounts.GetProfileKeypairForTest(true, true, true)
|
||||
seedImportedKeypair := accounts.GetSeedImportedKeypair1ForTest()
|
||||
woAccounts := accounts.GetWatchOnlyAccountsForTest()
|
||||
|
||||
// Empty DB
|
||||
capacity, err := s.m.RemainingAccountCapacity()
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(constants.MaxNumberOfAccounts, capacity)
|
||||
|
||||
capacity, err = s.m.RemainingKeypairCapacity()
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(constants.MaxNumberOfKeypairs, capacity)
|
||||
|
||||
capacity, err = s.m.RemainingWatchOnlyAccountCapacity()
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(constants.MaxNumberOfWatchOnlyAccounts, capacity)
|
||||
|
||||
// profile keypair with chat account, default wallet account and 2 more derived accounts added
|
||||
err = s.m.SaveOrUpdateKeypair(profileKeypair)
|
||||
s.Require().NoError(err)
|
||||
|
||||
capacity, err = s.m.RemainingAccountCapacity()
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(constants.MaxNumberOfAccounts-3, capacity)
|
||||
|
||||
capacity, err = s.m.RemainingKeypairCapacity()
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(constants.MaxNumberOfKeypairs-1, capacity)
|
||||
|
||||
capacity, err = s.m.RemainingWatchOnlyAccountCapacity()
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(constants.MaxNumberOfWatchOnlyAccounts, capacity)
|
||||
|
||||
// seed keypair with 2 derived accounts added
|
||||
err = s.m.SaveOrUpdateKeypair(seedImportedKeypair)
|
||||
s.Require().NoError(err)
|
||||
|
||||
capacity, err = s.m.RemainingAccountCapacity()
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(constants.MaxNumberOfAccounts-(3+2), capacity)
|
||||
|
||||
capacity, err = s.m.RemainingKeypairCapacity()
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(constants.MaxNumberOfKeypairs-(1+1), capacity)
|
||||
|
||||
capacity, err = s.m.RemainingWatchOnlyAccountCapacity()
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(constants.MaxNumberOfWatchOnlyAccounts, capacity)
|
||||
|
||||
// 1 Watch only accounts added
|
||||
err = s.m.SaveOrUpdateAccount(woAccounts[0])
|
||||
s.Require().NoError(err)
|
||||
|
||||
capacity, err = s.m.RemainingAccountCapacity()
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(constants.MaxNumberOfAccounts-(3+2+1), capacity)
|
||||
|
||||
capacity, err = s.m.RemainingKeypairCapacity()
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(constants.MaxNumberOfKeypairs-(1+1), capacity)
|
||||
|
||||
capacity, err = s.m.RemainingWatchOnlyAccountCapacity()
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(constants.MaxNumberOfWatchOnlyAccounts-1, capacity)
|
||||
|
||||
// try to add 3 more keypairs
|
||||
seedImportedKeypair2 := accounts.GetSeedImportedKeypair2ForTest()
|
||||
seedImportedKeypair2.KeyUID = "0000000000000000000000000000000000000000000000000000000000000091"
|
||||
seedImportedKeypair2.Accounts[0].Address = types.Address{0x91}
|
||||
seedImportedKeypair2.Accounts[0].KeyUID = seedImportedKeypair2.KeyUID
|
||||
seedImportedKeypair2.Accounts[1].Address = types.Address{0x92}
|
||||
seedImportedKeypair2.Accounts[1].KeyUID = seedImportedKeypair2.KeyUID
|
||||
|
||||
err = s.m.SaveOrUpdateKeypair(seedImportedKeypair2)
|
||||
s.Require().NoError(err)
|
||||
|
||||
seedImportedKeypair3 := accounts.GetSeedImportedKeypair2ForTest()
|
||||
seedImportedKeypair3.KeyUID = "0000000000000000000000000000000000000000000000000000000000000093"
|
||||
seedImportedKeypair3.Accounts[0].Address = types.Address{0x93}
|
||||
seedImportedKeypair3.Accounts[0].KeyUID = seedImportedKeypair3.KeyUID
|
||||
seedImportedKeypair3.Accounts[1].Address = types.Address{0x94}
|
||||
seedImportedKeypair3.Accounts[1].KeyUID = seedImportedKeypair3.KeyUID
|
||||
|
||||
err = s.m.SaveOrUpdateKeypair(seedImportedKeypair3)
|
||||
s.Require().NoError(err)
|
||||
|
||||
seedImportedKeypair4 := accounts.GetSeedImportedKeypair2ForTest()
|
||||
seedImportedKeypair4.KeyUID = "0000000000000000000000000000000000000000000000000000000000000095"
|
||||
seedImportedKeypair4.Accounts[0].Address = types.Address{0x95}
|
||||
seedImportedKeypair4.Accounts[0].KeyUID = seedImportedKeypair4.KeyUID
|
||||
seedImportedKeypair4.Accounts[1].Address = types.Address{0x96}
|
||||
seedImportedKeypair4.Accounts[1].KeyUID = seedImportedKeypair4.KeyUID
|
||||
|
||||
err = s.m.SaveOrUpdateKeypair(seedImportedKeypair4)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// check the capacity after adding 3 more keypairs
|
||||
capacity, err = s.m.RemainingAccountCapacity()
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(constants.MaxNumberOfAccounts-(3+2+1+3*2), capacity)
|
||||
|
||||
capacity, err = s.m.RemainingKeypairCapacity()
|
||||
s.Require().Error(err)
|
||||
s.Require().Equal("no more keypairs can be added", err.Error())
|
||||
s.Require().Equal(0, capacity)
|
||||
|
||||
capacity, err = s.m.RemainingWatchOnlyAccountCapacity()
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(constants.MaxNumberOfWatchOnlyAccounts-1, capacity)
|
||||
|
||||
// add 2 more watch only accounts
|
||||
err = s.m.SaveOrUpdateAccount(woAccounts[1])
|
||||
s.Require().NoError(err)
|
||||
err = s.m.SaveOrUpdateAccount(woAccounts[2])
|
||||
s.Require().NoError(err)
|
||||
|
||||
// check the capacity after adding 8 more watch only accounts
|
||||
capacity, err = s.m.RemainingAccountCapacity()
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(constants.MaxNumberOfAccounts-(3+2+3+3*2), capacity)
|
||||
|
||||
capacity, err = s.m.RemainingKeypairCapacity()
|
||||
s.Require().Error(err)
|
||||
s.Require().Equal("no more keypairs can be added", err.Error())
|
||||
s.Require().Equal(0, capacity)
|
||||
|
||||
capacity, err = s.m.RemainingWatchOnlyAccountCapacity()
|
||||
s.Require().Error(err)
|
||||
s.Require().Equal("no more watch-only accounts can be added", err.Error())
|
||||
s.Require().Equal(0, capacity)
|
||||
|
||||
// add 6 accounts more
|
||||
seedImportedKeypair4.Accounts[0].Address = types.Address{0x81}
|
||||
err = s.m.SaveOrUpdateAccount(seedImportedKeypair4.Accounts[0])
|
||||
s.Require().NoError(err)
|
||||
|
||||
seedImportedKeypair4.Accounts[0].Address = types.Address{0x82}
|
||||
err = s.m.SaveOrUpdateAccount(seedImportedKeypair4.Accounts[0])
|
||||
s.Require().NoError(err)
|
||||
|
||||
seedImportedKeypair4.Accounts[0].Address = types.Address{0x83}
|
||||
err = s.m.SaveOrUpdateAccount(seedImportedKeypair4.Accounts[0])
|
||||
s.Require().NoError(err)
|
||||
|
||||
seedImportedKeypair4.Accounts[0].Address = types.Address{0x84}
|
||||
err = s.m.SaveOrUpdateAccount(seedImportedKeypair4.Accounts[0])
|
||||
s.Require().NoError(err)
|
||||
|
||||
seedImportedKeypair4.Accounts[0].Address = types.Address{0x85}
|
||||
err = s.m.SaveOrUpdateAccount(seedImportedKeypair4.Accounts[0])
|
||||
s.Require().NoError(err)
|
||||
|
||||
seedImportedKeypair4.Accounts[0].Address = types.Address{0x86}
|
||||
err = s.m.SaveOrUpdateAccount(seedImportedKeypair4.Accounts[0])
|
||||
s.Require().NoError(err)
|
||||
|
||||
// check the capacity after adding 8 more watch only accounts
|
||||
capacity, err = s.m.RemainingAccountCapacity()
|
||||
s.Require().Error(err)
|
||||
s.Require().Equal("no more accounts can be added", err.Error())
|
||||
s.Require().Equal(0, capacity)
|
||||
|
||||
capacity, err = s.m.RemainingKeypairCapacity()
|
||||
s.Require().Error(err)
|
||||
s.Require().Equal("no more keypairs can be added", err.Error())
|
||||
s.Require().Equal(0, capacity)
|
||||
|
||||
capacity, err = s.m.RemainingWatchOnlyAccountCapacity()
|
||||
s.Require().Error(err)
|
||||
s.Require().Equal("no more watch-only accounts can be added", err.Error())
|
||||
s.Require().Equal(0, capacity)
|
||||
}
|
|
@ -211,6 +211,21 @@ func (api *API) AddKeypair(ctx context.Context, password string, keypair *accoun
|
|||
return nil
|
||||
}
|
||||
|
||||
// RemainingAccountCapacity returns the number of accounts that can be added.
|
||||
func (api *API) RemainingAccountCapacity(ctx context.Context) (int, error) {
|
||||
return (*api.messenger).RemainingAccountCapacity()
|
||||
}
|
||||
|
||||
// RemainingKeypairCapacity returns the number of keypairs that can be added.
|
||||
func (api *API) RemainingKeypairCapacity(ctx context.Context) (int, error) {
|
||||
return (*api.messenger).RemainingKeypairCapacity()
|
||||
}
|
||||
|
||||
// RemainingWatchOnlyAccountCapacity returns the number of watch-only accounts that can be added.
|
||||
func (api *API) RemainingWatchOnlyAccountCapacity(ctx context.Context) (int, error) {
|
||||
return (*api.messenger).RemainingWatchOnlyAccountCapacity()
|
||||
}
|
||||
|
||||
func (api *API) checkAccountValidity(account *accounts.Account) error {
|
||||
if len(account.Address) == 0 {
|
||||
return errors.New("`Address` field of an account must be set")
|
||||
|
|
|
@ -1126,6 +1126,15 @@ func (api *PublicAPI) GetSavedAddresses(ctx context.Context) ([]*wallet.SavedAdd
|
|||
return api.service.messenger.GetSavedAddresses(ctx)
|
||||
}
|
||||
|
||||
func (api *PublicAPI) GetSavedAddressesPerMode(ctx context.Context, testnetMode bool) ([]*wallet.SavedAddress, error) {
|
||||
return api.service.messenger.GetSavedAddressesPerMode(testnetMode)
|
||||
}
|
||||
|
||||
// RemainingCapacityForSavedAddresses returns the number of saved addresses that can be added
|
||||
func (api *PublicAPI) RemainingCapacityForSavedAddresses(ctx context.Context, testnetMode bool) (int, error) {
|
||||
return api.service.messenger.RemainingCapacityForSavedAddresses(testnetMode)
|
||||
}
|
||||
|
||||
// PushNotifications server endpoints
|
||||
func (api *PublicAPI) StartPushNotificationsServer() error {
|
||||
err := api.service.accountsDB.SaveSettingField(settings.PushNotificationsServerEnabled, true)
|
||||
|
|
|
@ -3,10 +3,12 @@ package wallet
|
|||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/status-im/status-go/constants"
|
||||
multiAccCommon "github.com/status-im/status-go/multiaccounts/common"
|
||||
)
|
||||
|
||||
|
@ -115,11 +117,28 @@ func (sam *SavedAddressesManager) GetSavedAddresses() ([]*SavedAddress, error) {
|
|||
return sam.getSavedAddresses("removed != 1")
|
||||
}
|
||||
|
||||
func (sam *SavedAddressesManager) GetSavedAddressesPerMode(testnetMode bool) ([]*SavedAddress, error) {
|
||||
return sam.getSavedAddresses(fmt.Sprintf("is_test = %t AND removed != 1", testnetMode))
|
||||
}
|
||||
|
||||
// GetRawSavedAddresses provides access to the soft-delete and sync metadata
|
||||
func (sam *SavedAddressesManager) GetRawSavedAddresses() ([]*SavedAddress, error) {
|
||||
return sam.getSavedAddresses("")
|
||||
}
|
||||
|
||||
func (sam *SavedAddressesManager) RemainingCapacityForSavedAddresses(testnetMode bool) (int, error) {
|
||||
savedAddress, err := sam.GetSavedAddressesPerMode(testnetMode)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
remainingCapacity := constants.MaxNumberOfSavedAddresses - len(savedAddress)
|
||||
if remainingCapacity <= 0 {
|
||||
return 0, errors.New("no more save addresses can be added")
|
||||
}
|
||||
|
||||
return remainingCapacity, nil
|
||||
}
|
||||
|
||||
func (sam *SavedAddressesManager) upsertSavedAddress(sa SavedAddress, tx *sql.Tx) (err error) {
|
||||
if tx == nil {
|
||||
tx, err = sam.db.Begin()
|
||||
|
|
|
@ -394,3 +394,145 @@ func TestSavedAddressesAddSame(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(rst))
|
||||
}
|
||||
|
||||
func TestSavedAddressesPerTestnetMode(t *testing.T) {
|
||||
manager, stop := setupTestSavedAddressesDB(t)
|
||||
defer stop()
|
||||
|
||||
addresses := []SavedAddress{
|
||||
SavedAddress{
|
||||
Address: common.Address{1},
|
||||
Name: "addr1",
|
||||
IsTest: true,
|
||||
},
|
||||
SavedAddress{
|
||||
Address: common.Address{2},
|
||||
Name: "addr2",
|
||||
IsTest: false,
|
||||
},
|
||||
SavedAddress{
|
||||
Address: common.Address{3},
|
||||
Name: "addr3",
|
||||
IsTest: true,
|
||||
},
|
||||
SavedAddress{
|
||||
Address: common.Address{4},
|
||||
Name: "addr4",
|
||||
IsTest: false,
|
||||
},
|
||||
SavedAddress{
|
||||
Address: common.Address{5},
|
||||
Name: "addr5",
|
||||
IsTest: true,
|
||||
Removed: true,
|
||||
},
|
||||
SavedAddress{
|
||||
Address: common.Address{6},
|
||||
Name: "addr6",
|
||||
IsTest: false,
|
||||
Removed: true,
|
||||
},
|
||||
SavedAddress{
|
||||
Address: common.Address{7},
|
||||
Name: "addr7",
|
||||
IsTest: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, sa := range addresses {
|
||||
err := manager.upsertSavedAddress(sa, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
res, err := manager.GetSavedAddresses()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(res), len(addresses)-2)
|
||||
|
||||
res, err = manager.GetSavedAddressesPerMode(true)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(res), 3)
|
||||
|
||||
res, err = manager.GetSavedAddressesPerMode(false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(res), 2)
|
||||
}
|
||||
|
||||
func TestSavedAddressesCapacity(t *testing.T) {
|
||||
manager, stop := setupTestSavedAddressesDB(t)
|
||||
defer stop()
|
||||
|
||||
addresses := []SavedAddress{
|
||||
SavedAddress{
|
||||
Address: common.Address{1},
|
||||
Name: "addr1",
|
||||
IsTest: true,
|
||||
},
|
||||
SavedAddress{
|
||||
Address: common.Address{2},
|
||||
Name: "addr2",
|
||||
IsTest: false,
|
||||
},
|
||||
SavedAddress{
|
||||
Address: common.Address{3},
|
||||
Name: "addr3",
|
||||
IsTest: true,
|
||||
},
|
||||
SavedAddress{
|
||||
Address: common.Address{4},
|
||||
Name: "addr4",
|
||||
IsTest: false,
|
||||
},
|
||||
SavedAddress{
|
||||
Address: common.Address{5},
|
||||
Name: "addr5",
|
||||
IsTest: true,
|
||||
Removed: true,
|
||||
},
|
||||
SavedAddress{
|
||||
Address: common.Address{6},
|
||||
Name: "addr6",
|
||||
IsTest: false,
|
||||
Removed: true,
|
||||
},
|
||||
SavedAddress{
|
||||
Address: common.Address{7},
|
||||
Name: "addr7",
|
||||
IsTest: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, sa := range addresses {
|
||||
err := manager.upsertSavedAddress(sa, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
capacity, err := manager.RemainingCapacityForSavedAddresses(true)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 17, capacity)
|
||||
|
||||
capacity, err = manager.RemainingCapacityForSavedAddresses(false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 18, capacity)
|
||||
|
||||
// add 17 more for testnet and 18 more for mainnet mode
|
||||
for i := 1; i < 36; i++ {
|
||||
sa := SavedAddress{
|
||||
Address: common.Address{byte(i + 8)},
|
||||
Name: "addr" + strconv.Itoa(i+8),
|
||||
IsTest: i%2 == 0,
|
||||
}
|
||||
|
||||
err := manager.upsertSavedAddress(sa, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
capacity, err = manager.RemainingCapacityForSavedAddresses(true)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "no more save addresses can be added", err.Error())
|
||||
require.Equal(t, 0, capacity)
|
||||
|
||||
capacity, err = manager.RemainingCapacityForSavedAddresses(false)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "no more save addresses can be added", err.Error())
|
||||
require.Equal(t, 0, capacity)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue