Skip to content

Commit

Permalink
feat: Add Wallet Initialization Gamification Trigger - MEED-2208 - Me…
Browse files Browse the repository at this point in the history
…eds-io/MIPs#50 (#390)

* This change will introduce a new Trigger to gamification for wallet
first creation.
* Allow to send initial funds to First initialization of Metamask Wallet
Prior to this change, when initializing Metamask wallet for the first
time for a user, the initial funds wasn't sent to user. This change will
allow to send initial funds for a given user for the first time only by
triggering the generic event 'exo.wallet.addressAssociation.new' which
was triggered only when the wallet is initialized with a Browser Wallet.
  • Loading branch information
boubaker authored Jul 20, 2023
1 parent 40a5173 commit 36a2d56
Show file tree
Hide file tree
Showing 15 changed files with 377 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ public Object around(ProceedingJoinPoint point) throws Throwable { // NOSONAR
} finally {
long duration = System.currentTimeMillis() - startTime;
try {
Map<String, Object> parameters = statisticService.getStatisticParameters(operation, result, point.getArgs());
Map<String, Object> parameters = statisticService == null ? null
: statisticService.getStatisticParameters(operation,
result,
point.getArgs());
if (parameters != null) {
if (local) {
put(parameters, LOCAL_SERVICE, service);
Expand All @@ -109,7 +112,9 @@ public Object around(ProceedingJoinPoint point) throws Throwable { // NOSONAR
put(parameters, STATUS_CODE, "200");
}
}
addStatisticEntry(parameters);
if (ExoContainer.hasProfile("analytics")) {
addStatisticEntry(parameters);
}
}
} catch (Throwable e) {
LOG.warn("Error adding statistic log entry in method '{}' for statistic type '{}'", method.getName(), operation, e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
import java.util.Random;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.stream.Collectors;

import javax.servlet.http.HttpSession;

Expand Down Expand Up @@ -102,10 +101,12 @@ private WalletUtils() {
}

@SuppressWarnings("all")
public static final char[] SIMPLE_CHARS = new char[] { 'A', 'B', 'C', 'D',
public static final char[] SIMPLE_CHARS = new char[] {
'A', 'B', 'C', 'D',
'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1',
'2', '3', '4', '5', '6', '7', '8', '9' };
'2', '3', '4', '5', '6', '7', '8', '9'
};

public static final String COMETD_CHANNEL = "/eXo/Application/Addons/Wallet";

Expand Down Expand Up @@ -168,6 +169,8 @@ private WalletUtils() {

public static final String WALLET_BROWSER_PHRASE_NAME = "WALLET_BROWSER_PHRASE";

public static final String WALLET_INITIALIZED_SETTING_PARAM = "WALLET_INITIALIZED";

public static final String ADMIN_KEY_PARAMETER = "admin.wallet.key";

public static final String ABI_PATH_PARAMETER = "contract.abi.path";
Expand Down Expand Up @@ -365,6 +368,23 @@ private WalletUtils() {

public static final String LOGIN_MESSAGE_ATTRIBUTE_NAME = "login_message";

public static final String GAMIFICATION_BROADCAST_ACTION_EVENT =
"exo.gamification.generic.action";

public static final String GAMIFICATION_EVENT_ID = "eventId";

public static final String GAMIFICATION_EARNER_ID = "senderId";

public static final String GAMIFICATION_RECEIVER_ID = "receiverId";

public static final String GAMIFICATION_OBJECT_ID = "objectId";

public static final String GAMIFICATION_OBJECT_TYPE = "objectType";

public static final String GAMIFICATION_WALLET_OBJECT_TYPE = "wallet";

public static final String GAMIFICATION_CREATE_WALLET_EVENT = "createWallet";

public static final Random Random = new Random();

public static String blockchainUrlSuffix = null; // NOSONAR
Expand Down Expand Up @@ -394,7 +414,7 @@ public static List<String> getNotificationReceiversUsers(Wallet wallet, String e
} else if (StringUtils.isBlank(excludedId)) {
return Arrays.asList(managers);
} else {
return Arrays.stream(managers).filter(member -> !excludedId.equals(member)).collect(Collectors.toList());
return Arrays.stream(managers).filter(member -> !excludedId.equals(member)).toList();
}
}
} else if (WalletType.isUser(wallet.getType())) {
Expand Down Expand Up @@ -643,9 +663,9 @@ public static boolean isUserSpaceManager(String id, String modifier) {
/**
* Return true if user can access wallet detailed information
*
* @param wallet wallet details to check
* @param currentUser user accessing wallet details
* @return true if has access, else false
* @param wallet wallet details to check
* @param currentUser user accessing wallet details
* @return true if has access, else false
*/
public static boolean canAccessWallet(Wallet wallet, String currentUser) {
if (StringUtils.isBlank(currentUser)) {
Expand Down Expand Up @@ -938,11 +958,7 @@ public static final void logStatistics(TransactionDetail transactionDetail) {
case ETHER_FUNC_SEND_FUNDS:
parameters.put("amount_ether", transactionDetail.getValue());
break;
case CONTRACT_FUNC_TRANSFORMTOVESTED:
case CONTRACT_FUNC_TRANSFERFROM:
case CONTRACT_FUNC_TRANSFER:
case CONTRACT_FUNC_APPROVE:
case CONTRACT_FUNC_REWARD:
case CONTRACT_FUNC_TRANSFORMTOVESTED, CONTRACT_FUNC_TRANSFERFROM, CONTRACT_FUNC_TRANSFER, CONTRACT_FUNC_APPROVE, CONTRACT_FUNC_REWARD:
parameters.put("amount_token", transactionDetail.getContractAmount());
break;
case CONTRACT_FUNC_ADDADMIN:
Expand Down Expand Up @@ -978,11 +994,11 @@ public static final SpaceService getSpaceService() {
* Format Wallet Balance amount in currency format, without currency symbol
* and switch user locale.
*
* @param balance amount to format
* @param locale designated locale to display balance
* @param simplified if true, the fractions will be ignored when the balance
* is greater than 100.
* @return formatted balance in user locale
* @param balance amount to format
* @param locale designated locale to display balance
* @param simplified if true, the fractions will be ignored when the balance
* is greater than 100.
* @return formatted balance in user locale
*/
public static final String formatBalance(double balance, Locale locale, boolean simplified) {
// Avoid to display fractions when the amount of balance is big
Expand Down
2 changes: 1 addition & 1 deletion wallet-services/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
<artifactId>wallet-services</artifactId>
<name>eXo Add-on:: Wallet - Services</name>
<properties>
<exo.test.coverage.ratio>0.62</exo.test.coverage.ratio>
<exo.test.coverage.ratio>0.63</exo.test.coverage.ratio>
</properties>
<dependencies>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

import javax.persistence.TypedQuery;

import org.apache.commons.lang.StringUtils;

import org.exoplatform.commons.persistence.impl.GenericDAOJPAImpl;
import org.exoplatform.wallet.entity.WalletEntity;

Expand All @@ -38,7 +40,7 @@ public void deleteAll(List<WalletEntity> entities) {
public WalletEntity findByAddress(String address) {
TypedQuery<WalletEntity> query = getEntityManager().createNamedQuery("Wallet.findByAddress",
WalletEntity.class);
query.setParameter("address", address);
query.setParameter("address", StringUtils.lowerCase(address));
List<WalletEntity> resultList = query.getResultList();
return resultList == null || resultList.isEmpty() ? null : resultList.get(0);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
@ExoEntity
@DynamicUpdate
@Table(name = "ADDONS_WALLET_ACCOUNT")
@NamedQuery(name = "Wallet.findByAddress", query = "SELECT w FROM Wallet w WHERE w.address = :address")
@NamedQuery(name = "Wallet.findByAddress", query = "SELECT w FROM Wallet w WHERE LOWER(w.address) = :address")
public class WalletEntity implements Serializable {
private static final long serialVersionUID = -1622032986992776281L;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* This file is part of the Meeds project (https://meeds.io/).
*
* Copyright (C) 2020 - 2023 Meeds Association [email protected]
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.exoplatform.wallet.listener;

import static org.exoplatform.wallet.utils.WalletUtils.GAMIFICATION_BROADCAST_ACTION_EVENT;
import static org.exoplatform.wallet.utils.WalletUtils.GAMIFICATION_CREATE_WALLET_EVENT;
import static org.exoplatform.wallet.utils.WalletUtils.GAMIFICATION_EARNER_ID;
import static org.exoplatform.wallet.utils.WalletUtils.GAMIFICATION_EVENT_ID;
import static org.exoplatform.wallet.utils.WalletUtils.GAMIFICATION_OBJECT_ID;
import static org.exoplatform.wallet.utils.WalletUtils.GAMIFICATION_OBJECT_TYPE;
import static org.exoplatform.wallet.utils.WalletUtils.GAMIFICATION_RECEIVER_ID;
import static org.exoplatform.wallet.utils.WalletUtils.GAMIFICATION_WALLET_OBJECT_TYPE;

import java.util.HashMap;
import java.util.Map;

import org.exoplatform.services.listener.Asynchronous;
import org.exoplatform.services.listener.Event;
import org.exoplatform.services.listener.Listener;
import org.exoplatform.services.listener.ListenerService;
import org.exoplatform.wallet.model.Wallet;

@Asynchronous
public class GamificationWalletInitializationListener extends Listener<Object, String> {

private ListenerService listenerService;

public GamificationWalletInitializationListener(ListenerService listenerService) {
this.listenerService = listenerService;
}

@Override
public void onEvent(Event<Object, String> event) throws Exception {
Wallet wallet = (Wallet) event.getSource();
Map<String, String> gam = new HashMap<>();
gam.put(GAMIFICATION_EVENT_ID, GAMIFICATION_CREATE_WALLET_EVENT);
gam.put(GAMIFICATION_EARNER_ID, String.valueOf(wallet.getTechnicalId()));
gam.put(GAMIFICATION_RECEIVER_ID, String.valueOf(wallet.getTechnicalId()));
gam.put(GAMIFICATION_OBJECT_TYPE, GAMIFICATION_WALLET_OBJECT_TYPE);
gam.put(GAMIFICATION_OBJECT_ID, wallet.getAddress());
listenerService.broadcast(GAMIFICATION_BROADCAST_ACTION_EVENT, gam, null);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@
import static org.exoplatform.wallet.utils.WalletUtils.WALLET_DISABLED_EVENT;
import static org.exoplatform.wallet.utils.WalletUtils.WALLET_ENABLED_EVENT;
import static org.exoplatform.wallet.utils.WalletUtils.WALLET_INITIALIZATION_MODIFICATION_EVENT;
import static org.exoplatform.wallet.utils.WalletUtils.WALLET_INITIALIZED_SETTING_PARAM;
import static org.exoplatform.wallet.utils.WalletUtils.WALLET_MODIFIED_EVENT;
import static org.exoplatform.wallet.utils.WalletUtils.WALLET_PROVIDER_MODIFIED_EVENT;
import static org.exoplatform.wallet.utils.WalletUtils.WALLET_SCOPE;
import static org.exoplatform.wallet.utils.WalletUtils.canAccessWallet;
import static org.exoplatform.wallet.utils.WalletUtils.checkUserIsSpaceManager;
import static org.exoplatform.wallet.utils.WalletUtils.computeWalletFromIdentity;
Expand Down Expand Up @@ -62,6 +64,9 @@
import org.web3j.crypto.Sign.SignatureData;
import org.web3j.utils.Numeric;

import org.exoplatform.commons.api.settings.SettingService;
import org.exoplatform.commons.api.settings.SettingValue;
import org.exoplatform.commons.api.settings.data.Context;
import org.exoplatform.commons.utils.CommonsUtils;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.container.PortalContainer;
Expand Down Expand Up @@ -108,6 +113,8 @@ public class WalletAccountServiceImpl implements WalletAccountService, ExoWallet

private WalletTokenAdminService tokenAdminService;

private SettingService settingService;

private WalletStorage accountStorage;

private AddressLabelStorage labelStorage;
Expand All @@ -121,8 +128,10 @@ public class WalletAccountServiceImpl implements WalletAccountService, ExoWallet
public WalletAccountServiceImpl(PortalContainer container,
WalletStorage walletAccountStorage,
AddressLabelStorage labelStorage,
SettingService settingService,
InitParams params) {
this.container = container;
this.settingService = settingService;
this.accountStorage = walletAccountStorage;
this.labelStorage = labelStorage;
if (params != null && params.containsKey(ADMIN_KEY_PARAMETER)
Expand Down Expand Up @@ -436,18 +445,17 @@ public void saveWalletAddress(Wallet wallet, String currentUser) throws IllegalA

long identityId = wallet.getTechnicalId();
Wallet oldWallet = accountStorage.getWalletByIdentityId(identityId, getContractAddress());
boolean isNew = oldWallet == null;
checkCanSaveWallet(wallet, oldWallet, currentUser);
if (!isNew && StringUtils.equalsIgnoreCase(oldWallet.getAddress(), wallet.getAddress())) {
if (oldWallet != null && StringUtils.equalsIgnoreCase(oldWallet.getAddress(), wallet.getAddress())) {
throw new IllegalAccessException("Can't modify wallet properties once saved");
}

WalletProvider provider = isNew || oldWallet.getProvider() == null ? WalletProvider.INTERNAL_WALLET
: WalletProvider.valueOf(oldWallet.getProvider());
computeWalletProperties(wallet, oldWallet, provider, isNew);
accountStorage.saveWallet(wallet, isNew);
WalletProvider provider = oldWallet == null || oldWallet.getProvider() == null ? WalletProvider.INTERNAL_WALLET
: WalletProvider.valueOf(oldWallet.getProvider());
computeWalletProperties(wallet, oldWallet, provider, oldWallet == null);
accountStorage.saveWallet(wallet, oldWallet == null);

if (!isNew) {
if (oldWallet != null) {
// Automatically Remove old private key when modifying address associated
// to wallet
accountStorage.removeWalletPrivateKey(identityId);
Expand All @@ -458,18 +466,32 @@ public void saveWalletAddress(Wallet wallet, String currentUser) throws IllegalA
if (!WalletType.isAdmin(wallet.getType())) {
refreshWalletFromBlockchain(wallet, getContractDetail(), null);

String eventName = isNew ? NEW_ADDRESS_ASSOCIATED_EVENT : MODIFY_ADDRESS_ASSOCIATED_EVENT;
boolean isNew = oldWallet == null && !accountStorage.hasWalletBackup(identityId) && !isUserWalletInitialized(wallet.getId());
String eventName = isNew ? NEW_ADDRESS_ASSOCIATED_EVENT
: MODIFY_ADDRESS_ASSOCIATED_EVENT;
try {
getListenerService().broadcast(eventName, wallet.clone(), currentUser);
} catch (Exception e) {
LOG.error("Error broadcasting event {} for wallet {}", eventName, wallet, e);
} finally {
setUserWalletAsInitialized(wallet.getId());
}
}
}

@Override
public Wallet saveWallet(Wallet wallet, boolean isNew) {
return accountStorage.saveWallet(wallet, isNew);
wallet = accountStorage.saveWallet(wallet, isNew);
if (isNew && !isUserWalletInitialized(wallet.getId())) {
try {
getListenerService().broadcast(NEW_ADDRESS_ASSOCIATED_EVENT, wallet.clone(), wallet.getId());
} catch (Exception e) {
LOG.error("Error broadcasting event {} for wallet {}", NEW_ADDRESS_ASSOCIATED_EVENT, wallet, e);
} finally {
setUserWalletAsInitialized(wallet.getId());
}
}
return wallet;
}

@Override
Expand All @@ -487,6 +509,7 @@ public void switchToInternalWallet(long identityId) {
// No internal wallet created by user, so delete wallet information
accountStorage.removeWallet(identityId);
}
setUserWalletAsInitialized(wallet.getId());
}

@Override
Expand All @@ -507,6 +530,8 @@ public void switchWalletProvider(long identityId,
throw new IllegalStateException("Invalid Signed Message", e);
}

Wallet existingWallet = getWalletByIdentityId(identityId);

Wallet wallet = null;
if (accountStorage.hasWallet(identityId)) {
accountStorage.switchToWalletProvider(identityId, provider, newAddress);
Expand All @@ -526,10 +551,20 @@ public void switchWalletProvider(long identityId,

refreshWalletFromBlockchain(wallet, getContractDetail(), null);

boolean isNew = !isUserWalletInitialized(wallet.getId()) && !accountStorage.hasWalletBackup(identityId) && (existingWallet == null || StringUtils.isBlank(existingWallet.getAddress()) || StringUtils.isNotBlank(existingWallet.getInitializationState()));
try {
getListenerService().broadcast(WALLET_PROVIDER_MODIFIED_EVENT, provider, wallet);
if (isNew) {
getListenerService().broadcast(NEW_ADDRESS_ASSOCIATED_EVENT, wallet.clone(), wallet.getId());
} else {
getListenerService().broadcast(WALLET_PROVIDER_MODIFIED_EVENT, provider, wallet);
}
} catch (Exception e) {
LOG.error("Error while braodcasting wallet {} provider modification to {}", wallet);
LOG.error("Error broadcasting event {} for wallet {}",
isNew ? NEW_ADDRESS_ASSOCIATED_EVENT : WALLET_PROVIDER_MODIFIED_EVENT,
wallet,
e);
} finally {
setUserWalletAsInitialized(wallet.getId());
}
}

Expand Down Expand Up @@ -975,4 +1010,18 @@ private void computeWalletProperties(Wallet wallet, Wallet oldWallet, WalletProv
setWalletPassPhrase(wallet, oldWallet);
}

private void setUserWalletAsInitialized(String username) {
settingService.set(Context.USER.id(username),
WALLET_SCOPE,
WALLET_INITIALIZED_SETTING_PARAM,
SettingValue.create("true"));
}

private boolean isUserWalletInitialized(String username) {
SettingValue<?> value = settingService.get(Context.USER.id(username),
WALLET_SCOPE,
WALLET_INITIALIZED_SETTING_PARAM);
return value != null && Boolean.parseBoolean(value.getValue().toString());
}

}
Loading

0 comments on commit 36a2d56

Please sign in to comment.