bumped to 1.1.2
This commit is contained in:
parent
13b201b020
commit
36a371ef34
10 changed files with 370 additions and 62 deletions
12
Makefile
12
Makefile
|
@ -595,3 +595,15 @@ user3-desktop-mainnet:
|
|||
--apiPort=1204 \
|
||||
--useNativeXmrWallet=false \
|
||||
--ignoreLocalXmrNode=false \
|
||||
|
||||
parner1-bot-stagenet:
|
||||
./haveno-bot$(APP_EXT) \
|
||||
--baseCurrencyNetwork=XMR_STAGENET \
|
||||
--useLocalhostForP2P=false \
|
||||
--useDevPrivilegeKeys=false \
|
||||
--nodePort=9999 \
|
||||
--appName=haveno-XMR_STAGENET_user3 \
|
||||
--apiPassword=apitest \
|
||||
--apiPort=1204 \
|
||||
--useNativeXmrWallet=false \
|
||||
--ignoreLocalXmrNode=false \
|
64
bot/src/main/java/haveno/bot/app/HavenoBot.java
Normal file
64
bot/src/main/java/haveno/bot/app/HavenoBot.java
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq 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 Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Haveno Multi-Platform by Kewbit.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq 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 Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.bot.app;
|
||||
|
||||
import com.google.inject.Injector;
|
||||
import haveno.core.app.misc.AppSetup;
|
||||
import haveno.core.app.misc.AppSetupWithP2P;
|
||||
import haveno.core.network.p2p.inventory.GetInventoryRequestHandler;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class HavenoBot {
|
||||
@Setter
|
||||
private Injector injector;
|
||||
private AppSetup appSetup;
|
||||
private GetInventoryRequestHandler getInventoryRequestHandler;
|
||||
|
||||
public HavenoBot() {
|
||||
}
|
||||
|
||||
public void startApplication() {
|
||||
appSetup = injector.getInstance(AppSetupWithP2P.class);
|
||||
appSetup.start();
|
||||
|
||||
getInventoryRequestHandler = injector.getInstance(GetInventoryRequestHandler.class);
|
||||
}
|
||||
|
||||
public void shutDown() {
|
||||
getInventoryRequestHandler.shutDown();
|
||||
}
|
||||
}
|
187
bot/src/main/java/haveno/bot/app/HavenoBotMain.java
Normal file
187
bot/src/main/java/haveno/bot/app/HavenoBotMain.java
Normal file
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq 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 Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.bot.app;
|
||||
|
||||
import haveno.common.UserThread;
|
||||
import haveno.common.app.AppModule;
|
||||
import haveno.common.handlers.ResultHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import haveno.common.Timer;
|
||||
import haveno.common.config.BaseCurrencyNetwork;
|
||||
import haveno.common.config.Config;
|
||||
import haveno.core.app.TorSetup;
|
||||
import haveno.core.app.misc.ExecutableForAppWithP2p;
|
||||
import haveno.core.app.misc.ModuleForAppWithP2p;
|
||||
import haveno.core.user.Cookie;
|
||||
import haveno.core.user.CookieKey;
|
||||
import haveno.core.user.User;
|
||||
import haveno.network.p2p.P2PService;
|
||||
import haveno.network.p2p.P2PServiceListener;
|
||||
import haveno.network.p2p.peers.PeerManager;
|
||||
|
||||
@Slf4j
|
||||
public class HavenoBotMain extends ExecutableForAppWithP2p {
|
||||
|
||||
private static final long CHECK_CONNECTION_LOSS_SEC = 30;
|
||||
private static final String VERSION = "1.1.2";
|
||||
private HavenoBot bot;
|
||||
private Timer checkConnectionLossTime;
|
||||
|
||||
public HavenoBotMain() {
|
||||
super("Haveno Bot", "haveno-bot", "haveno_bot", VERSION);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("HavenoBot.VERSION: " + VERSION);
|
||||
new HavenoBotMain().execute(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int doExecute() {
|
||||
super.doExecute();
|
||||
|
||||
checkMemory(config, this);
|
||||
|
||||
return keepRunning();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addCapabilities() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void launchApplication() {
|
||||
UserThread.execute(() -> {
|
||||
try {
|
||||
bot = new HavenoBot();
|
||||
UserThread.execute(this::onApplicationLaunched);
|
||||
} catch (Exception e) {
|
||||
log.error("Error launching haveno bot: {}\n", e.toString(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onApplicationLaunched() {
|
||||
super.onApplicationLaunched();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// We continue with a series of synchronous execution tasks
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected AppModule getModule() {
|
||||
return new ModuleForAppWithP2p(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyInjector() {
|
||||
super.applyInjector();
|
||||
|
||||
bot.setInjector(injector);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startApplication() {
|
||||
Cookie cookie = injector.getInstance(User.class).getCookie();
|
||||
cookie.getAsOptionalBoolean(CookieKey.CLEAN_TOR_DIR_AT_RESTART).ifPresent(wasCleanTorDirSet -> {
|
||||
if (wasCleanTorDirSet) {
|
||||
injector.getInstance(TorSetup.class).cleanupTorFiles(() -> {
|
||||
log.info("Tor directory reset");
|
||||
cookie.remove(CookieKey.CLEAN_TOR_DIR_AT_RESTART);
|
||||
}, log::error);
|
||||
}
|
||||
});
|
||||
|
||||
bot.startApplication();
|
||||
|
||||
injector.getInstance(P2PService.class).addP2PServiceListener(new P2PServiceListener() {
|
||||
@Override
|
||||
public void onDataReceived() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNoSeedNodeAvailable() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNoPeersAvailable() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdatedDataReceived() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTorNodeReady() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHiddenServicePublished() {
|
||||
UserThread.runAfter(() -> setupConnectionLossCheck(), 60);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetupFailed(Throwable throwable) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestCustomBridges() {
|
||||
// Do nothing
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupConnectionLossCheck() {
|
||||
// For dev testing (usually on XMR_LOCAL) we don't want to get the seed shut
|
||||
// down as it is normal that the seed is the only actively running node.
|
||||
if (Config.baseCurrencyNetwork() != BaseCurrencyNetwork.XMR_MAINNET) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkConnectionLossTime != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkConnectionLossTime = UserThread.runPeriodically(() -> {
|
||||
if (injector.getInstance(PeerManager.class).getNumAllConnectionsLostEvents() > 1) {
|
||||
// We set a flag to clear tor cache files at re-start. We cannot clear it now as Tor is used and
|
||||
// that can cause problems.
|
||||
injector.getInstance(User.class).getCookie().putAsBoolean(CookieKey.CLEAN_TOR_DIR_AT_RESTART, true);
|
||||
shutDown(this);
|
||||
}
|
||||
}, CHECK_CONNECTION_LOSS_SEC);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void gracefulShutDown(ResultHandler resultHandler) {
|
||||
bot.shutDown();
|
||||
super.gracefulShutDown(resultHandler);
|
||||
}
|
||||
}
|
19
bot/src/main/resources/logback.xml
Normal file
19
bot/src/main/resources/logback.xml
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
|
||||
<conversionRule conversionWord="hl2" converterClass="haveno.common.app.LogHighlighter" />
|
||||
|
||||
<appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%hl2(%d{MMM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{40}: %msg %xEx%n)</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="TRACE">
|
||||
<appender-ref ref="CONSOLE_APPENDER"/>
|
||||
</root>
|
||||
|
||||
<logger name="io.grpc.netty" level="WARN"/>
|
||||
<logger name="org.apache" level="WARN" />
|
||||
|
||||
</configuration>
|
68
build.gradle
68
build.gradle
|
@ -132,7 +132,8 @@ configure([project(':cli'),
|
|||
project(':seednode'),
|
||||
project(':statsnode'),
|
||||
project(':inventory'),
|
||||
project(':apitest')]) {
|
||||
project(':apitest'),
|
||||
project(':bot')]) {
|
||||
|
||||
apply plugin: 'application'
|
||||
|
||||
|
@ -735,6 +736,71 @@ configure(project(':daemon')) {
|
|||
}
|
||||
}
|
||||
|
||||
configure(project(':bot')) {
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
apply plugin: 'application'
|
||||
|
||||
application {
|
||||
mainClass = 'haveno.bot.app.HavenoBotMain'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':proto')
|
||||
implementation project(':common')
|
||||
implementation project(':p2p')
|
||||
implementation project(':core')
|
||||
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
||||
compileOnly "javax.annotation:javax.annotation-api:$javaxAnnotationVersion"
|
||||
compileOnly "org.projectlombok:lombok:$lombokVersion"
|
||||
implementation "ch.qos.logback:logback-classic:$logbackVersion"
|
||||
implementation "ch.qos.logback:logback-core:$logbackVersion"
|
||||
implementation "com.google.code.gson:gson:$gsonVersion"
|
||||
implementation "com.google.guava:guava:$guavaVersion"
|
||||
implementation "com.google.protobuf:protobuf-java:$protobufVersion"
|
||||
implementation "org.apache.commons:commons-lang3:$langVersion"
|
||||
implementation "org.jetbrains:annotations:$jetbrainsAnnotationsVersion"
|
||||
implementation "org.slf4j:slf4j-api:$slf4jVersion"
|
||||
implementation("com.github.bisq-network:bitcoinj:$bitcoinjVersion") {
|
||||
exclude(module: 'bcprov-jdk15on')
|
||||
exclude(module: 'guava')
|
||||
exclude(module: 'jsr305')
|
||||
exclude(module: 'okhttp')
|
||||
exclude(module: 'okio')
|
||||
exclude(module: 'protobuf-java')
|
||||
exclude(module: 'slf4j-api')
|
||||
}
|
||||
implementation("com.google.inject:guice:$guiceVersion") {
|
||||
exclude(module: 'guava')
|
||||
}
|
||||
implementation("io.grpc:grpc-protobuf:$grpcVersion") {
|
||||
exclude(module: 'animal-sniffer-annotations')
|
||||
exclude(module: 'guava')
|
||||
}
|
||||
implementation("io.grpc:grpc-stub:$grpcVersion") {
|
||||
exclude(module: 'animal-sniffer-annotations')
|
||||
exclude(module: 'guava')
|
||||
}
|
||||
runtimeOnly("io.grpc:grpc-netty-shaded:$grpcVersion") {
|
||||
exclude(module: 'animal-sniffer-annotations')
|
||||
exclude(module: 'guava')
|
||||
}
|
||||
testAnnotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
||||
testCompileOnly "org.projectlombok:lombok:$lombokVersion"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:$jupiterVersion"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-params:$jupiterVersion"
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$jupiterVersion")
|
||||
|
||||
implementation("io.github.woodser:monero-java:$moneroJavaVersion") {
|
||||
exclude(module: 'jackson-core')
|
||||
exclude(module: 'jackson-annotations')
|
||||
exclude(module: 'jackson-databind')
|
||||
exclude(module: 'bcprov-jdk15on')
|
||||
exclude(group: 'org.slf4j', module: 'slf4j-simple')
|
||||
}
|
||||
implementation "org.openjfx:javafx-base:$javafxVersion:$os"
|
||||
implementation "org.openjfx:javafx-graphics:$javafxVersion:$os"
|
||||
}
|
||||
}
|
||||
|
||||
configure(project(':inventory')) {
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
|
|
|
@ -97,25 +97,13 @@ public class AlertManager {
|
|||
}
|
||||
|
||||
protected List<String> getPubKeyList() {
|
||||
if (useDevPrivilegeKeys) return List.of(DevEnv.DEV_PRIVILEGE_PUB_KEY);
|
||||
switch (Config.baseCurrencyNetwork()) {
|
||||
case XMR_LOCAL:
|
||||
return List.of(
|
||||
"027a381b5333a56e1cc3d90d3a7d07f26509adf7029ed06fc997c656621f8da1ee",
|
||||
"024baabdba90e7cc0dc4626ef73ea9d722ea7085d1104491da8c76f28187513492");
|
||||
case XMR_STAGENET:
|
||||
return List.of(
|
||||
"036d8a1dfcb406886037d2381da006358722823e1940acc2598c844bbc0fd1026f",
|
||||
"026c581ad773d987e6bd10785ac7f7e0e64864aedeb8bce5af37046de812a37854",
|
||||
"025b058c9f2c60d839669dbfa5578cf5a8117d60e6b70e2f0946f8a691273c6a36");
|
||||
case XMR_MAINNET:
|
||||
return List.of();
|
||||
default:
|
||||
throw new RuntimeException("Unhandled base currency network: " + Config.baseCurrencyNetwork());
|
||||
}
|
||||
return List.of(
|
||||
"0326b14f3a55d02575dceed5202b8b125f458cbe0fdceeee294b443bf1a8d8cf78",
|
||||
"03d62d14438adbe7aea688ade1f73933c6f0a705f238c02c5b54b83dd1e4fca225",
|
||||
"023c8fdea9ff2d03daef54337907e70a7b0e20084a75fcc3ad2f0c28d8b691dea1"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -96,22 +96,11 @@ public class PrivateNotificationManager implements MessageListener {
|
|||
}
|
||||
|
||||
protected List<String> getPubKeyList() {
|
||||
if (useDevPrivilegeKeys) return List.of(DevEnv.DEV_PRIVILEGE_PUB_KEY);
|
||||
switch (Config.baseCurrencyNetwork()) {
|
||||
case XMR_LOCAL:
|
||||
return List.of(
|
||||
"027a381b5333a56e1cc3d90d3a7d07f26509adf7029ed06fc997c656621f8da1ee",
|
||||
"024baabdba90e7cc0dc4626ef73ea9d722ea7085d1104491da8c76f28187513492");
|
||||
case XMR_STAGENET:
|
||||
return List.of(
|
||||
"02ba7c5de295adfe57b60029f3637a2c6b1d0e969a8aaefb9e0ddc3a7963f26925",
|
||||
"026c581ad773d987e6bd10785ac7f7e0e64864aedeb8bce5af37046de812a37854",
|
||||
"025b058c9f2c60d839669dbfa5578cf5a8117d60e6b70e2f0946f8a691273c6a36");
|
||||
case XMR_MAINNET:
|
||||
return List.of();
|
||||
default:
|
||||
throw new RuntimeException("Unhandled base currency network: " + Config.baseCurrencyNetwork());
|
||||
}
|
||||
return List.of(
|
||||
"0326b14f3a55d02575dceed5202b8b125f458cbe0fdceeee294b443bf1a8d8cf78",
|
||||
"03d62d14438adbe7aea688ade1f73933c6f0a705f238c02c5b54b83dd1e4fca225",
|
||||
"023c8fdea9ff2d03daef54337907e70a7b0e20084a75fcc3ad2f0c28d8b691dea1"
|
||||
);
|
||||
}
|
||||
|
||||
private void handleMessage(DecryptedMessageWithPubKey decryptedMessageWithPubKey, NodeAddress senderNodeAddress) {
|
||||
|
|
|
@ -114,11 +114,11 @@ public class FilterManager {
|
|||
this.providersRepository = providersRepository;
|
||||
this.ignoreDevMsg = ignoreDevMsg;
|
||||
|
||||
publicKeys = useDevPrivilegeKeys ?
|
||||
Collections.singletonList(DevEnv.DEV_PRIVILEGE_PUB_KEY) :
|
||||
List.of("0358d47858acdc41910325fce266571540681ef83a0d6fedce312bef9810793a27",
|
||||
"029340c3e7d4bb0f9e651b5f590b434fecb6175aeaa57145c7804ff05d210e534f",
|
||||
"034dc7530bf66ffd9580aa98031ea9a18ac2d269f7c56c0e71eca06105b9ed69f9");
|
||||
publicKeys = List.of(
|
||||
"0326b14f3a55d02575dceed5202b8b125f458cbe0fdceeee294b443bf1a8d8cf78",
|
||||
"03d62d14438adbe7aea688ade1f73933c6f0a705f238c02c5b54b83dd1e4fca225",
|
||||
"023c8fdea9ff2d03daef54337907e70a7b0e20084a75fcc3ad2f0c28d8b691dea1"
|
||||
);
|
||||
|
||||
banFilter.setBannedNodePredicate(this::isNodeAddressBannedFromNetwork);
|
||||
}
|
||||
|
|
|
@ -59,29 +59,11 @@ public class ArbitratorManager extends DisputeAgentManager<Arbitrator> {
|
|||
|
||||
@Override
|
||||
protected List<String> getPubKeyList() {
|
||||
switch (Config.baseCurrencyNetwork()) {
|
||||
case XMR_LOCAL:
|
||||
return List.of(
|
||||
"027a381b5333a56e1cc3d90d3a7d07f26509adf7029ed06fc997c656621f8da1ee",
|
||||
"024baabdba90e7cc0dc4626ef73ea9d722ea7085d1104491da8c76f28187513492",
|
||||
"026eeec3c119dd6d537249d74e5752a642dd2c3cc5b6a9b44588eb58344f29b519");
|
||||
case XMR_STAGENET:
|
||||
return List.of(
|
||||
"03bb559ce207a4deb51d4c705076c95b85ad8581d35936b2a422dcb504eaf7cdb0",
|
||||
"026c581ad773d987e6bd10785ac7f7e0e64864aedeb8bce5af37046de812a37854",
|
||||
"025b058c9f2c60d839669dbfa5578cf5a8117d60e6b70e2f0946f8a691273c6a36",
|
||||
"036c7d3f4bf05ef39b9d1b0a5d453a18210de36220c3d83cd16e59bd6132b037ad",
|
||||
"030f7122a10ff73cd73808bddace95be77a94189c8a0eb24586265e125ce5ce6b9",
|
||||
"03aa23e062afa0dda465f46986f8aa8d0374ad3e3f256141b05681dcb1e39c3859",
|
||||
"02d3beb1293ca2ca14e6d42ca8bd18089a62aac62fd6bb23923ee6ead46ac60fba",
|
||||
"03fa0f38f27bdd324db6f933f7e57851dadf3b911e4db6b19dd0950492c4525a31",
|
||||
"02a1a458df5acf4ab08fdca748e28f33a955a30854c8c1a831ee733dca7f0d2fcd",
|
||||
"0374dd70f3fa6e47ec5ab97932e1cec6233e98e6ae3129036b17118650c44fd3de");
|
||||
case XMR_MAINNET:
|
||||
return List.of();
|
||||
default:
|
||||
throw new RuntimeException("Unhandled base currency network: " + Config.baseCurrencyNetwork());
|
||||
}
|
||||
return List.of(
|
||||
"0326b14f3a55d02575dceed5202b8b125f458cbe0fdceeee294b443bf1a8d8cf78",
|
||||
"03d62d14438adbe7aea688ade1f73933c6f0a705f238c02c5b54b83dd1e4fca225",
|
||||
"023c8fdea9ff2d03daef54337907e70a7b0e20084a75fcc3ad2f0c28d8b691dea1"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -12,5 +12,6 @@ include 'seednode'
|
|||
include 'statsnode'
|
||||
include 'inventory'
|
||||
include 'apitest'
|
||||
include 'bot'
|
||||
|
||||
rootProject.name = 'haveno'
|
||||
|
|
Loading…
Reference in a new issue