mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-11-02 20:36:44 +00:00
refactor paynym functionality to rely on bip47 support
This commit is contained in:
parent
e83c02653c
commit
ce6b371206
24 changed files with 423 additions and 414 deletions
|
@ -92,7 +92,7 @@ dependencies {
|
|||
implementation('org.slf4j:jul-to-slf4j:1.7.30') {
|
||||
exclude group: 'org.slf4j'
|
||||
}
|
||||
implementation('com.sparrowwallet.nightjar:nightjar:0.2.27')
|
||||
implementation('com.sparrowwallet.nightjar:nightjar:0.2.30')
|
||||
implementation('io.reactivex.rxjava2:rxjava:2.2.15')
|
||||
implementation('io.reactivex.rxjava2:rxjavafx:2.2.2')
|
||||
implementation('org.apache.commons:commons-lang3:3.7')
|
||||
|
@ -461,7 +461,7 @@ extraJavaModuleInfo {
|
|||
module('cbor-0.9.jar', 'co.nstant.in.cbor', '0.9') {
|
||||
exports('co.nstant.in.cbor')
|
||||
}
|
||||
module('nightjar-0.2.27.jar', 'com.sparrowwallet.nightjar', '0.2.27') {
|
||||
module('nightjar-0.2.30.jar', 'com.sparrowwallet.nightjar', '0.2.30') {
|
||||
requires('com.google.common')
|
||||
requires('net.sourceforge.streamsupport')
|
||||
requires('org.slf4j')
|
||||
|
|
2
drongo
2
drongo
|
@ -1 +1 @@
|
|||
Subproject commit 7bb07ab39eafc0de54d3dc2e19a444d39f9a1fc3
|
||||
Subproject commit 956f59880e508127b62d62022e3e2618f659f4d2
|
|
@ -28,7 +28,7 @@ import com.sparrowwallet.sparrow.net.ServerType;
|
|||
import com.sparrowwallet.sparrow.preferences.PreferenceGroup;
|
||||
import com.sparrowwallet.sparrow.preferences.PreferencesDialog;
|
||||
import com.sparrowwallet.sparrow.soroban.CounterpartyDialog;
|
||||
import com.sparrowwallet.sparrow.soroban.PayNymDialog;
|
||||
import com.sparrowwallet.sparrow.paynym.PayNymDialog;
|
||||
import com.sparrowwallet.sparrow.soroban.Soroban;
|
||||
import com.sparrowwallet.sparrow.soroban.SorobanServices;
|
||||
import com.sparrowwallet.sparrow.transaction.TransactionController;
|
||||
|
@ -55,7 +55,6 @@ import javafx.geometry.Insets;
|
|||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
|
@ -1019,10 +1018,6 @@ public class AppController implements Initializable {
|
|||
whirlpool.setHDWallet(storage.getWalletId(wallet), copy);
|
||||
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
|
||||
soroban.setHDWallet(copy);
|
||||
} else if(Config.get().isUsePayNym() && SorobanServices.canWalletMix(wallet)) {
|
||||
String walletId = storage.getWalletId(wallet);
|
||||
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
|
||||
soroban.setPaymentCode(copy);
|
||||
}
|
||||
|
||||
StandardAccount standardAccount = wallet.getStandardAccountType();
|
||||
|
|
|
@ -18,6 +18,7 @@ import com.sparrowwallet.sparrow.control.TrayManager;
|
|||
import com.sparrowwallet.sparrow.event.*;
|
||||
import com.sparrowwallet.sparrow.io.*;
|
||||
import com.sparrowwallet.sparrow.net.*;
|
||||
import com.sparrowwallet.sparrow.paynym.PayNymService;
|
||||
import com.sparrowwallet.sparrow.soroban.SorobanServices;
|
||||
import com.sparrowwallet.sparrow.whirlpool.WhirlpoolServices;
|
||||
import javafx.application.Platform;
|
||||
|
@ -86,6 +87,8 @@ public class AppServices {
|
|||
|
||||
private final SorobanServices sorobanServices = new SorobanServices();
|
||||
|
||||
private static PayNymService payNymService;
|
||||
|
||||
private final MainApp application;
|
||||
|
||||
private final Map<Window, List<WalletTabData>> walletWindows = new LinkedHashMap<>();
|
||||
|
@ -232,6 +235,11 @@ public class AppServices {
|
|||
versionCheckService.cancel();
|
||||
}
|
||||
|
||||
if(payNymService != null) {
|
||||
PayNymService.ShutdownService shutdownService = new PayNymService.ShutdownService(payNymService);
|
||||
shutdownService.start();
|
||||
}
|
||||
|
||||
if(Tor.getDefault() != null) {
|
||||
Tor.getDefault().shutdown();
|
||||
}
|
||||
|
@ -487,6 +495,26 @@ public class AppServices {
|
|||
return get().sorobanServices;
|
||||
}
|
||||
|
||||
public static PayNymService getPayNymService() {
|
||||
if(payNymService == null) {
|
||||
HostAndPort torProxy = getTorProxy();
|
||||
payNymService = new PayNymService(torProxy);
|
||||
} else {
|
||||
HostAndPort torProxy = getTorProxy();
|
||||
if(!Objects.equals(payNymService.getTorProxy(), torProxy)) {
|
||||
payNymService.setTorProxy(getTorProxy());
|
||||
}
|
||||
}
|
||||
|
||||
return payNymService;
|
||||
}
|
||||
|
||||
public static HostAndPort getTorProxy() {
|
||||
return AppServices.isTorRunning() ?
|
||||
HostAndPort.fromParts("localhost", TorService.PROXY_PORT) :
|
||||
(Config.get().getProxyServer() == null || Config.get().getProxyServer().isEmpty() || !Config.get().isUseProxy() ? null : HostAndPort.fromString(Config.get().getProxyServer()));
|
||||
}
|
||||
|
||||
public static AppController newAppWindow(Stage stage) {
|
||||
try {
|
||||
FXMLLoader appLoader = new FXMLLoader(AppServices.class.getResource("app.fxml"));
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import com.samourai.wallet.bip47.rpc.PaymentCode;
|
||||
import com.sparrowwallet.drongo.bip47.PaymentCode;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
|
@ -75,6 +75,14 @@ public class PayNymAvatar extends StackPane {
|
|||
this.paymentCodeProperty.set(paymentCode);
|
||||
}
|
||||
|
||||
public void setPaymentCode(com.samourai.wallet.bip47.rpc.PaymentCode paymentCode) {
|
||||
setPaymentCode(PaymentCode.fromString(paymentCode.toString()));
|
||||
}
|
||||
|
||||
public void clearPaymentCode() {
|
||||
this.paymentCodeProperty.set(null);
|
||||
}
|
||||
|
||||
private static String getCacheId(PaymentCode paymentCode, double width) {
|
||||
return paymentCode.toString();
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||
import com.sparrowwallet.sparrow.soroban.PayNym;
|
||||
import com.sparrowwallet.sparrow.soroban.PayNymController;
|
||||
import com.sparrowwallet.sparrow.paynym.PayNym;
|
||||
import com.sparrowwallet.sparrow.paynym.PayNymController;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.*;
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import com.samourai.wallet.bip47.rpc.PaymentCode;
|
||||
import com.sparrowwallet.drongo.bip47.PaymentCode;
|
||||
|
||||
public class PaymentCodeTextField extends CopyableTextField {
|
||||
private String paymentCodeStr;
|
||||
|
||||
public void setPaymentCode(PaymentCode paymentCode) {
|
||||
this.paymentCodeStr = paymentCode.toString();
|
||||
setPaymentCodeString();
|
||||
}
|
||||
|
||||
public void setPaymentCode(com.samourai.wallet.bip47.rpc.PaymentCode paymentCode) {
|
||||
this.paymentCodeStr = paymentCode.toString();
|
||||
setPaymentCodeString();
|
||||
}
|
||||
|
||||
private void setPaymentCodeString() {
|
||||
String abbrevPcode = paymentCodeStr.substring(0, 12) + "..." + paymentCodeStr.substring(paymentCodeStr.length() - 5);
|
||||
setText(abbrevPcode);
|
||||
}
|
||||
|
|
|
@ -15,8 +15,7 @@ import com.sparrowwallet.sparrow.AppServices;
|
|||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.*;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.soroban.PayNym;
|
||||
import com.sparrowwallet.sparrow.soroban.Soroban;
|
||||
import com.sparrowwallet.sparrow.paynym.PayNym;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
|
@ -1718,9 +1717,8 @@ public class ElectrumServer {
|
|||
}
|
||||
|
||||
private PayNym getPayNym(PaymentCode paymentCode) {
|
||||
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
|
||||
try {
|
||||
return soroban.getPayNym(paymentCode.toString()).blockingFirst();
|
||||
return AppServices.getPayNymService().getPayNym(paymentCode.toString()).blockingFirst();
|
||||
} catch(Exception e) {
|
||||
//ignore
|
||||
}
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
package com.sparrowwallet.sparrow.soroban;
|
||||
package com.sparrowwallet.sparrow.paynym;
|
||||
|
||||
import com.samourai.wallet.bip47.rpc.PaymentCode;
|
||||
import com.sparrowwallet.drongo.bip47.InvalidPaymentCodeException;
|
||||
import com.sparrowwallet.drongo.bip47.PaymentCode;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PayNym {
|
||||
private static final Logger log = LoggerFactory.getLogger(PayNym.class);
|
||||
|
||||
private final PaymentCode paymentCode;
|
||||
private final String nymId;
|
||||
private final String nymName;
|
||||
|
@ -57,4 +62,16 @@ public class PayNym {
|
|||
public static List<ScriptType> getV1ScriptTypes() {
|
||||
return List.of(ScriptType.P2PKH);
|
||||
}
|
||||
|
||||
public static PayNym fromString(String strPaymentCode, String nymId, String nymName, boolean segwit, List<PayNym> following, List<PayNym> followers) {
|
||||
PaymentCode paymentCode;
|
||||
try {
|
||||
paymentCode = new PaymentCode(strPaymentCode);
|
||||
} catch(InvalidPaymentCodeException e) {
|
||||
log.error("Error creating PayNym from payment code " + strPaymentCode, e);
|
||||
paymentCode = null;
|
||||
}
|
||||
|
||||
return new PayNym(paymentCode, nymId, nymName, segwit, following, followers);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.sparrowwallet.sparrow.soroban;
|
||||
package com.sparrowwallet.sparrow.paynym;
|
||||
|
||||
import com.sparrowwallet.drongo.address.P2WPKHAddress;
|
||||
|
|
@ -1,13 +1,10 @@
|
|||
package com.sparrowwallet.sparrow.soroban;
|
||||
package com.sparrowwallet.sparrow.paynym;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import com.samourai.wallet.bip47.rpc.PaymentCode;
|
||||
import com.sparrowwallet.drongo.SecureString;
|
||||
import com.sparrowwallet.drongo.bip47.PaymentCode;
|
||||
import com.sparrowwallet.drongo.bip47.SecretPoint;
|
||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||
import com.sparrowwallet.drongo.crypto.EncryptionType;
|
||||
import com.sparrowwallet.drongo.crypto.InvalidPasswordException;
|
||||
import com.sparrowwallet.drongo.crypto.Key;
|
||||
import com.sparrowwallet.drongo.protocol.*;
|
||||
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||
import com.sparrowwallet.drongo.wallet.*;
|
||||
|
@ -38,12 +35,15 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import java.util.*;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
|
||||
|
||||
public class PayNymController extends SorobanController {
|
||||
public class PayNymController {
|
||||
private static final Logger log = LoggerFactory.getLogger(PayNymController.class);
|
||||
|
||||
public static final Pattern PAYNYM_REGEX = Pattern.compile("\\+[a-z]+[0-9][0-9a-fA-F][0-9a-fA-F]");
|
||||
|
||||
private static final long MINIMUM_P2PKH_OUTPUT_SATS = 546L;
|
||||
|
||||
private String walletId;
|
||||
|
@ -97,7 +97,7 @@ public class PayNymController extends SorobanController {
|
|||
|
||||
Wallet masterWallet = getMasterWallet();
|
||||
if(masterWallet.hasPaymentCode()) {
|
||||
paymentCode.setPaymentCode(new PaymentCode(masterWallet.getPaymentCode().toString()));
|
||||
paymentCode.setPaymentCode(masterWallet.getPaymentCode());
|
||||
}
|
||||
|
||||
findNymProperty.addListener((observable, oldValue, nymIdentifier) -> {
|
||||
|
@ -166,13 +166,12 @@ public class PayNymController extends SorobanController {
|
|||
}
|
||||
|
||||
private void refresh() {
|
||||
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
|
||||
if(!getMasterWallet().hasPaymentCode()) {
|
||||
throw new IllegalStateException("Payment code is not present");
|
||||
}
|
||||
retrievePayNymProgress.setVisible(true);
|
||||
|
||||
soroban.getPayNym(getMasterWallet().getPaymentCode().toString()).subscribe(payNym -> {
|
||||
AppServices.getPayNymService().getPayNym(getMasterWallet().getPaymentCode().toString()).subscribe(payNym -> {
|
||||
retrievePayNymProgress.setVisible(false);
|
||||
walletPayNym = payNym;
|
||||
payNymName.setText(payNym.nymName());
|
||||
|
@ -219,8 +218,7 @@ public class PayNymController extends SorobanController {
|
|||
followingList.setItems(FXCollections.observableList(new ArrayList<>()));
|
||||
findPayNym.setVisible(true);
|
||||
|
||||
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
|
||||
soroban.getPayNym(nymIdentifier).subscribe(searchedPayNym -> {
|
||||
AppServices.getPayNymService().getPayNym(nymIdentifier).subscribe(searchedPayNym -> {
|
||||
findPayNym.setVisible(false);
|
||||
List<PayNym> searchList = new ArrayList<>();
|
||||
searchList.add(searchedPayNym);
|
||||
|
@ -233,7 +231,6 @@ public class PayNymController extends SorobanController {
|
|||
}
|
||||
|
||||
public void showQR(ActionEvent event) {
|
||||
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
|
||||
QRDisplayDialog qrDisplayDialog = new QRDisplayDialog(getMasterWallet().getPaymentCode().toString());
|
||||
qrDisplayDialog.showAndWait();
|
||||
}
|
||||
|
@ -253,97 +250,36 @@ public class PayNymController extends SorobanController {
|
|||
|
||||
public void retrievePayNym(ActionEvent event) {
|
||||
Config.get().setUsePayNym(true);
|
||||
makeAuthenticatedCall(null);
|
||||
}
|
||||
|
||||
public void followPayNym(PaymentCode paymentCode) {
|
||||
makeAuthenticatedCall(paymentCode);
|
||||
}
|
||||
|
||||
private void makeAuthenticatedCall(PaymentCode contact) {
|
||||
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
|
||||
if(soroban.getHdWallet() == null) {
|
||||
Wallet wallet = getMasterWallet();
|
||||
if(wallet.isEncrypted()) {
|
||||
Wallet copy = wallet.copy();
|
||||
WalletPasswordDialog dlg = new WalletPasswordDialog(copy.getMasterName(), WalletPasswordDialog.PasswordRequirement.LOAD);
|
||||
Optional<SecureString> password = dlg.showAndWait();
|
||||
if(password.isPresent()) {
|
||||
Storage storage = AppServices.get().getOpenWallets().get(wallet);
|
||||
Storage.KeyDerivationService keyDerivationService = new Storage.KeyDerivationService(storage, password.get(), true);
|
||||
keyDerivationService.setOnSucceeded(workerStateEvent -> {
|
||||
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Done"));
|
||||
ECKey encryptionFullKey = keyDerivationService.getValue();
|
||||
Key key = new Key(encryptionFullKey.getPrivKeyBytes(), storage.getKeyDeriver().getSalt(), EncryptionType.Deriver.ARGON2);
|
||||
copy.decrypt(key);
|
||||
|
||||
try {
|
||||
soroban.setHDWallet(copy);
|
||||
makeAuthenticatedCall(soroban, contact);
|
||||
} finally {
|
||||
key.clear();
|
||||
encryptionFullKey.clear();
|
||||
password.get().clear();
|
||||
}
|
||||
});
|
||||
keyDerivationService.setOnFailed(workerStateEvent -> {
|
||||
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Failed"));
|
||||
if(keyDerivationService.getException() instanceof InvalidPasswordException) {
|
||||
Optional<ButtonType> optResponse = showErrorDialog("Invalid Password", "The wallet password was invalid. Try again?", ButtonType.CANCEL, ButtonType.OK);
|
||||
if(optResponse.isPresent() && optResponse.get().equals(ButtonType.OK)) {
|
||||
Platform.runLater(() -> makeAuthenticatedCall(contact));
|
||||
}
|
||||
} else {
|
||||
log.error("Error deriving wallet key", keyDerivationService.getException());
|
||||
}
|
||||
});
|
||||
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.START, "Decrypting wallet..."));
|
||||
keyDerivationService.start();
|
||||
}
|
||||
} else {
|
||||
soroban.setHDWallet(wallet);
|
||||
makeAuthenticatedCall(soroban, contact);
|
||||
}
|
||||
} else {
|
||||
makeAuthenticatedCall(soroban, contact);
|
||||
}
|
||||
}
|
||||
|
||||
private void makeAuthenticatedCall(Soroban soroban, PaymentCode contact) {
|
||||
if(contact != null) {
|
||||
followPayNym(soroban, contact);
|
||||
} else {
|
||||
retrievePayNym(soroban);
|
||||
}
|
||||
}
|
||||
|
||||
private void retrievePayNym(Soroban soroban) {
|
||||
soroban.createPayNym().subscribe(createMap -> {
|
||||
PayNymService payNymService = AppServices.getPayNymService();
|
||||
Wallet masterWallet = getMasterWallet();
|
||||
payNymService.createPayNym(masterWallet).subscribe(createMap -> {
|
||||
payNymName.setText((String)createMap.get("nymName"));
|
||||
payNymAvatar.setPaymentCode(new PaymentCode(getMasterWallet().getPaymentCode().toString()));
|
||||
payNymAvatar.setPaymentCode(masterWallet.getPaymentCode());
|
||||
payNymName.setVisible(true);
|
||||
|
||||
claimPayNym(soroban, createMap, getMasterWallet().getScriptType() != ScriptType.P2PKH);
|
||||
payNymService.claimPayNym(masterWallet, createMap, getMasterWallet().getScriptType() != ScriptType.P2PKH);
|
||||
refresh();
|
||||
}, error -> {
|
||||
log.error("Error retrieving PayNym", error);
|
||||
Optional<ButtonType> optResponse = showErrorDialog("Error retrieving PayNym", "Could not retrieve PayNym. Try again?", ButtonType.CANCEL, ButtonType.OK);
|
||||
if(optResponse.isPresent() && optResponse.get().equals(ButtonType.OK)) {
|
||||
retrievePayNym(soroban);
|
||||
retrievePayNym(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void followPayNym(Soroban soroban, PaymentCode contact) {
|
||||
soroban.getAuthToken(new HashMap<>()).subscribe(authToken -> {
|
||||
String signature = soroban.getSignature(authToken);
|
||||
soroban.followPaymentCode(contact, authToken, signature).subscribe(followMap -> {
|
||||
public void followPayNym(PaymentCode contact) {
|
||||
PayNymService payNymService = AppServices.getPayNymService();
|
||||
Wallet masterWallet = getMasterWallet();
|
||||
payNymService.getAuthToken(masterWallet, new HashMap<>()).subscribe(authToken -> {
|
||||
String signature = payNymService.getSignature(masterWallet, authToken);
|
||||
payNymService.followPaymentCode(contact, authToken, signature).subscribe(followMap -> {
|
||||
refresh();
|
||||
}, error -> {
|
||||
log.error("Could not follow payment code", error);
|
||||
Optional<ButtonType> optResponse = showErrorDialog("Error retrieving PayNym", "Could not follow payment code. Try again?", ButtonType.CANCEL, ButtonType.OK);
|
||||
if(optResponse.isPresent() && optResponse.get().equals(ButtonType.OK)) {
|
||||
followPayNym(soroban, contact);
|
||||
followPayNym(contact);
|
||||
} else {
|
||||
followingList.refresh();
|
||||
}
|
||||
|
@ -352,7 +288,7 @@ public class PayNymController extends SorobanController {
|
|||
log.error("Could not follow payment code", error);
|
||||
Optional<ButtonType> optResponse = showErrorDialog("Error retrieving PayNym", "Could not follow payment code. Try again?", ButtonType.CANCEL, ButtonType.OK);
|
||||
if(optResponse.isPresent() && optResponse.get().equals(ButtonType.OK)) {
|
||||
followPayNym(soroban, contact);
|
||||
followPayNym(contact);
|
||||
} else {
|
||||
followingList.refresh();
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.sparrowwallet.sparrow.soroban;
|
||||
package com.sparrowwallet.sparrow.paynym;
|
||||
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
|
@ -0,0 +1,259 @@
|
|||
package com.sparrowwallet.sparrow.paynym;
|
||||
|
||||
import com.google.common.net.HostAndPort;
|
||||
import com.samourai.http.client.HttpUsage;
|
||||
import com.samourai.http.client.IHttpClient;
|
||||
import com.sparrowwallet.drongo.bip47.InvalidPaymentCodeException;
|
||||
import com.sparrowwallet.drongo.bip47.PaymentCode;
|
||||
import com.sparrowwallet.drongo.crypto.ChildNumber;
|
||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.nightjar.http.JavaHttpClientService;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.rxjavafx.schedulers.JavaFxScheduler;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java8.util.Optional;
|
||||
import javafx.concurrent.Service;
|
||||
import javafx.concurrent.Task;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class PayNymService {
|
||||
private static final Logger log = LoggerFactory.getLogger(PayNymService.class);
|
||||
|
||||
private final JavaHttpClientService httpClientService;
|
||||
|
||||
public PayNymService(HostAndPort torProxy) {
|
||||
this.httpClientService = new JavaHttpClientService(torProxy);
|
||||
}
|
||||
|
||||
public Observable<Map<String, Object>> createPayNym(Wallet wallet) {
|
||||
return createPayNym(getPaymentCode(wallet));
|
||||
}
|
||||
|
||||
public Observable<Map<String, Object>> createPayNym(PaymentCode paymentCode) {
|
||||
if(paymentCode == null) {
|
||||
throw new IllegalStateException("Payment code is null");
|
||||
}
|
||||
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("content-type", "application/json");
|
||||
|
||||
HashMap<String, Object> body = new HashMap<>();
|
||||
body.put("code", paymentCode.toString());
|
||||
|
||||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
|
||||
return httpClient.postJson("https://paynym.is/api/v1/create", Map.class, headers, body)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
.map(Optional::get);
|
||||
}
|
||||
|
||||
public Observable<Map<String, Object>> updateToken(PaymentCode paymentCode) {
|
||||
if(paymentCode == null) {
|
||||
throw new IllegalStateException("Payment code is null");
|
||||
}
|
||||
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("content-type", "application/json");
|
||||
|
||||
HashMap<String, Object> body = new HashMap<>();
|
||||
body.put("code", paymentCode.toString());
|
||||
|
||||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
|
||||
return httpClient.postJson("https://paynym.is/api/v1/token", Map.class, headers, body)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
.map(Optional::get);
|
||||
}
|
||||
|
||||
public void claimPayNym(Wallet wallet, Map<String, Object> createMap, boolean segwit) {
|
||||
if(createMap.get("claimed") == Boolean.FALSE) {
|
||||
getAuthToken(wallet, createMap).subscribe(authToken -> {
|
||||
String signature = getSignature(wallet, authToken);
|
||||
claimPayNym(authToken, signature).subscribe(claimMap -> {
|
||||
log.debug("Claimed payment code " + claimMap.get("claimed"));
|
||||
addPaymentCode(getPaymentCode(wallet), authToken, signature, segwit).subscribe(addMap -> {
|
||||
log.debug("Added payment code " + addMap);
|
||||
});
|
||||
}, error -> {
|
||||
getAuthToken(wallet, new HashMap<>()).subscribe(newAuthToken -> {
|
||||
String newSignature = getSignature(wallet, newAuthToken);
|
||||
claimPayNym(newAuthToken, newSignature).subscribe(claimMap -> {
|
||||
log.debug("Claimed payment code " + claimMap.get("claimed"));
|
||||
addPaymentCode(getPaymentCode(wallet), newAuthToken, newSignature, segwit).subscribe(addMap -> {
|
||||
log.debug("Added payment code " + addMap);
|
||||
});
|
||||
}, newError -> {
|
||||
log.error("Error claiming PayNym with new authToken", newError);
|
||||
});
|
||||
}, newError -> {
|
||||
log.error("Error retrieving new authToken", newError);
|
||||
});
|
||||
});
|
||||
}, error -> {
|
||||
log.error("Error retrieving authToken", error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Observable<Map<String, Object>> claimPayNym(String authToken, String signature) {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("content-type", "application/json");
|
||||
headers.put("auth-token", authToken);
|
||||
|
||||
HashMap<String, Object> body = new HashMap<>();
|
||||
body.put("signature", signature);
|
||||
|
||||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
|
||||
return httpClient.postJson("https://paynym.is/api/v1/claim", Map.class, headers, body)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
.map(Optional::get);
|
||||
}
|
||||
|
||||
public Observable<Map<String, Object>> addPaymentCode(PaymentCode paymentCode, String authToken, String signature, boolean segwit) {
|
||||
String strPaymentCode;
|
||||
try {
|
||||
strPaymentCode = segwit ? paymentCode.makeSamouraiPaymentCode() : paymentCode.toString();
|
||||
} catch(InvalidPaymentCodeException e) {
|
||||
log.warn("Error creating segwit enabled payment code", e);
|
||||
strPaymentCode = paymentCode.toString();
|
||||
}
|
||||
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("content-type", "application/json");
|
||||
headers.put("auth-token", authToken);
|
||||
|
||||
HashMap<String, Object> body = new HashMap<>();
|
||||
body.put("nym", paymentCode.toString());
|
||||
body.put("code", strPaymentCode);
|
||||
body.put("signature", signature);
|
||||
|
||||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
|
||||
return httpClient.postJson("https://paynym.is/api/v1/nym/add", Map.class, headers, body)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
.map(Optional::get);
|
||||
}
|
||||
|
||||
public Observable<Map<String, Object>> followPaymentCode(com.samourai.wallet.bip47.rpc.PaymentCode paymentCode, String authToken, String signature) {
|
||||
return followPaymentCode(PaymentCode.fromString(paymentCode.toString()), authToken, signature);
|
||||
}
|
||||
|
||||
public Observable<Map<String, Object>> followPaymentCode(PaymentCode paymentCode, String authToken, String signature) {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("content-type", "application/json");
|
||||
headers.put("auth-token", authToken);
|
||||
|
||||
HashMap<String, Object> body = new HashMap<>();
|
||||
body.put("signature", signature);
|
||||
body.put("target", paymentCode.toString());
|
||||
|
||||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
|
||||
return httpClient.postJson("https://paynym.is/api/v1/follow", Map.class, headers, body)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
.map(Optional::get);
|
||||
}
|
||||
|
||||
public Observable<Map<String, Object>> fetchPayNym(String nymIdentifier) {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("content-type", "application/json");
|
||||
|
||||
HashMap<String, Object> body = new HashMap<>();
|
||||
body.put("nym", nymIdentifier);
|
||||
|
||||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
|
||||
return httpClient.postJson("https://paynym.is/api/v1/nym", Map.class, headers, body)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
.map(Optional::get);
|
||||
}
|
||||
|
||||
public Observable<PayNym> getPayNym(String nymIdentifier) {
|
||||
return fetchPayNym(nymIdentifier).map(nymMap -> {
|
||||
List<Map<String, Object>> codes = (List<Map<String, Object>>)nymMap.get("codes");
|
||||
PaymentCode code = new PaymentCode((String)codes.stream().filter(codeMap -> codeMap.get("segwit") == Boolean.FALSE).map(codeMap -> codeMap.get("code")).findFirst().orElse(codes.get(0).get("code")));
|
||||
|
||||
List<Map<String, Object>> followingMaps = (List<Map<String, Object>>)nymMap.get("following");
|
||||
List<PayNym> following = followingMaps.stream().map(followingMap -> {
|
||||
return PayNym.fromString((String)followingMap.get("code"), (String)followingMap.get("nymId"), (String)followingMap.get("nymName"), (Boolean)followingMap.get("segwit"), Collections.emptyList(), Collections.emptyList());
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
List<Map<String, Object>> followersMaps = (List<Map<String, Object>>)nymMap.get("followers");
|
||||
List<PayNym> followers = followersMaps.stream().map(followerMap -> {
|
||||
return PayNym.fromString((String)followerMap.get("code"), (String)followerMap.get("nymId"), (String)followerMap.get("nymName"), (Boolean)followerMap.get("segwit"), Collections.emptyList(), Collections.emptyList());
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
return new PayNym(code, (String)nymMap.get("nymID"), (String)nymMap.get("nymName"), (Boolean)nymMap.get("segwit"), following, followers);
|
||||
});
|
||||
}
|
||||
|
||||
public Observable<String> getAuthToken(Wallet wallet, Map<String, Object> map) {
|
||||
if(map.containsKey("token")) {
|
||||
return Observable.just((String)map.get("token"));
|
||||
}
|
||||
|
||||
return updateToken(wallet).map(tokenMap -> (String)tokenMap.get("token"));
|
||||
}
|
||||
|
||||
public Observable<Map<String, Object>> updateToken(Wallet wallet) {
|
||||
return updateToken(getPaymentCode(wallet));
|
||||
}
|
||||
|
||||
public String getSignature(Wallet wallet, String authToken) {
|
||||
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet();
|
||||
Keystore keystore = masterWallet.getKeystores().get(0);
|
||||
List<ChildNumber> derivation = keystore.getKeyDerivation().getDerivation();
|
||||
ChildNumber derivationStart = derivation.isEmpty() ? ChildNumber.ZERO_HARDENED : derivation.get(derivation.size() - 1);
|
||||
ECKey notificationPrivKey = keystore.getBip47ExtendedPrivateKey().getKey(List.of(derivationStart, new ChildNumber(0)));
|
||||
return notificationPrivKey.signMessage(authToken, ScriptType.P2PKH);
|
||||
}
|
||||
|
||||
private PaymentCode getPaymentCode(Wallet wallet) {
|
||||
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet();
|
||||
return masterWallet.getPaymentCode();
|
||||
}
|
||||
|
||||
public HostAndPort getTorProxy() {
|
||||
return httpClientService.getTorProxy();
|
||||
}
|
||||
|
||||
public void setTorProxy(HostAndPort torProxy) {
|
||||
//Ensure all http clients are shutdown first
|
||||
httpClientService.shutdown();
|
||||
httpClientService.setTorProxy(torProxy);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
httpClientService.shutdown();
|
||||
}
|
||||
|
||||
public static class ShutdownService extends Service<Boolean> {
|
||||
private final PayNymService payNymService;
|
||||
|
||||
public ShutdownService(PayNymService payNymService) {
|
||||
this.payNymService = payNymService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Task<Boolean> createTask() {
|
||||
return new Task<>() {
|
||||
protected Boolean call() throws Exception {
|
||||
payNymService.shutdown();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,6 +14,8 @@ import com.sparrowwallet.drongo.wallet.WalletNode;
|
|||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.control.*;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.paynym.PayNymDialog;
|
||||
import com.sparrowwallet.sparrow.paynym.PayNymService;
|
||||
import io.reactivex.rxjavafx.schedulers.JavaFxScheduler;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import javafx.application.Platform;
|
||||
|
@ -177,7 +179,8 @@ public class CounterpartyController extends SorobanController {
|
|||
payNym.setVisible(false);
|
||||
}
|
||||
|
||||
paymentCode.setPaymentCode(soroban.getPaymentCode());
|
||||
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet();
|
||||
paymentCode.setPaymentCode(masterWallet.getPaymentCode());
|
||||
paymentCodeQR.prefHeightProperty().bind(paymentCode.heightProperty());
|
||||
paymentCodeQR.prefWidthProperty().bind(showPayNym.widthProperty());
|
||||
|
||||
|
@ -228,7 +231,7 @@ public class CounterpartyController extends SorobanController {
|
|||
String code = requestMessage.getSender();
|
||||
CahootsType cahootsType = requestMessage.getType();
|
||||
PaymentCode paymentCodeInitiator = new PaymentCode(code);
|
||||
updateMixPartner(soroban, paymentCodeInitiator, cahootsType);
|
||||
updateMixPartner(paymentCodeInitiator, cahootsType);
|
||||
Boolean accepted = (Boolean)Platform.enterNestedEventLoop(meetingAccepted);
|
||||
sorobanMeetingService.sendMeetingResponse(paymentCodeInitiator, requestMessage, accepted)
|
||||
.subscribeOn(Schedulers.io())
|
||||
|
@ -236,7 +239,7 @@ public class CounterpartyController extends SorobanController {
|
|||
.subscribe(responseMessage -> {
|
||||
if(accepted) {
|
||||
startCounterpartyCollaboration(counterpartyCahootsWallet, paymentCodeInitiator, cahootsType);
|
||||
followPaymentCode(soroban, paymentCodeInitiator);
|
||||
followPaymentCode(paymentCodeInitiator);
|
||||
}
|
||||
}, error -> {
|
||||
log.error("Error sending meeting response", error);
|
||||
|
@ -251,12 +254,12 @@ public class CounterpartyController extends SorobanController {
|
|||
}
|
||||
}
|
||||
|
||||
private void updateMixPartner(Soroban soroban, PaymentCode paymentCodeInitiator, CahootsType cahootsType) {
|
||||
private void updateMixPartner(PaymentCode paymentCodeInitiator, CahootsType cahootsType) {
|
||||
String code = paymentCodeInitiator.toString();
|
||||
mixingPartner.setText(code.substring(0, 12) + "..." + code.substring(code.length() - 5));
|
||||
if(Config.get().isUsePayNym()) {
|
||||
mixPartnerAvatar.setPaymentCode(paymentCodeInitiator);
|
||||
soroban.getPayNym(paymentCodeInitiator.toString()).subscribe(payNym -> {
|
||||
AppServices.getPayNymService().getPayNym(paymentCodeInitiator.toString()).subscribe(payNym -> {
|
||||
mixingPartner.setText(payNym.nymName());
|
||||
}, error -> {
|
||||
//ignore, may not be a PayNym
|
||||
|
@ -332,11 +335,12 @@ public class CounterpartyController extends SorobanController {
|
|||
}
|
||||
}
|
||||
|
||||
private void followPaymentCode(Soroban soroban, PaymentCode paymentCodeInitiator) {
|
||||
if(Config.get().isUsePayNym() && soroban.getHdWallet() != null) {
|
||||
soroban.getAuthToken(new HashMap<>()).subscribe(authToken -> {
|
||||
String signature = soroban.getSignature(authToken);
|
||||
soroban.followPaymentCode(paymentCodeInitiator, authToken, signature).subscribe(followMap -> {
|
||||
private void followPaymentCode(PaymentCode paymentCodeInitiator) {
|
||||
if(Config.get().isUsePayNym()) {
|
||||
PayNymService payNymService = AppServices.getPayNymService();
|
||||
payNymService.getAuthToken(wallet, new HashMap<>()).subscribe(authToken -> {
|
||||
String signature = payNymService.getSignature(wallet, authToken);
|
||||
payNymService.followPaymentCode(paymentCodeInitiator, authToken, signature).subscribe(followMap -> {
|
||||
log.debug("Followed payment code " + followMap.get("following"));
|
||||
}, error -> {
|
||||
log.warn("Could not follow payment code", error);
|
||||
|
@ -376,13 +380,13 @@ public class CounterpartyController extends SorobanController {
|
|||
public void retrievePayNym(ActionEvent event) {
|
||||
Config.get().setUsePayNym(true);
|
||||
|
||||
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
|
||||
soroban.createPayNym().subscribe(createMap -> {
|
||||
PayNymService payNymService = AppServices.getPayNymService();
|
||||
payNymService.createPayNym(wallet).subscribe(createMap -> {
|
||||
payNym.setText((String)createMap.get("nymName"));
|
||||
payNymAvatar.setPaymentCode(soroban.getPaymentCode());
|
||||
payNymAvatar.setPaymentCode(wallet.isMasterWallet() ? wallet.getPaymentCode() : wallet.getMasterWallet().getPaymentCode());
|
||||
payNym.setVisible(true);
|
||||
|
||||
claimPayNym(soroban, createMap, true);
|
||||
payNymService.claimPayNym(wallet, createMap, true);
|
||||
}, error -> {
|
||||
log.error("Error retrieving PayNym", error);
|
||||
Optional<ButtonType> optResponse = showErrorDialog("Error retrieving PayNym", "Could not retrieve PayNym. Try again?", ButtonType.CANCEL, ButtonType.OK);
|
||||
|
@ -400,8 +404,8 @@ public class CounterpartyController extends SorobanController {
|
|||
}
|
||||
|
||||
public void showPayNymQR(ActionEvent event) {
|
||||
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
|
||||
QRDisplayDialog qrDisplayDialog = new QRDisplayDialog(soroban.getPaymentCode().toString());
|
||||
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet();
|
||||
QRDisplayDialog qrDisplayDialog = new QRDisplayDialog(masterWallet.getPaymentCode().toString());
|
||||
qrDisplayDialog.showAndWait();
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,9 @@ import com.sparrowwallet.sparrow.event.WalletNodeHistoryChangedEvent;
|
|||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.io.Storage;
|
||||
import com.sparrowwallet.sparrow.net.ElectrumServer;
|
||||
import com.sparrowwallet.sparrow.paynym.PayNym;
|
||||
import com.sparrowwallet.sparrow.paynym.PayNymAddress;
|
||||
import com.sparrowwallet.sparrow.paynym.PayNymDialog;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.rxjavafx.schedulers.JavaFxScheduler;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
@ -54,6 +57,7 @@ import java.util.*;
|
|||
import java.util.function.UnaryOperator;
|
||||
|
||||
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
|
||||
import static com.sparrowwallet.sparrow.paynym.PayNymController.PAYNYM_REGEX;
|
||||
import static com.sparrowwallet.sparrow.soroban.Soroban.TIMEOUT_MS;
|
||||
|
||||
public class InitiatorController extends SorobanController {
|
||||
|
@ -200,7 +204,7 @@ public class InitiatorController extends SorobanController {
|
|||
setPayNymFollowers();
|
||||
} else if(payNym != null) {
|
||||
counterpartyPayNymName.set(payNym.nymName());
|
||||
counterpartyPaymentCode.set(payNym.paymentCode());
|
||||
counterpartyPaymentCode.set(new PaymentCode(payNym.paymentCode().toString()));
|
||||
payNymAvatar.setPaymentCode(payNym.paymentCode());
|
||||
counterparty.setText(payNym.nymName());
|
||||
step1.requestFocus();
|
||||
|
@ -250,12 +254,11 @@ public class InitiatorController extends SorobanController {
|
|||
//Assumed valid payment code
|
||||
} else if(Config.get().isUsePayNym() && PAYNYM_REGEX.matcher(newValue).matches()) {
|
||||
if(!newValue.equals(counterpartyPayNymName.get())) {
|
||||
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
|
||||
payNymLoading.setVisible(true);
|
||||
soroban.getPayNym(newValue).subscribe(payNym -> {
|
||||
AppServices.getPayNymService().getPayNym(newValue).subscribe(payNym -> {
|
||||
payNymLoading.setVisible(false);
|
||||
counterpartyPayNymName.set(payNym.nymName());
|
||||
counterpartyPaymentCode.set(payNym.paymentCode());
|
||||
counterpartyPaymentCode.set(new PaymentCode(payNym.paymentCode().toString()));
|
||||
payNymAvatar.setPaymentCode(payNym.paymentCode());
|
||||
}, error -> {
|
||||
payNymLoading.setVisible(false);
|
||||
|
@ -265,7 +268,7 @@ public class InitiatorController extends SorobanController {
|
|||
} else {
|
||||
counterpartyPayNymName.set(null);
|
||||
counterpartyPaymentCode.set(null);
|
||||
payNymAvatar.setPaymentCode(null);
|
||||
payNymAvatar.clearPaymentCode();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -284,7 +287,7 @@ public class InitiatorController extends SorobanController {
|
|||
if(payment.getAddress() instanceof PayNymAddress payNymAddress) {
|
||||
PayNym payNym = payNymAddress.getPayNym();
|
||||
counterpartyPayNymName.set(payNym.nymName());
|
||||
counterpartyPaymentCode.set(payNym.paymentCode());
|
||||
counterpartyPaymentCode.set(new PaymentCode(payNym.paymentCode().toString()));
|
||||
payNymAvatar.setPaymentCode(payNym.paymentCode());
|
||||
counterparty.setText(payNym.nymName());
|
||||
counterparty.setEditable(false);
|
||||
|
@ -306,20 +309,18 @@ public class InitiatorController extends SorobanController {
|
|||
}
|
||||
|
||||
private void setPayNymFollowers() {
|
||||
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
|
||||
if(soroban.getPaymentCode() != null) {
|
||||
soroban.getFollowing().subscribe(followerPayNyms -> {
|
||||
findPayNym.setVisible(true);
|
||||
payNymFollowers.setItems(FXCollections.observableList(followerPayNyms));
|
||||
}, error -> {
|
||||
if(error.getMessage().endsWith("404")) {
|
||||
Config.get().setUsePayNym(false);
|
||||
AppServices.showErrorDialog("Could not retrieve PayNym", "This wallet does not have an associated PayNym or any followers yet. You can retrieve the PayNym using the Find PayNym button.");
|
||||
} else {
|
||||
log.warn("Could not retrieve followers: ", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet();
|
||||
AppServices.getPayNymService().getPayNym(masterWallet.getPaymentCode().toString()).map(PayNym::following).subscribe(followerPayNyms -> {
|
||||
findPayNym.setVisible(true);
|
||||
payNymFollowers.setItems(FXCollections.observableList(followerPayNyms));
|
||||
}, error -> {
|
||||
if(error.getMessage().endsWith("404")) {
|
||||
Config.get().setUsePayNym(false);
|
||||
AppServices.showErrorDialog("Could not retrieve PayNym", "This wallet does not have an associated PayNym or any followers yet. You can retrieve the PayNym using the Find PayNym button.");
|
||||
} else {
|
||||
log.warn("Could not retrieve followers: ", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void startInitiatorMeetingRequest() {
|
||||
|
@ -376,7 +377,7 @@ public class InitiatorController extends SorobanController {
|
|||
private void startInitiatorMeetingRequest(Soroban soroban, Wallet wallet) {
|
||||
SparrowCahootsWallet initiatorCahootsWallet = soroban.getCahootsWallet(wallet, (long)walletTransaction.getFeeRate());
|
||||
|
||||
getPaymentCodeCounterparty(soroban).subscribe(paymentCodeCounterparty -> {
|
||||
getPaymentCodeCounterparty().subscribe(paymentCodeCounterparty -> {
|
||||
try {
|
||||
SorobanCahootsService sorobanMeetingService = soroban.getSorobanCahootsService(initiatorCahootsWallet);
|
||||
sorobanMeetingService.sendMeetingRequest(paymentCodeCounterparty, cahootsType)
|
||||
|
@ -585,11 +586,11 @@ public class InitiatorController extends SorobanController {
|
|||
}
|
||||
}
|
||||
|
||||
private Observable<PaymentCode> getPaymentCodeCounterparty(Soroban soroban) {
|
||||
private Observable<PaymentCode> getPaymentCodeCounterparty() {
|
||||
if(counterpartyPaymentCode.get() != null) {
|
||||
return Observable.just(counterpartyPaymentCode.get());
|
||||
} else {
|
||||
return soroban.getPayNym(counterparty.getText()).map(PayNym::paymentCode);
|
||||
return AppServices.getPayNymService().getPayNym(counterparty.getText()).map(payNym -> new PaymentCode(payNym.paymentCode().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -618,7 +619,7 @@ public class InitiatorController extends SorobanController {
|
|||
Optional<PayNym> optPayNym = payNymDialog.showAndWait();
|
||||
optPayNym.ifPresent(payNym -> {
|
||||
counterpartyPayNymName.set(payNym.nymName());
|
||||
counterpartyPaymentCode.set(payNym.paymentCode());
|
||||
counterpartyPaymentCode.set(new PaymentCode(payNym.paymentCode().toString()));
|
||||
payNymAvatar.setPaymentCode(payNym.paymentCode());
|
||||
counterparty.setText(payNym.nymName());
|
||||
step1.requestFocus();
|
||||
|
|
|
@ -1,142 +0,0 @@
|
|||
package com.sparrowwallet.sparrow.soroban;
|
||||
|
||||
import com.samourai.http.client.HttpUsage;
|
||||
import com.samourai.http.client.IHttpClient;
|
||||
import com.samourai.wallet.bip47.rpc.PaymentCode;
|
||||
import com.sparrowwallet.nightjar.http.JavaHttpClientService;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.rxjavafx.schedulers.JavaFxScheduler;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java8.util.Optional;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class PayNymService {
|
||||
private final JavaHttpClientService httpClientService;
|
||||
|
||||
public PayNymService(JavaHttpClientService httpClientService) {
|
||||
this.httpClientService = httpClientService;
|
||||
}
|
||||
|
||||
public Observable<Map<String, Object>> createPayNym(PaymentCode paymentCode) {
|
||||
if(paymentCode == null) {
|
||||
throw new IllegalStateException("Payment code is null");
|
||||
}
|
||||
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("content-type", "application/json");
|
||||
|
||||
HashMap<String, Object> body = new HashMap<>();
|
||||
body.put("code", paymentCode.toString());
|
||||
|
||||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
|
||||
return httpClient.postJson("https://paynym.is/api/v1/create", Map.class, headers, body)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
.map(Optional::get);
|
||||
}
|
||||
|
||||
public Observable<Map<String, Object>> updateToken(PaymentCode paymentCode) {
|
||||
if(paymentCode == null) {
|
||||
throw new IllegalStateException("Payment code is null");
|
||||
}
|
||||
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("content-type", "application/json");
|
||||
|
||||
HashMap<String, Object> body = new HashMap<>();
|
||||
body.put("code", paymentCode.toString());
|
||||
|
||||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
|
||||
return httpClient.postJson("https://paynym.is/api/v1/token", Map.class, headers, body)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
.map(Optional::get);
|
||||
}
|
||||
|
||||
public Observable<Map<String, Object>> claimPayNym(String authToken, String signature) {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("content-type", "application/json");
|
||||
headers.put("auth-token", authToken);
|
||||
|
||||
HashMap<String, Object> body = new HashMap<>();
|
||||
body.put("signature", signature);
|
||||
|
||||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
|
||||
return httpClient.postJson("https://paynym.is/api/v1/claim", Map.class, headers, body)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
.map(Optional::get);
|
||||
}
|
||||
|
||||
public Observable<Map<String, Object>> addPaymentCode(PaymentCode paymentCode, String authToken, String signature, boolean segwit) {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("content-type", "application/json");
|
||||
headers.put("auth-token", authToken);
|
||||
|
||||
HashMap<String, Object> body = new HashMap<>();
|
||||
body.put("nym", paymentCode.toString());
|
||||
body.put("code", segwit ? paymentCode.makeSamouraiPaymentCode() : paymentCode.toString());
|
||||
body.put("signature", signature);
|
||||
|
||||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
|
||||
return httpClient.postJson("https://paynym.is/api/v1/nym/add", Map.class, headers, body)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
.map(Optional::get);
|
||||
}
|
||||
|
||||
public Observable<Map<String, Object>> followPaymentCode(PaymentCode paymentCode, String authToken, String signature) {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("content-type", "application/json");
|
||||
headers.put("auth-token", authToken);
|
||||
|
||||
HashMap<String, Object> body = new HashMap<>();
|
||||
body.put("signature", signature);
|
||||
body.put("target", paymentCode.toString());
|
||||
|
||||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
|
||||
return httpClient.postJson("https://paynym.is/api/v1/follow", Map.class, headers, body)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
.map(Optional::get);
|
||||
}
|
||||
|
||||
public Observable<Map<String, Object>> fetchPayNym(String nymIdentifier) {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("content-type", "application/json");
|
||||
|
||||
HashMap<String, Object> body = new HashMap<>();
|
||||
body.put("nym", nymIdentifier);
|
||||
|
||||
IHttpClient httpClient = httpClientService.getHttpClient(HttpUsage.COORDINATOR_REST);
|
||||
return httpClient.postJson("https://paynym.is/api/v1/nym", Map.class, headers, body)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(JavaFxScheduler.platform())
|
||||
.map(Optional::get);
|
||||
}
|
||||
|
||||
public Observable<PayNym> getPayNym(String nymIdentifier) {
|
||||
return fetchPayNym(nymIdentifier).map(nymMap -> {
|
||||
List<Map<String, Object>> codes = (List<Map<String, Object>>)nymMap.get("codes");
|
||||
PaymentCode code = new PaymentCode((String)codes.stream().filter(codeMap -> codeMap.get("segwit") == Boolean.FALSE).map(codeMap -> codeMap.get("code")).findFirst().orElse(codes.get(0).get("code")));
|
||||
|
||||
List<Map<String, Object>> followingMaps = (List<Map<String, Object>>)nymMap.get("following");
|
||||
List<PayNym> following = followingMaps.stream().map(followingMap -> {
|
||||
return new PayNym(new PaymentCode((String)followingMap.get("code")), (String)followingMap.get("nymId"), (String)followingMap.get("nymName"), (Boolean)followingMap.get("segwit"), Collections.emptyList(), Collections.emptyList());
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
List<Map<String, Object>> followersMaps = (List<Map<String, Object>>)nymMap.get("followers");
|
||||
List<PayNym> followers = followersMaps.stream().map(followerMap -> {
|
||||
return new PayNym(new PaymentCode((String)followerMap.get("code")), (String)followerMap.get("nymId"), (String)followerMap.get("nymName"), (Boolean)followerMap.get("segwit"), Collections.emptyList(), Collections.emptyList());
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
return new PayNym(code, (String)nymMap.get("nymID"), (String)nymMap.get("nymName"), (Boolean)nymMap.get("segwit"), following, followers);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -6,23 +6,17 @@ import com.samourai.http.client.IHttpClient;
|
|||
import com.samourai.soroban.client.SorobanServer;
|
||||
import com.samourai.soroban.client.cahoots.SorobanCahootsService;
|
||||
import com.samourai.soroban.client.rpc.RpcClient;
|
||||
import com.samourai.wallet.bip47.rpc.BIP47Wallet;
|
||||
import com.samourai.wallet.bip47.rpc.PaymentCode;
|
||||
import com.samourai.wallet.bip47.rpc.java.Bip47UtilJava;
|
||||
import com.samourai.wallet.cahoots.CahootsWallet;
|
||||
import com.samourai.wallet.hd.HD_Wallet;
|
||||
import com.samourai.wallet.hd.HD_WalletFactoryGeneric;
|
||||
import com.sparrowwallet.drongo.Drongo;
|
||||
import com.sparrowwallet.drongo.Network;
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.crypto.DumpedPrivateKey;
|
||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.nightjar.http.JavaHttpClientService;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import io.reactivex.Observable;
|
||||
import javafx.concurrent.Service;
|
||||
import javafx.concurrent.Task;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -42,43 +36,19 @@ public class Soroban {
|
|||
|
||||
private final SorobanServer sorobanServer;
|
||||
private final JavaHttpClientService httpClientService;
|
||||
private final PayNymService payNymService;
|
||||
|
||||
private HD_Wallet hdWallet;
|
||||
private BIP47Wallet bip47Wallet;
|
||||
private PaymentCode paymentCode;
|
||||
private int bip47Account;
|
||||
|
||||
public Soroban(Network network, HostAndPort torProxy) {
|
||||
this.sorobanServer = SorobanServer.valueOf(network.getName().toUpperCase());
|
||||
this.httpClientService = new JavaHttpClientService(torProxy);
|
||||
this.payNymService = new PayNymService(httpClientService);
|
||||
}
|
||||
|
||||
public HD_Wallet getHdWallet() {
|
||||
return hdWallet;
|
||||
}
|
||||
|
||||
public PaymentCode getPaymentCode() {
|
||||
return paymentCode;
|
||||
}
|
||||
|
||||
public void setPaymentCode(Wallet wallet) {
|
||||
if(wallet.isEncrypted()) {
|
||||
throw new IllegalStateException("Wallet cannot be encrypted");
|
||||
}
|
||||
|
||||
try {
|
||||
Keystore keystore = wallet.getKeystores().get(0);
|
||||
List<String> words = keystore.getSeed().getMnemonicCode();
|
||||
String passphrase = keystore.getSeed().getPassphrase().asString();
|
||||
byte[] seed = hdWalletFactory.computeSeedFromWords(words);
|
||||
BIP47Wallet bip47Wallet = hdWalletFactory.getBIP47(Utils.bytesToHex(seed), passphrase, sorobanServer.getParams());
|
||||
paymentCode = bip47Util.getPaymentCode(bip47Wallet, wallet.isMasterWallet() ? wallet.getAccountIndex() : wallet.getMasterWallet().getAccountIndex());
|
||||
} catch(Exception e) {
|
||||
throw new IllegalStateException("Could not create payment code", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setHDWallet(Wallet wallet) {
|
||||
if(wallet.isEncrypted()) {
|
||||
throw new IllegalStateException("Wallet cannot be encrypted");
|
||||
|
@ -92,8 +62,7 @@ public class Soroban {
|
|||
String passphrase = keystore.getSeed().getPassphrase().asString();
|
||||
byte[] seed = hdWalletFactory.computeSeedFromWords(words);
|
||||
hdWallet = new HD_Wallet(purpose, new ArrayList<>(words), sorobanServer.getParams(), seed, passphrase);
|
||||
bip47Wallet = hdWalletFactory.getBIP47(hdWallet.getSeedHex(), hdWallet.getPassphrase(), sorobanServer.getParams());
|
||||
paymentCode = bip47Util.getPaymentCode(bip47Wallet, wallet.isMasterWallet() ? wallet.getAccountIndex() : wallet.getMasterWallet().getAccountIndex());
|
||||
bip47Account = wallet.isMasterWallet() ? wallet.getAccountIndex() : wallet.getMasterWallet().getAccountIndex();
|
||||
} catch(Exception e) {
|
||||
throw new IllegalStateException("Could not create Soroban HD wallet ", e);
|
||||
}
|
||||
|
@ -109,8 +78,6 @@ public class Soroban {
|
|||
Soroban soroban = AppServices.getSorobanServices().getSoroban(associatedWallet);
|
||||
if(soroban != null && soroban.getHdWallet() != null) {
|
||||
hdWallet = soroban.hdWallet;
|
||||
bip47Wallet = soroban.bip47Wallet;
|
||||
paymentCode = soroban.paymentCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -120,7 +87,7 @@ public class Soroban {
|
|||
}
|
||||
|
||||
try {
|
||||
return new SparrowCahootsWallet(wallet, hdWallet, sorobanServer, (long)feeRate);
|
||||
return new SparrowCahootsWallet(wallet, hdWallet, bip47Account, sorobanServer, (long)feeRate);
|
||||
} catch(Exception e) {
|
||||
log.error("Could not create cahoots wallet", e);
|
||||
}
|
||||
|
@ -148,47 +115,6 @@ public class Soroban {
|
|||
httpClientService.shutdown();
|
||||
}
|
||||
|
||||
public Observable<Map<String, Object>> createPayNym() {
|
||||
return payNymService.createPayNym(paymentCode);
|
||||
}
|
||||
|
||||
public Observable<Map<String, Object>> updateToken() {
|
||||
return payNymService.updateToken(paymentCode);
|
||||
}
|
||||
|
||||
public Observable<Map<String, Object>> claimPayNym(String authToken, String signature) {
|
||||
return payNymService.claimPayNym(authToken, signature);
|
||||
}
|
||||
|
||||
public Observable<Map<String, Object>> addPaymentCode(String authToken, String signature, boolean segwit) {
|
||||
return payNymService.addPaymentCode(paymentCode, authToken, signature, segwit);
|
||||
}
|
||||
|
||||
public Observable<Map<String, Object>> followPaymentCode(PaymentCode paymentCode, String authToken, String signature) {
|
||||
return payNymService.followPaymentCode(paymentCode, authToken, signature);
|
||||
}
|
||||
|
||||
public Observable<PayNym> getPayNym(String nymIdentifier) {
|
||||
return payNymService.getPayNym(nymIdentifier);
|
||||
}
|
||||
|
||||
public Observable<List<PayNym>> getFollowing() {
|
||||
return payNymService.getPayNym(paymentCode.toString()).map(PayNym::following);
|
||||
}
|
||||
|
||||
public Observable<String> getAuthToken(Map<String, Object> map) {
|
||||
if(map.containsKey("token")) {
|
||||
return Observable.just((String)map.get("token"));
|
||||
}
|
||||
|
||||
return updateToken().map(tokenMap -> (String)tokenMap.get("token"));
|
||||
}
|
||||
|
||||
public String getSignature(String authToken) {
|
||||
ECKey notificationAddressKey = DumpedPrivateKey.fromBase58(bip47Wallet.getAccount(0).addressAt(0).getPrivateKeyString()).getKey();
|
||||
return notificationAddressKey.signMessage(authToken, ScriptType.P2PKH);
|
||||
}
|
||||
|
||||
public static class ShutdownService extends Service<Boolean> {
|
||||
private final Soroban soroban;
|
||||
|
||||
|
|
|
@ -21,37 +21,6 @@ import java.util.stream.Collectors;
|
|||
|
||||
public class SorobanController {
|
||||
private static final Logger log = LoggerFactory.getLogger(SorobanController.class);
|
||||
protected static final Pattern PAYNYM_REGEX = Pattern.compile("\\+[a-z]+[0-9][0-9a-fA-F][0-9a-fA-F]");
|
||||
|
||||
protected void claimPayNym(Soroban soroban, Map<String, Object> createMap, boolean segwit) {
|
||||
if(createMap.get("claimed") == Boolean.FALSE) {
|
||||
soroban.getAuthToken(createMap).subscribe(authToken -> {
|
||||
String signature = soroban.getSignature(authToken);
|
||||
soroban.claimPayNym(authToken, signature).subscribe(claimMap -> {
|
||||
log.debug("Claimed payment code " + claimMap.get("claimed"));
|
||||
soroban.addPaymentCode(authToken, signature, segwit).subscribe(addMap -> {
|
||||
log.debug("Added payment code " + addMap);
|
||||
});
|
||||
}, error -> {
|
||||
soroban.getAuthToken(new HashMap<>()).subscribe(newAuthToken -> {
|
||||
String newSignature = soroban.getSignature(newAuthToken);
|
||||
soroban.claimPayNym(newAuthToken, newSignature).subscribe(claimMap -> {
|
||||
log.debug("Claimed payment code " + claimMap.get("claimed"));
|
||||
soroban.addPaymentCode(newAuthToken, newSignature, segwit).subscribe(addMap -> {
|
||||
log.debug("Added payment code " + addMap);
|
||||
});
|
||||
}, newError -> {
|
||||
log.error("Error claiming PayNym with new authToken", newError);
|
||||
});
|
||||
}, newError -> {
|
||||
log.error("Error retrieving new authToken", newError);
|
||||
});
|
||||
});
|
||||
}, error -> {
|
||||
log.error("Error retrieving authToken", error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected Transaction getTransaction(Cahoots cahoots) throws PSBTParseException {
|
||||
if(cahoots.getPSBT() != null) {
|
||||
|
|
|
@ -19,6 +19,8 @@ import java.util.HashMap;
|
|||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.sparrowwallet.sparrow.AppServices.getTorProxy;
|
||||
|
||||
public class SorobanServices {
|
||||
private static final Logger log = LoggerFactory.getLogger(SorobanServices.class);
|
||||
|
||||
|
@ -51,12 +53,6 @@ public class SorobanServices {
|
|||
return soroban;
|
||||
}
|
||||
|
||||
private HostAndPort getTorProxy() {
|
||||
return AppServices.isTorRunning() ?
|
||||
HostAndPort.fromParts("localhost", TorService.PROXY_PORT) :
|
||||
(Config.get().getProxyServer() == null || Config.get().getProxyServer().isEmpty() || !Config.get().isUseProxy() ? null : HostAndPort.fromString(Config.get().getProxyServer()));
|
||||
}
|
||||
|
||||
public static boolean canWalletMix(Wallet wallet) {
|
||||
return Soroban.SOROBAN_NETWORKS.contains(Network.get())
|
||||
&& wallet.getKeystores().size() == 1
|
||||
|
|
|
@ -15,18 +15,19 @@ import com.sparrowwallet.drongo.wallet.WalletNode;
|
|||
import com.sparrowwallet.sparrow.whirlpool.Whirlpool;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class SparrowCahootsWallet extends SimpleCahootsWallet {
|
||||
private final Wallet wallet;
|
||||
private final int account;
|
||||
private final int bip47Account;
|
||||
|
||||
public SparrowCahootsWallet(Wallet wallet, HD_Wallet bip84w, SorobanServer sorobanServer, long feePerB) throws Exception {
|
||||
public SparrowCahootsWallet(Wallet wallet, HD_Wallet bip84w, int bip47Account, SorobanServer sorobanServer, long feePerB) throws Exception {
|
||||
super(bip84w, sorobanServer.getParams(), wallet.getFreshNode(KeyPurpose.CHANGE).getIndex(), feePerB);
|
||||
this.wallet = wallet;
|
||||
this.account = wallet.getAccountIndex();
|
||||
this.bip47Account = bip47Account;
|
||||
bip84w.getAccount(account).getReceive().setAddrIdx(wallet.getFreshNode(KeyPurpose.RECEIVE).getIndex());
|
||||
bip84w.getAccount(account).getChange().setAddrIdx(wallet.getFreshNode(KeyPurpose.CHANGE).getIndex());
|
||||
}
|
||||
|
@ -67,4 +68,9 @@ public class SparrowCahootsWallet extends SimpleCahootsWallet {
|
|||
public Pair<Integer, Integer> fetchChangeIndex(int account) throws Exception {
|
||||
return Pair.of(wallet.getFreshNode(KeyPurpose.CHANGE).getIndex(), 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBip47Account() {
|
||||
return bip47Account;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,9 @@ import com.sparrowwallet.sparrow.event.FiatCurrencySelectedEvent;
|
|||
import com.sparrowwallet.sparrow.event.OpenWalletsEvent;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.net.ExchangeSource;
|
||||
import com.sparrowwallet.sparrow.paynym.PayNym;
|
||||
import com.sparrowwallet.sparrow.paynym.PayNymAddress;
|
||||
import com.sparrowwallet.sparrow.paynym.PayNymDialog;
|
||||
import com.sparrowwallet.sparrow.soroban.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
|
|
|
@ -19,7 +19,7 @@ import com.sparrowwallet.sparrow.io.Config;
|
|||
import com.sparrowwallet.sparrow.io.Storage;
|
||||
import com.sparrowwallet.sparrow.net.*;
|
||||
import com.sparrowwallet.sparrow.soroban.InitiatorDialog;
|
||||
import com.sparrowwallet.sparrow.soroban.PayNymAddress;
|
||||
import com.sparrowwallet.sparrow.paynym.PayNymAddress;
|
||||
import com.sparrowwallet.sparrow.soroban.SorobanServices;
|
||||
import com.sparrowwallet.sparrow.whirlpool.Whirlpool;
|
||||
import javafx.animation.KeyFrame;
|
||||
|
|
|
@ -29,6 +29,8 @@ import java.util.*;
|
|||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.sparrowwallet.sparrow.AppServices.getTorProxy;
|
||||
|
||||
public class WhirlpoolServices {
|
||||
private static final Logger log = LoggerFactory.getLogger(WhirlpoolServices.class);
|
||||
|
||||
|
@ -61,12 +63,6 @@ public class WhirlpoolServices {
|
|||
return whirlpool;
|
||||
}
|
||||
|
||||
private HostAndPort getTorProxy() {
|
||||
return AppServices.isTorRunning() ?
|
||||
HostAndPort.fromParts("localhost", TorService.PROXY_PORT) :
|
||||
(Config.get().getProxyServer() == null || Config.get().getProxyServer().isEmpty() || !Config.get().isUseProxy() ? null : HostAndPort.fromString(Config.get().getProxyServer()));
|
||||
}
|
||||
|
||||
private void bindDebugAccelerator() {
|
||||
List<Window> windows = whirlpoolMap.keySet().stream().map(walletId -> AppServices.get().getWindowForWallet(walletId)).filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
||||
for(Window window : windows) {
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<?import com.sparrowwallet.sparrow.control.PaymentCodeTextField?>
|
||||
<?import org.controlsfx.glyphfont.Glyph?>
|
||||
|
||||
<StackPane prefHeight="460.0" prefWidth="600.0" stylesheets="@paynym.css, @../general.css" styleClass="paynym-pane" fx:controller="com.sparrowwallet.sparrow.soroban.PayNymController" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml">
|
||||
<StackPane prefHeight="460.0" prefWidth="600.0" stylesheets="@paynym.css, @../general.css" styleClass="paynym-pane" fx:controller="com.sparrowwallet.sparrow.paynym.PayNymController" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml">
|
||||
<VBox spacing="10">
|
||||
<HBox styleClass="title-area">
|
||||
<HBox alignment="CENTER_LEFT">
|
||||
|
|
Loading…
Reference in a new issue