Merge remote-tracking branch 'origin/master' into add-xmr-tx-key-service-2

This commit is contained in:
jmacxx 2020-08-26 21:57:05 -05:00
commit c6f1e5274f
No known key found for this signature in database
GPG key ID: 155297BABFE94A1B
114 changed files with 2819 additions and 448 deletions

View file

@ -57,7 +57,7 @@ configure(subprojects) {
junitVersion = '4.12' junitVersion = '4.12'
jupiterVersion = '5.3.2' jupiterVersion = '5.3.2'
kotlinVersion = '1.3.41' kotlinVersion = '1.3.41'
knowmXchangeVersion = '4.3.3' knowmXchangeVersion = '4.4.2'
langVersion = '3.8' langVersion = '3.8'
logbackVersion = '1.1.11' logbackVersion = '1.1.11'
loggingVersion = '1.2' loggingVersion = '1.2'
@ -378,7 +378,7 @@ configure(project(':desktop')) {
apply plugin: 'witness' apply plugin: 'witness'
apply from: '../gradle/witness/gradle-witness.gradle' apply from: '../gradle/witness/gradle-witness.gradle'
version = '1.3.6-SNAPSHOT' version = '1.3.7'
mainClassName = 'bisq.desktop.app.BisqAppMain' mainClassName = 'bisq.desktop.app.BisqAppMain'
@ -458,20 +458,43 @@ configure(project(':pricenode')) {
dependencies { dependencies {
compile project(":core") compile project(":core")
compileOnly "org.projectlombok:lombok:$lombokVersion"
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
implementation "com.google.code.gson:gson:$gsonVersion" implementation "com.google.code.gson:gson:$gsonVersion"
implementation "commons-codec:commons-codec:$codecVersion" implementation "commons-codec:commons-codec:$codecVersion"
implementation "org.apache.httpcomponents:httpcore:$httpcoreVersion" implementation "org.apache.httpcomponents:httpcore:$httpcoreVersion"
implementation("org.apache.httpcomponents:httpclient:$httpclientVersion") { implementation("org.apache.httpcomponents:httpclient:$httpclientVersion") {
exclude(module: 'commons-codec') exclude(module: 'commons-codec')
} }
compile("org.knowm.xchange:xchange-bitcoinaverage:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-bitbay:$knowmXchangeVersion")
compile("org.knowm.xchange:xchange-btcmarkets:$knowmXchangeVersion")
compile("org.knowm.xchange:xchange-binance:$knowmXchangeVersion")
compile("org.knowm.xchange:xchange-bitfinex:$knowmXchangeVersion")
compile("org.knowm.xchange:xchange-bitflyer:$knowmXchangeVersion")
compile("org.knowm.xchange:xchange-bitstamp:$knowmXchangeVersion")
compile("org.knowm.xchange:xchange-cexio:$knowmXchangeVersion")
compile("org.knowm.xchange:xchange-coinmate:$knowmXchangeVersion")
compile("org.knowm.xchange:xchange-coinmarketcap:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-coinmarketcap:$knowmXchangeVersion")
compile("org.knowm.xchange:xchange-coinone:$knowmXchangeVersion")
compile("org.knowm.xchange:xchange-exmo:$knowmXchangeVersion")
compile("org.knowm.xchange:xchange-hitbtc:$knowmXchangeVersion")
compile("org.knowm.xchange:xchange-huobi:$knowmXchangeVersion")
compile("org.knowm.xchange:xchange-independentreserve:$knowmXchangeVersion")
compile("org.knowm.xchange:xchange-kraken:$knowmXchangeVersion")
compile("org.knowm.xchange:xchange-luno:$knowmXchangeVersion")
compile("org.knowm.xchange:xchange-mercadobitcoin:$knowmXchangeVersion")
compile("org.knowm.xchange:xchange-paribu:$knowmXchangeVersion")
compile("org.knowm.xchange:xchange-poloniex:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-poloniex:$knowmXchangeVersion")
compile("org.knowm.xchange:xchange-quoine:$knowmXchangeVersion")
compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion")
compile("org.springframework.boot:spring-boot-starter-actuator") compile("org.springframework.boot:spring-boot-starter-actuator")
testCompile "org.junit.jupiter:junit-jupiter-api:$jupiterVersion" testCompile "org.junit.jupiter:junit-jupiter-api:$jupiterVersion"
testCompile "org.junit.jupiter:junit-jupiter-params:$jupiterVersion" testCompile "org.junit.jupiter:junit-jupiter-params:$jupiterVersion"
testRuntime("org.junit.jupiter:junit-jupiter-engine:$jupiterVersion") testRuntime("org.junit.jupiter:junit-jupiter-engine:$jupiterVersion")
testCompileOnly "org.projectlombok:lombok:$lombokVersion"
testAnnotationProcessor "org.projectlombok:lombok:$lombokVersion"
} }
test { test {

View file

@ -27,7 +27,7 @@ public class Version {
// VERSION = 0.5.0 introduces proto buffer for the P2P network and local DB and is a not backward compatible update // VERSION = 0.5.0 introduces proto buffer for the P2P network and local DB and is a not backward compatible update
// Therefore all sub versions start again with 1 // Therefore all sub versions start again with 1
// We use semantic versioning with major, minor and patch // We use semantic versioning with major, minor and patch
public static final String VERSION = "1.3.6"; public static final String VERSION = "1.3.7";
public static int getMajorVersion(String version) { public static int getMajorVersion(String version) {
return getSubVersion(version, 0); return getSubVersion(version, 0);

View file

@ -52,6 +52,13 @@ import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.SourceDataLine;
/**
* Prevents that Bisq gets hibernated from the OS. On OSX there is a tool called caffeinate but it seems it does not
* provide the behaviour we need, thus we use the trick to play a almost silent sound file in a loop. This keeps the
* application active even if the OS has moved to hibernate. Hibernating Bisq would cause network degradations and other
* resource limitations which would lead to offers not published or if a taker takes an offer that the trade process is
* at risk to fail due too slow response time.
*/
@Slf4j @Slf4j
@Singleton @Singleton
public class AvoidStandbyModeService { public class AvoidStandbyModeService {
@ -88,7 +95,7 @@ public class AvoidStandbyModeService {
private void start() { private void start() {
isStopped = false; isStopped = false;
if (Utilities.isLinux() || Utilities.isOSX()) { if (Utilities.isLinux()) {
startInhibitor(); startInhibitor();
} else { } else {
new Thread(this::playSilentAudioFile, "AvoidStandbyModeService-thread").start(); new Thread(this::playSilentAudioFile, "AvoidStandbyModeService-thread").start();
@ -125,7 +132,6 @@ public class AvoidStandbyModeService {
private void stopInhibitor() { private void stopInhibitor() {
try { try {
// Cannot toggle off osx caffeinate, but it will shutdown with bisq.
if (Utilities.isLinux()) { if (Utilities.isLinux()) {
if (!isStopped) { if (!isStopped) {
Objects.requireNonNull(stopLinuxInhibitorCountdownLatch).await(); Objects.requireNonNull(stopLinuxInhibitorCountdownLatch).await();
@ -167,7 +173,9 @@ public class AvoidStandbyModeService {
private File getSoundFile() throws IOException, ResourceNotFoundException { private File getSoundFile() throws IOException, ResourceNotFoundException {
File soundFile = new File(config.appDataDir, "prevent-app-nap-silent-sound.aiff"); File soundFile = new File(config.appDataDir, "prevent-app-nap-silent-sound.aiff");
if (!soundFile.exists()) { // We replaced the old file which was 42 MB with a smaller file of 0.8 MB. To enforce replacement we check for
// the size...
if (!soundFile.exists() || soundFile.length() > 42000000) {
FileUtil.resourceToFile("prevent-app-nap-silent-sound.aiff", soundFile); FileUtil.resourceToFile("prevent-app-nap-silent-sound.aiff", soundFile);
} }
return soundFile; return soundFile;
@ -196,7 +204,7 @@ public class AvoidStandbyModeService {
? new String[]{cmd, "--app-id", "Bisq", "--inhibit", "suspend", "--reason", "Avoid Standby", "--inhibit-only"} ? new String[]{cmd, "--app-id", "Bisq", "--inhibit", "suspend", "--reason", "Avoid Standby", "--inhibit-only"}
: new String[]{cmd, "--who", "Bisq", "--what", "sleep", "--why", "Avoid Standby", "--mode", "block", "tail", "-f", "/dev/null"}; : new String[]{cmd, "--who", "Bisq", "--what", "sleep", "--why", "Avoid Standby", "--mode", "block", "tail", "-f", "/dev/null"};
} else { } else {
params = Utilities.isOSX() ? new String[]{cmd, "-w", "" + ProcessHandle.current().pid()} : null; params = null;
} }
} else { } else {
params = null; // fall back to silent audio file player params = null; // fall back to silent audio file player
@ -235,7 +243,6 @@ public class AvoidStandbyModeService {
new ArrayList<>() {{ new ArrayList<>() {{
add(gnomeSessionInhibitPathSpec.get()); // On linux, preferred inhibitor is gnome-session-inhibit, add(gnomeSessionInhibitPathSpec.get()); // On linux, preferred inhibitor is gnome-session-inhibit,
add(systemdInhibitPathSpec.get()); // then fall back to systemd-inhibit if it is installed. add(systemdInhibitPathSpec.get()); // then fall back to systemd-inhibit if it is installed.
add(caffeinatePathSec.get()); // On OSX, caffeinate should be installed.
}}; }};
private final Supplier<Optional<String>> gnomeSessionInhibitPathSpec = () -> private final Supplier<Optional<String>> gnomeSessionInhibitPathSpec = () ->
@ -243,7 +250,4 @@ public class AvoidStandbyModeService {
private final Supplier<Optional<String>> systemdInhibitPathSpec = () -> private final Supplier<Optional<String>> systemdInhibitPathSpec = () ->
cmdPath.apply(new String[]{"/usr/bin/systemd-inhibit", "/bin/systemd-inhibit"}); cmdPath.apply(new String[]{"/usr/bin/systemd-inhibit", "/bin/systemd-inhibit"});
private final Supplier<Optional<String>> caffeinatePathSec = () ->
cmdPath.apply(new String[]{"/usr/bin/caffeinate"});
} }

View file

@ -81,7 +81,7 @@ public class LocalBitcoinNode {
*/ */
private static boolean detect(int port) { private static boolean detect(int port) {
try (Socket socket = new Socket()) { try (Socket socket = new Socket()) {
var address = new InetSocketAddress(InetAddress.getLocalHost(), port); var address = new InetSocketAddress(InetAddress.getLoopbackAddress(), port);
socket.connect(address, CONNECTION_TIMEOUT); socket.connect(address, CONNECTION_TIMEOUT);
log.info("Local Bitcoin node detected on port {}", port); log.info("Local Bitcoin node detected on port {}", port);
return true; return true;

View file

@ -140,6 +140,10 @@ public class OfferUtil {
if (makerFee == null) if (makerFee == null)
return true; return true;
Coin surplusFunds = availableBalance.subtract(makerFee);
if (Restrictions.isDust(surplusFunds)) {
return false; // we can't be left with dust
}
return !availableBalance.subtract(makerFee).isNegative(); return !availableBalance.subtract(makerFee).isNegative();
} }
@ -171,6 +175,10 @@ public class OfferUtil {
if (takerFee == null) if (takerFee == null)
return true; return true;
Coin surplusFunds = availableBalance.subtract(takerFee);
if (Restrictions.isDust(surplusFunds)) {
return false; // we can't be left with dust
}
return !availableBalance.subtract(takerFee).isNegative(); return !availableBalance.subtract(takerFee).isNegative();
} }

View file

@ -36,11 +36,11 @@ import javax.annotation.Nullable;
@Slf4j @Slf4j
public class ProvidersRepository { public class ProvidersRepository {
private static final List<String> DEFAULT_NODES = Arrays.asList( private static final List<String> DEFAULT_NODES = Arrays.asList(
"http://xc3nh4juf2hshy7e.onion/", // @emzy "http://wizpriceje6q5tdrxkyiazsgu7irquiqjy2dptezqhrtu7l2qelqktid.onion/", // @wiz
"http://ceaanhbvluug4we6.onion/", // @mrosseel "http://emzypricpidesmyqg2hc6dkwitqzaxrqnpkdg3ae2wef5znncu2ambqd.onion/", // @emzy
"http://44mgyoe2b6oqiytt.onion/", // @devinbileck "http://aprcndeiwdrkbf4fq7iozxbd27dl72oeo76n7zmjwdi4z34agdrnheyd.onion/", // @mrosseel
"http://62nvujg5iou3vu3i.onion/", // @alexej996 "http://devinpndvdwll4wiqcyq5e7itezmarg7rzicrvf6brzkwxdm374kmmyd.onion/", // @devinbileck
"http://gztmprecgqjq64zh.onion/" // @wiz "http://ro7nv73awqs3ga2qtqeqawrjpbxwarsazznszvr6whv7tes5ehffopid.onion/" // @alexej996
); );
private final Config config; private final Config config;
@ -110,7 +110,7 @@ public class ProvidersRepository {
// If we run in localhost mode we don't have the tor node running, so we need a clearnet host // If we run in localhost mode we don't have the tor node running, so we need a clearnet host
// Use localhost for using a locally running provider // Use localhost for using a locally running provider
// providerAsString = Collections.singletonList("http://localhost:8080/"); // providerAsString = Collections.singletonList("http://localhost:8080/");
providers = Collections.singletonList("http://174.138.104.137:8080/"); // @miker providers = Collections.singletonList("https://price.bisq.wiz.biz/"); // @wiz
} else { } else {
providers = DEFAULT_NODES; providers = DEFAULT_NODES;
} }

View file

@ -83,7 +83,7 @@ public class PriceFeedService {
private String currencyCode; private String currencyCode;
private final StringProperty currencyCodeProperty = new SimpleStringProperty(); private final StringProperty currencyCodeProperty = new SimpleStringProperty();
private final IntegerProperty updateCounter = new SimpleIntegerProperty(0); private final IntegerProperty updateCounter = new SimpleIntegerProperty(0);
private long epochInSecondAtLastRequest; private long epochInMillisAtLastRequest;
private Map<String, Long> timeStampMap = new HashMap<>(); private Map<String, Long> timeStampMap = new HashMap<>();
private long retryDelay = 1; private long retryDelay = 1;
private long requestTs; private long requestTs;
@ -280,24 +280,8 @@ public class PriceFeedService {
return updateCounter; return updateCounter;
} }
public Date getLastRequestTimeStampBtcAverage() { public Date getLastRequestTimeStamp() {
return new Date(epochInSecondAtLastRequest); return new Date(epochInMillisAtLastRequest);
}
public Date getLastRequestTimeStampPoloniex() {
Long ts = timeStampMap.get("btcAverageTs");
if (ts != null) {
return new Date(ts);
} else
return new Date();
}
public Date getLastRequestTimeStampCoinmarketcap() {
Long ts = timeStampMap.get("coinmarketcapTs");
if (ts != null) {
return new Date(ts);
} else
return new Date();
} }
public void applyLatestBisqMarketPrice(Set<TradeStatistics2> tradeStatisticsSet) { public void applyLatestBisqMarketPrice(Set<TradeStatistics2> tradeStatisticsSet) {
@ -400,7 +384,12 @@ public class PriceFeedService {
UserThread.execute(() -> { UserThread.execute(() -> {
checkNotNull(result, "Result must not be null at requestAllPrices"); checkNotNull(result, "Result must not be null at requestAllPrices");
timeStampMap = result.first; timeStampMap = result.first;
epochInSecondAtLastRequest = timeStampMap.get("btcAverageTs");
// Each currency rate has a different timestamp, depending on when
// the pricenode aggregate rate was calculated
// However, the request timestamp is when the pricenode was queried
epochInMillisAtLastRequest = System.currentTimeMillis();
final Map<String, MarketPrice> priceMap = result.second; final Map<String, MarketPrice> priceMap = result.second;
cache.putAll(priceMap); cache.putAll(priceMap);

View file

@ -29,6 +29,7 @@ import org.bitcoinj.core.Coin;
import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionInput; import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutPoint;
import org.bitcoinj.core.TransactionOutput; import org.bitcoinj.core.TransactionOutput;
import java.util.List; import java.util.List;
@ -70,6 +71,12 @@ public class DelayedPayoutTxValidation {
} }
} }
public static class InvalidInputException extends Exception {
InvalidInputException(String msg) {
super(msg);
}
}
public static void validatePayoutTx(Trade trade, public static void validatePayoutTx(Trade trade,
Transaction delayedPayoutTx, Transaction delayedPayoutTx,
DaoFacade daoFacade, DaoFacade daoFacade,
@ -184,4 +191,19 @@ public class DelayedPayoutTxValidation {
throw new DonationAddressException(errorMsg); throw new DonationAddressException(errorMsg);
} }
} }
public static void validatePayoutTxInput(Transaction depositTx,
Transaction delayedPayoutTx)
throws InvalidInputException {
TransactionInput input = delayedPayoutTx.getInput(0);
checkNotNull(input, "delayedPayoutTx.getInput(0) must not be null");
// input.getConnectedOutput() is null as the tx is not committed at that point
TransactionOutPoint outpoint = input.getOutpoint();
if (!outpoint.getHash().toString().equals(depositTx.getHashAsString()) || outpoint.getIndex() != 0) {
throw new InvalidInputException("Input of delayed payout transaction does not point to output of deposit tx.\n" +
"Delayed payout tx=" + delayedPayoutTx + "\n" +
"Deposit tx=" + depositTx);
}
}
} }

View file

@ -27,6 +27,8 @@ import org.bitcoinj.core.Transaction;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j @Slf4j
public class BuyerVerifiesFinalDelayedPayoutTx extends TradeTask { public class BuyerVerifiesFinalDelayedPayoutTx extends TradeTask {
@SuppressWarnings({"unused"}) @SuppressWarnings({"unused"})
@ -40,18 +42,25 @@ public class BuyerVerifiesFinalDelayedPayoutTx extends TradeTask {
runInterceptHook(); runInterceptHook();
Transaction delayedPayoutTx = trade.getDelayedPayoutTx(); Transaction delayedPayoutTx = trade.getDelayedPayoutTx();
checkNotNull(delayedPayoutTx, "trade.getDelayedPayoutTx() must not be null");
// Check again tx // Check again tx
DelayedPayoutTxValidation.validatePayoutTx(trade, DelayedPayoutTxValidation.validatePayoutTx(trade,
delayedPayoutTx, delayedPayoutTx,
processModel.getDaoFacade(), processModel.getDaoFacade(),
processModel.getBtcWalletService()); processModel.getBtcWalletService());
// Now as we know the deposit tx we can also verify the input
Transaction depositTx = trade.getDepositTx();
checkNotNull(depositTx, "trade.getDepositTx() must not be null");
DelayedPayoutTxValidation.validatePayoutTxInput(depositTx, delayedPayoutTx);
complete(); complete();
} catch (DelayedPayoutTxValidation.DonationAddressException | } catch (DelayedPayoutTxValidation.DonationAddressException |
DelayedPayoutTxValidation.MissingDelayedPayoutTxException | DelayedPayoutTxValidation.MissingDelayedPayoutTxException |
DelayedPayoutTxValidation.InvalidTxException | DelayedPayoutTxValidation.InvalidTxException |
DelayedPayoutTxValidation.InvalidLockTimeException | DelayedPayoutTxValidation.InvalidLockTimeException |
DelayedPayoutTxValidation.AmountMismatchException e) { DelayedPayoutTxValidation.AmountMismatchException |
DelayedPayoutTxValidation.InvalidInputException e) {
failed(e.getMessage()); failed(e.getMessage());
} catch (Throwable t) { } catch (Throwable t) {
failed(t); failed(t);

View file

@ -1,13 +1,13 @@
# nodeaddress.onion:port [(@owner,@backup)] # nodeaddress.onion:port [(@owner,@backup)]
5quyxpxheyvzmb2d.onion:8000 (@miker) 5quyxpxheyvzmb2d.onion:8000 (@miker)
s67qglwhkgkyvr74.onion:8000 (@emzy) s67qglwhkgkyvr74.onion:8000 (@emzy)
3f3cu2yw7u457ztq.onion:8000 (@devinbileck,@ripcurlx)
723ljisnynbtdohi.onion:8000 (@emzy) 723ljisnynbtdohi.onion:8000 (@emzy)
rm7b56wbrcczpjvl.onion:8000 (@miker) rm7b56wbrcczpjvl.onion:8000 (@miker)
fl3mmribyxgrv63c.onion:8000 (@devinbileck,@ripcurlx)
wizseedscybbttk4bmb2lzvbuk2jtect37lcpva4l3twktmkzemwbead.onion:8000 (@wiz) wizseedscybbttk4bmb2lzvbuk2jtect37lcpva4l3twktmkzemwbead.onion:8000 (@wiz)
wizseed3d376esppbmbjxk2fhk2jg5fpucddrzj2kxtbxbx4vrnwclad.onion:8000 (@wiz) wizseed3d376esppbmbjxk2fhk2jg5fpucddrzj2kxtbxbx4vrnwclad.onion:8000 (@wiz)
wizseed7ab2gi3x267xahrp2pkndyrovczezzb46jk6quvguciuyqrid.onion:8000 (@wiz) wizseed7ab2gi3x267xahrp2pkndyrovczezzb46jk6quvguciuyqrid.onion:8000 (@wiz)
devinv3rhon24gqf5v6ondoqgyrbzyqihzyouzv7ptltsewhfmox2zqd.onion:8000 (@devinbileck) devinv3rhon24gqf5v6ondoqgyrbzyqihzyouzv7ptltsewhfmox2zqd.onion:8000 (@devinbileck)
devinsn2teu33efff62bnvwbxmfgbfjlgqsu3ad4b4fudx3a725eqnyd.onion:8000 (@devinbileck)
devinsn3xuzxhj6pmammrxpydhwwmwp75qkksedo5dn2tlmu7jggo7id.onion:8000 (@devinbileck)
sn3emzy56u3mxzsr4geysc52feoq5qt7ja56km6gygwnszkshunn2sid.onion:8000 (@emzy) sn3emzy56u3mxzsr4geysc52feoq5qt7ja56km6gygwnszkshunn2sid.onion:8000 (@emzy)
sn2bisqad7ncazupgbd3dcedqh5ptirgwofw63djwpdtftwhddo75oid.onion:8000 (@miker) sn2bisqad7ncazupgbd3dcedqh5ptirgwofw63djwpdtftwhddo75oid.onion:8000 (@miker)

View file

@ -243,7 +243,6 @@ mainView.marketPrice.bisqInternalPrice=Price of latest Bisq trade
mainView.marketPrice.tooltip.bisqInternalPrice=There is no market price from external price feed providers available.\n\ mainView.marketPrice.tooltip.bisqInternalPrice=There is no market price from external price feed providers available.\n\
The displayed price is the latest Bisq trade price for that currency. The displayed price is the latest Bisq trade price for that currency.
mainView.marketPrice.tooltip=Market price is provided by {0}{1}\nLast update: {2}\nProvider node URL: {3} mainView.marketPrice.tooltip=Market price is provided by {0}{1}\nLast update: {2}\nProvider node URL: {3}
mainView.marketPrice.tooltip.altcoinExtra=If the altcoin is not available at Poloniex we use https://coinmarketcap.com
mainView.balance.available=Available balance mainView.balance.available=Available balance
mainView.balance.reserved=Reserved in offers mainView.balance.reserved=Reserved in offers
mainView.balance.locked=Locked in trades mainView.balance.locked=Locked in trades
@ -1179,10 +1178,9 @@ setting.about.support=Support Bisq
setting.about.def=Bisq is not a company—it is a project open to the community. If you want to participate or support Bisq please follow the links below. setting.about.def=Bisq is not a company—it is a project open to the community. If you want to participate or support Bisq please follow the links below.
setting.about.contribute=Contribute setting.about.contribute=Contribute
setting.about.providers=Data providers setting.about.providers=Data providers
setting.about.apisWithFee=Bisq uses 3rd party APIs for Fiat and Altcoin market prices as well as for mining fee estimation. +setting.about.apisWithFee=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices, and Bisq Mempool Nodes for mining fee estimation.
setting.about.apis=Bisq uses 3rd party APIs for Fiat and Altcoin market prices. +setting.about.apis=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices.
setting.about.pricesProvided=Market prices provided by setting.about.pricesProvided=Market prices provided by
setting.about.pricesProviders={0}, {1} and {2}
setting.about.feeEstimation.label=Mining fee estimation provided by setting.about.feeEstimation.label=Mining fee estimation provided by
setting.about.versionDetails=Version details setting.about.versionDetails=Version details
setting.about.version=Application version setting.about.version=Application version
@ -1539,6 +1537,10 @@ You should write down the seed words on a sheet of paper. Do not save them on yo
Please note that the seed words are NOT a replacement for a backup.\n\ Please note that the seed words are NOT a replacement for a backup.\n\
You need to create a backup of the whole application directory from the \"Account/Backup\" screen to recover application state and data.\n\ You need to create a backup of the whole application directory from the \"Account/Backup\" screen to recover application state and data.\n\
Importing seed words is only recommended for emergency cases. The application will not be functional without a proper backup of the database files and keys! Importing seed words is only recommended for emergency cases. The application will not be functional without a proper backup of the database files and keys!
account.seed.backup.warning=Please note that the seed words are NOT a replacement for a backup.\n\
You need to create a backup of the whole application directory from the \"Account/Backup\" screen to recover application state and data.\n\
Importing seed words is only recommended for emergency cases. The application will not be functional without a proper backup of the database files and keys!\n\n\
See the wiki page https://bisq.wiki/Backing_up_application_data for extended info.
account.seed.warn.noPw.msg=You have not setup a wallet password which would protect the display of the seed words.\n\n\ account.seed.warn.noPw.msg=You have not setup a wallet password which would protect the display of the seed words.\n\n\
Do you want to display the seed words? Do you want to display the seed words?
account.seed.warn.noPw.yes=Yes, and don't ask me again account.seed.warn.noPw.yes=Yes, and don't ask me again
@ -3142,12 +3144,15 @@ payment.cashDeposit.info=Please confirm your bank allows you to send cash deposi
payment.revolut.info=Please be sure that the phone number you used for your Revolut account is registered at Revolut \ payment.revolut.info=Please be sure that the phone number you used for your Revolut account is registered at Revolut \
otherwise the BTC buyer cannot send you the funds. otherwise the BTC buyer cannot send you the funds.
payment.usPostalMoneyOrder.info=Money orders are one of the more private fiat purchase methods available on Bisq.\n\n\ payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\
However, please be aware of potentially increased risks associated with their use. Bisq will not bear any \ \n\
responsibility in case a sent money order is stolen, and the mediator or arbitrator will in such cases award the BTC \ - BTC buyers must write the BTC Sellers name in both the Payer and the Payees fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n\
to the sender of the money order, provided they can produce tracking information and receipts. \ - BTC buyers must send the USPMO to the BTC seller with Delivery Confirmation.\n\
It may be advisable for the sender to write the BTC seller's name on the money order, in order to minimize the \ \n\
risk that the money order is cashed by someone else. In the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Bisq mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\n\
Failure to provide the required information to the Mediator or Arbitrator will result in losing the dispute case.\n\n\
In all dispute cases, the USPMO sender bears 100% of the burden of responsibility in providing evidence/proof to the Mediator or Arbitrator.\n\n\
If you do not understand these requirements, do not trade using USPMO on Bisq.
payment.f2f.contact=Contact info payment.f2f.contact=Contact info
payment.f2f.contact.prompt=How you want to get contacted by the trading peer? (email address, phone number,...) payment.f2f.contact.prompt=How you want to get contacted by the trading peer? (email address, phone number,...)

View file

@ -242,7 +242,6 @@ mainView.marketPriceWithProvider.label=Marktpreis von {0}
mainView.marketPrice.bisqInternalPrice=Preis des letzten Bisq-Handels mainView.marketPrice.bisqInternalPrice=Preis des letzten Bisq-Handels
mainView.marketPrice.tooltip.bisqInternalPrice=Es ist kein Marktpreis von externen Marktpreis-Anbietern verfügbar.\nDer angezeigte Preis, ist der letzte Bisq-Handelspreis für diese Währung. mainView.marketPrice.tooltip.bisqInternalPrice=Es ist kein Marktpreis von externen Marktpreis-Anbietern verfügbar.\nDer angezeigte Preis, ist der letzte Bisq-Handelspreis für diese Währung.
mainView.marketPrice.tooltip=Marktpreis bereitgestellt von {0}{1}\nLetzte Aktualisierung: {2}\nURL des Knoten-Anbieters: {3} mainView.marketPrice.tooltip=Marktpreis bereitgestellt von {0}{1}\nLetzte Aktualisierung: {2}\nURL des Knoten-Anbieters: {3}
mainView.marketPrice.tooltip.altcoinExtra=Falls der Altcoin nicht auf Poloniex verfügbar ist nutzen wir https://coinmarketcap.com
mainView.balance.available=Verfügbarer Betrag mainView.balance.available=Verfügbarer Betrag
mainView.balance.reserved=In Angeboten reserviert mainView.balance.reserved=In Angeboten reserviert
mainView.balance.locked=In Trades gesperrt mainView.balance.locked=In Trades gesperrt
@ -355,8 +354,8 @@ shared.notSigned.noNeed=Dieser Kontotyp verwendet keine Unterzeichnung
offerbook.nrOffers=Anzahl der Angebote: {0} offerbook.nrOffers=Anzahl der Angebote: {0}
offerbook.volume={0} (min - max) offerbook.volume={0} (min - max)
offerbook.deposit=Deposit BTC (%) offerbook.deposit=Kaution BTC (%)
offerbook.deposit.help=Deposit paid by each trader to guarantee the trade. Will be returned when the trade is completed. offerbook.deposit.help=Kaution, die von jedem Trader gezahlt wird, um den Trade zu garantieren. Wird zurückgezahlt, wenn der Trade abgeschlossen ist.
offerbook.createOfferToBuy=Neues Angebot erstellen, um {0} zu kaufen offerbook.createOfferToBuy=Neues Angebot erstellen, um {0} zu kaufen
offerbook.createOfferToSell=Neues Angebot erstellen, um {0} zu verkaufen offerbook.createOfferToSell=Neues Angebot erstellen, um {0} zu verkaufen
@ -464,7 +463,7 @@ createOffer.tac=Mit der Erstellung dieses Angebots stimme ich zu, mit jedem Hän
createOffer.currencyForFee=Handelsgebühr createOffer.currencyForFee=Handelsgebühr
createOffer.setDeposit=Kaution des Käufers festlegen (%) createOffer.setDeposit=Kaution des Käufers festlegen (%)
createOffer.setDepositAsBuyer=Meine Kaution als Käufer festlegen (%) createOffer.setDepositAsBuyer=Meine Kaution als Käufer festlegen (%)
createOffer.setDepositForBothTraders=Set both traders' security deposit (%) createOffer.setDepositForBothTraders=Legen Sie die Kaution beider Trader fest (%)
createOffer.securityDepositInfo=Die Kaution ihres Käufers wird {0} createOffer.securityDepositInfo=Die Kaution ihres Käufers wird {0}
createOffer.securityDepositInfoAsBuyer=Ihre Kaution als Käufer wird {0} createOffer.securityDepositInfoAsBuyer=Ihre Kaution als Käufer wird {0}
createOffer.minSecurityDepositUsed=Min. Kaution des Käufers wird verwendet createOffer.minSecurityDepositUsed=Min. Kaution des Käufers wird verwendet
@ -683,7 +682,7 @@ portfolio.pending.step5_buyer.refunded=Rückerstattete Kaution
portfolio.pending.step5_buyer.withdrawBTC=Ihre Bitcoins abheben portfolio.pending.step5_buyer.withdrawBTC=Ihre Bitcoins abheben
portfolio.pending.step5_buyer.amount=Abzuhebender Betrag portfolio.pending.step5_buyer.amount=Abzuhebender Betrag
portfolio.pending.step5_buyer.withdrawToAddress=An diese Adresse abheben portfolio.pending.step5_buyer.withdrawToAddress=An diese Adresse abheben
portfolio.pending.step5_buyer.moveToBisqWallet=Keep funds in Bisq wallet portfolio.pending.step5_buyer.moveToBisqWallet=Gelder in der Bisq Wallet aufbewahren
portfolio.pending.step5_buyer.withdrawExternal=An externe Wallet abheben portfolio.pending.step5_buyer.withdrawExternal=An externe Wallet abheben
portfolio.pending.step5_buyer.alreadyWithdrawn=Ihre Gelder wurden bereits abgehoben.\nBitte überprüfen Sie den Transaktionsverlauf. portfolio.pending.step5_buyer.alreadyWithdrawn=Ihre Gelder wurden bereits abgehoben.\nBitte überprüfen Sie den Transaktionsverlauf.
portfolio.pending.step5_buyer.confirmWithdrawal=Anfrage zum Abheben bestätigen portfolio.pending.step5_buyer.confirmWithdrawal=Anfrage zum Abheben bestätigen
@ -1015,7 +1014,6 @@ setting.about.providers=Datenanbieter
setting.about.apisWithFee=Bisq nutzt für Fiatgeld- und Altcoin-Marktpreise sowie geschätzte Mining-Gebühren die APIs 3tr. setting.about.apisWithFee=Bisq nutzt für Fiatgeld- und Altcoin-Marktpreise sowie geschätzte Mining-Gebühren die APIs 3tr.
setting.about.apis=Bisq nutzt für Fiatgeld- und Altcoin-Marktpreise die APIs 3tr. setting.about.apis=Bisq nutzt für Fiatgeld- und Altcoin-Marktpreise die APIs 3tr.
setting.about.pricesProvided=Marktpreise zur Verfügung gestellt von setting.about.pricesProvided=Marktpreise zur Verfügung gestellt von
setting.about.pricesProviders={0}, {1} und {2}
setting.about.feeEstimation.label=Geschätzte Mining-Gebühr bereitgestellt von setting.about.feeEstimation.label=Geschätzte Mining-Gebühr bereitgestellt von
setting.about.versionDetails=Versionsdetails setting.about.versionDetails=Versionsdetails
setting.about.version=Anwendungsversion setting.about.version=Anwendungsversion
@ -1153,6 +1151,7 @@ account.password.info=Mit Passwortschutz müssen Sie Ihr Passwort eingeben, sowo
account.seed.backup.title=Backup der Seed-Wörter Ihrer Wallet erstellen account.seed.backup.title=Backup der Seed-Wörter Ihrer Wallet erstellen
account.seed.info=Bitte schreiben Sie die sowohl Seed-Wörter als auch das Datum auf! Mit diesen Seed-Wörtern und dem Datum können Sie Ihre Wallet jederzeit wiederherstellen.\nDie Seed-Wörter werden für die BTC- und BSQ-Wallet genutzt.\n\nSchreiben Sie die Seed-Wörter auf ein Blatt Papier schreiben und speichern Sie sie nicht auf Ihrem Computer.\n\nBitte beachten Sie, dass die Seed-Wörter KEIN Ersatz für ein Backup sind.\nSie müssen ein Backup des gesamten Anwendungsverzeichnisses unter \"Konto/Backup\" erstellen, um den ursprünglichen Zustand der Anwendung wiederherstellen zu können.\nDas Importieren der Seed-Wörter wird nur für Notfälle empfohlen. Die Anwendung wird ohne richtiges Backup der Datenbankdateien und Schlüssel nicht funktionieren! account.seed.info=Bitte schreiben Sie die sowohl Seed-Wörter als auch das Datum auf! Mit diesen Seed-Wörtern und dem Datum können Sie Ihre Wallet jederzeit wiederherstellen.\nDie Seed-Wörter werden für die BTC- und BSQ-Wallet genutzt.\n\nSchreiben Sie die Seed-Wörter auf ein Blatt Papier schreiben und speichern Sie sie nicht auf Ihrem Computer.\n\nBitte beachten Sie, dass die Seed-Wörter KEIN Ersatz für ein Backup sind.\nSie müssen ein Backup des gesamten Anwendungsverzeichnisses unter \"Konto/Backup\" erstellen, um den ursprünglichen Zustand der Anwendung wiederherstellen zu können.\nDas Importieren der Seed-Wörter wird nur für Notfälle empfohlen. Die Anwendung wird ohne richtiges Backup der Datenbankdateien und Schlüssel nicht funktionieren!
account.seed.backup.warning=Please note that the seed words are NOT a replacement for a backup.\nYou need to create a backup of the whole application directory from the \"Account/Backup\" screen to recover application state and data.\nImporting seed words is only recommended for emergency cases. The application will not be functional without a proper backup of the database files and keys!\n\nSee the wiki page https://bisq.wiki/Backing_up_application_data for extended info.
account.seed.warn.noPw.msg=Sie haben kein Wallet-Passwort festgelegt, was das Anzeigen der Seed-Wörter schützen würde.\n\nMöchten Sie die Seed-Wörter jetzt anzeigen? account.seed.warn.noPw.msg=Sie haben kein Wallet-Passwort festgelegt, was das Anzeigen der Seed-Wörter schützen würde.\n\nMöchten Sie die Seed-Wörter jetzt anzeigen?
account.seed.warn.noPw.yes=Ja, und nicht erneut fragen account.seed.warn.noPw.yes=Ja, und nicht erneut fragen
account.seed.enterPw=Geben Sie Ihr Passwort ein um die Seed-Wörter zu sehen account.seed.enterPw=Geben Sie Ihr Passwort ein um die Seed-Wörter zu sehen
@ -1999,7 +1998,7 @@ filterWindow.disableDaoBelowVersion=Min. für DAO erforderliche Version
filterWindow.disableTradeBelowVersion=Min. zum Handeln erforderliche Version filterWindow.disableTradeBelowVersion=Min. zum Handeln erforderliche Version
filterWindow.add=Filter hinzufügen filterWindow.add=Filter hinzufügen
filterWindow.remove=Filter entfernen filterWindow.remove=Filter entfernen
filterWindow.btcFeeReceiverAddresses=BTC fee receiver addresses filterWindow.btcFeeReceiverAddresses=BTC Gebühr Empfänger-Adressen
offerDetailsWindow.minBtcAmount=Min. BTC-Betrag offerDetailsWindow.minBtcAmount=Min. BTC-Betrag
offerDetailsWindow.min=(min. {0}) offerDetailsWindow.min=(min. {0})
@ -2217,22 +2216,22 @@ popup.accountSigning.signedByPeer=Eines Ihrer Zahlungskonten wurde von einem Tra
popup.accountSigning.peerLimitLifted=Das anfängliche Limit für eines Ihrer Konten wurde aufgehoben.\n\n{0} popup.accountSigning.peerLimitLifted=Das anfängliche Limit für eines Ihrer Konten wurde aufgehoben.\n\n{0}
popup.accountSigning.peerSigner=Eines Ihrer Konten ist reif genug, um andere Zahlungskonten zu unterzeichnen, und das anfängliche Limit für eines Ihrer Konten wurde aufgehoben.\n\n{0} popup.accountSigning.peerSigner=Eines Ihrer Konten ist reif genug, um andere Zahlungskonten zu unterzeichnen, und das anfängliche Limit für eines Ihrer Konten wurde aufgehoben.\n\n{0}
popup.accountSigning.singleAccountSelect.headline=Select account age witness popup.accountSigning.singleAccountSelect.headline=Zeuge für Konto-Alter auswählen
popup.accountSigning.singleAccountSelect.description=Search for account age witness. popup.accountSigning.singleAccountSelect.description=Nach Zeuge für Konto-Alter suchen
popup.accountSigning.singleAccountSelect.datePicker=Select point of time for signing popup.accountSigning.singleAccountSelect.datePicker=Zeitpunkt für Unterzeichnung auswählen
popup.accountSigning.confirmSingleAccount.headline=Confirm selected account age witness popup.accountSigning.confirmSingleAccount.headline=Ausgewählten Zeugen für Konto-Alter bestätigen
popup.accountSigning.confirmSingleAccount.selectedHash=Selected witness hash popup.accountSigning.confirmSingleAccount.selectedHash=Ausgewählter Zeugen-Hash
popup.accountSigning.confirmSingleAccount.button=Sign account age witness popup.accountSigning.confirmSingleAccount.button=Zeuge für Konto-Alter unterzeichnen
popup.accountSigning.successSingleAccount.description=Witness {0} was signed popup.accountSigning.successSingleAccount.description=Zeuge {0} wurde unterzeichnet
popup.accountSigning.successSingleAccount.success.headline=Success popup.accountSigning.successSingleAccount.success.headline=Erfolg
popup.accountSigning.successSingleAccount.signError=Failed to sign witness, {0} popup.accountSigning.successSingleAccount.signError=Zeuge nicht unterzeichnet, {0}
popup.accountSigning.unsignedPubKeys.headline=Unsigned Pubkeys popup.accountSigning.unsignedPubKeys.headline=Nicht unterzeichnete Pubkeys
popup.accountSigning.unsignedPubKeys.sign=Sign Pubkeys popup.accountSigning.unsignedPubKeys.sign=Pubkeys unterzeichnen
popup.accountSigning.unsignedPubKeys.signed=Pubkeys were signed popup.accountSigning.unsignedPubKeys.signed=Pubkeys wurden unterzeichnet
popup.accountSigning.unsignedPubKeys.result.headline=Signing completed popup.accountSigning.unsignedPubKeys.result.headline=Unterzeichnung abgeschlossen
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys popup.accountSigning.unsignedPubKeys.result.signed=Unterzeichnete Pubkeys
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign popup.accountSigning.unsignedPubKeys.result.failed=Fehler bei Unterzeichnung
#################################################################### ####################################################################
# Notifications # Notifications
@ -2509,13 +2508,13 @@ payment.accountType=Kontotyp
payment.checking=Überprüfe payment.checking=Überprüfe
payment.savings=Ersparnisse payment.savings=Ersparnisse
payment.personalId=Personalausweis payment.personalId=Personalausweis
payment.clearXchange.info=Zelle is a money transfer service that works best *through* another bank.\n\n1. Check this page to see if (and how) your bank works with Zelle:\nhttps://www.zellepay.com/get-started\n\n2. Take special note of your transfer limits—sending limits vary by bank, and banks often specify separate daily, weekly, and monthly limits.\n\n3. If your bank does not work with Zelle, you can still use it through the Zelle mobile app, but your transfer limits will be much lower.\n\n4. The name specified on your Bisq account MUST match the name on your Zelle/bank account. \n\nIf you cannot complete a Zelle transaction as specified in your trade contract, you may lose some (or all) of your security deposit.\n\nBecause of Zelle''s somewhat higher chargeback risk, sellers are advised to contact unsigned buyers through email or SMS to verify that the buyer really owns the Zelle account specified in Bisq. payment.clearXchange.info=Zelle ist ein Geldtransferdienst, der am besten *durch* eine andere Bank funktioniert.\n\n1. Sehen Sie auf dieser Seite nach, ob (und wie) Ihre Bank mit Zelle zusammenarbeitet:\nhttps://www.zellepay.com/get-started\n\n2. Achten Sie besonders auf Ihre Überweisungslimits - die Sendelimits variieren je nach Bank, und die Banken geben oft separate Tages-, Wochen- und Monatslimits an.\n\n3. Wenn Ihre Bank nicht mit Zelle zusammenarbeitet, können Sie sie trotzdem über die Zelle Mobile App benutzen, aber Ihre Überweisungslimits werden viel niedriger sein.\n\n4. Der auf Ihrem Bisq-Konto angegebene Name MUSS mit dem Namen auf Ihrem Zelle/Bankkonto übereinstimmen. \n\nWenn Sie eine Zelle Transaktion nicht wie in Ihrem Handelsvertrag angegeben durchführen können, verlieren Sie möglicherweise einen Teil (oder die gesamte) Kaution.\n\nWegen des etwas höheren Chargeback-Risikos von Zelle wird Verkäufern empfohlen, nicht unterzeichnete Käufer per E-Mail oder SMS zu kontaktieren, um zu überprüfen, ob der Käufer wirklich das in Bisq angegebene Zelle-Konto besitzt.
payment.fasterPayments.newRequirements.info=Einige Banken haben damit begonnen, den vollständigen Namen des Empfängers für Faster Payments Überweisungen zu überprüfen. Ihr aktuelles Faster Payments-Konto gibt keinen vollständigen Namen an.\n\nBitte erwägen Sie, Ihr Faster Payments-Konto in Bisq neu einzurichten, um zukünftigen {0} Käufern einen vollständigen Namen zu geben.\n\nWenn Sie das Konto neu erstellen, stellen Sie sicher, dass Sie die genaue Bankleitzahl, Kontonummer und die "Salt"-Werte für die Altersverifikation von Ihrem alten Konto auf Ihr neues Konto kopieren. Dadurch wird sichergestellt, dass das Alter und der Unterschriftsstatus Ihres bestehenden Kontos erhalten bleiben. payment.fasterPayments.newRequirements.info=Einige Banken haben damit begonnen, den vollständigen Namen des Empfängers für Faster Payments Überweisungen zu überprüfen. Ihr aktuelles Faster Payments-Konto gibt keinen vollständigen Namen an.\n\nBitte erwägen Sie, Ihr Faster Payments-Konto in Bisq neu einzurichten, um zukünftigen {0} Käufern einen vollständigen Namen zu geben.\n\nWenn Sie das Konto neu erstellen, stellen Sie sicher, dass Sie die genaue Bankleitzahl, Kontonummer und die "Salt"-Werte für die Altersverifikation von Ihrem alten Konto auf Ihr neues Konto kopieren. Dadurch wird sichergestellt, dass das Alter und der Unterschriftsstatus Ihres bestehenden Kontos erhalten bleiben.
payment.moneyGram.info=Wenn MoneyGram verwendet wird, muss der BTC Käufer die MoneyGram und ein Foto der Quittung per E-Mail an den BTC-Verkäufer senden. Die Quittung muss den vollständigen Namen, das Land, das Bundesland des Verkäufers und den Betrag deutlich zeigen. Der Käufer bekommt die E-Mail-Adresse des Verkäufers im Handelsprozess angezeigt. payment.moneyGram.info=Wenn MoneyGram verwendet wird, muss der BTC Käufer die MoneyGram und ein Foto der Quittung per E-Mail an den BTC-Verkäufer senden. Die Quittung muss den vollständigen Namen, das Land, das Bundesland des Verkäufers und den Betrag deutlich zeigen. Der Käufer bekommt die E-Mail-Adresse des Verkäufers im Handelsprozess angezeigt.
payment.westernUnion.info=Wenn Western Union verwendet wird, muss der BTC Käufer die MTCN (Tracking-Nummer) und ein Foto der Quittung per E-Mail an den BTC-Verkäufer senden. Die Quittung muss den vollständigen Namen, die Stadt, das Land des Verkäufers und den Betrag deutlich zeigen. Der Käufer bekommt die E-Mail-Adresse des Verkäufers im Handelsprozess angezeigt. payment.westernUnion.info=Wenn Western Union verwendet wird, muss der BTC Käufer die MTCN (Tracking-Nummer) und ein Foto der Quittung per E-Mail an den BTC-Verkäufer senden. Die Quittung muss den vollständigen Namen, die Stadt, das Land des Verkäufers und den Betrag deutlich zeigen. Der Käufer bekommt die E-Mail-Adresse des Verkäufers im Handelsprozess angezeigt.
payment.halCash.info=Bei Verwendung von HalCash muss der BTC-Käufer dem BTC-Verkäufer den HalCash-Code per SMS vom Mobiltelefon senden.\n\nBitte achten Sie darauf, dass Sie den maximalen Betrag, den Sie bei Ihrer Bank mit HalCash versenden dürfen, nicht überschreiten. Der Mindestbetrag pro Auszahlung beträgt 10 EUR und der Höchstbetrag 600 EUR. Bei wiederholten Abhebungen sind es 3000 EUR pro Empfänger pro Tag und 6000 EUR pro Empfänger pro Monat. Bitte überprüfen Sie diese Limits bei Ihrer Bank, um sicherzustellen, dass sie die gleichen Limits wie hier angegeben verwenden.\n\nDer Auszahlungsbetrag muss ein Vielfaches von 10 EUR betragen, da Sie keine anderen Beträge an einem Geldautomaten abheben können. Die Benutzeroberfläche beim Erstellen und Annehmen eines Angebots passt den BTC-Betrag so an, dass der EUR-Betrag korrekt ist. Sie können keinen marktbasierten Preis verwenden, da sich der EUR-Betrag bei sich ändernden Preisen ändern würde.\n\nIm Streitfall muss der BTC-Käufer den Nachweis erbringen, dass er die EUR geschickt hat. payment.halCash.info=Bei Verwendung von HalCash muss der BTC-Käufer dem BTC-Verkäufer den HalCash-Code per SMS vom Mobiltelefon senden.\n\nBitte achten Sie darauf, dass Sie den maximalen Betrag, den Sie bei Ihrer Bank mit HalCash versenden dürfen, nicht überschreiten. Der Mindestbetrag pro Auszahlung beträgt 10 EUR und der Höchstbetrag 600 EUR. Bei wiederholten Abhebungen sind es 3000 EUR pro Empfänger pro Tag und 6000 EUR pro Empfänger pro Monat. Bitte überprüfen Sie diese Limits bei Ihrer Bank, um sicherzustellen, dass sie die gleichen Limits wie hier angegeben verwenden.\n\nDer Auszahlungsbetrag muss ein Vielfaches von 10 EUR betragen, da Sie keine anderen Beträge an einem Geldautomaten abheben können. Die Benutzeroberfläche beim Erstellen und Annehmen eines Angebots passt den BTC-Betrag so an, dass der EUR-Betrag korrekt ist. Sie können keinen marktbasierten Preis verwenden, da sich der EUR-Betrag bei sich ändernden Preisen ändern würde.\n\nIm Streitfall muss der BTC-Käufer den Nachweis erbringen, dass er die EUR geschickt hat.
payment.limits.info=Please be aware that all bank transfers carry a certain amount of chargeback risk.\n\nTo mitigate this risk, Bisq sets per-trade limits based on two factors:\n\n1. The estimated level of chargeback risk for the payment method used\n2. The age of your account for that payment method\n\nThe account you are creating now is new and its age is zero. As your account ages, your per-trade limits will grow:\n\n● During the 1st month, your per-trade limit will be {0}\n● During the 2nd month, your per-trade limit will be {1}\n● After the 2nd month, your per-trade limit will be {2}\n\nPlease note: limits only apply to trade size. You can place as many trades as you like. payment.limits.info=Bitte beachten Sie, dass alle Banküberweisungen mit einem gewissen Rückbuchungsrisiko verbunden sind.\n\nUm dieses Risiko zu mindern, setzt Bisq Limits pro Trade fest, die auf zwei Faktoren basieren:\n\n1. Die geschätzte Höhe des Rückbuchungsrisikos für die verwendete Zahlungsmethode\n2. Das Alter Ihres Kontos für diese Zahlungsmethode\n\nDas Konto, das Sie jetzt anlegen, ist neu und sein Alter ist Null. Wenn Ihr Konto altert, werden die Limits pro Trade erhöht:\n\n● Während des 1. Monats wird Ihr Limit pro Trade {0} betragen\n● Während des 2. Monats wird Ihr Limit pro Trade {1} betragen\n● Nach dem 2. Monat wird Ihr Limit pro Trade {2} betragen\n\nBitte beachten Sie: Die Beschränkungen gelten nur für die Trade-Größe. Sie können so viele Trades platzieren, wie Sie möchten.
payment.limits.info.withSigning=To limit chargeback risk, Bisq sets per-trade limits for this payment account type based on the following 2 factors:\n\n1. General chargeback risk for the payment method\n2. Account signing status\n\nThis payment account is not yet signed, so it is limited to buying {0} per trade. After signing, buy limits will increase as follows:\n\n● Before signing, and for 30 days after signing, your per-trade buy limit will be {0}\n● 30 days after signing, your per-trade buy limit will be {1}\n● 60 days after signing, your per-trade buy limit will be {2}\n\nSell limits are not affected by account signing, and increase with account age.\n\nSee more:\nhttps://bisq.wiki/Account_limits\n\nPlease note: limits only apply to trade size. You can place as many trades as you like. payment.limits.info.withSigning=Um das Rückbuchungsrisiko zu begrenzen, setzt Bisq für diesen Zahlungskontotyp Limits pro Trade auf der Grundlage der folgenden 2 Faktoren fest:\n\n1. Allgemeines Rückbuchungsrisiko für die Zahlungsmethode\n2. Status der Kontounterzeichnung\n\nDieses Zahlungskonto ist noch nicht unterzeichnet, so dass es auf den Kauf von {0} pro Trade beschränkt ist. Nach der Unterzeichnung werden die Kauflimits wie folgt erhöht:\n\n● Vor der Unterzeichnung und für 30 Tage nach der Unterzeichnung beträgt Ihr Kauflimit pro Trade {0}\n● 30 Tage nach der Unterzeichnung beträgt Ihr Kauflimit pro Trade {1}\n● 60 Tage nach der Unterzeichnung beträgt Ihr Kauflimit pro Trade {2}\n\nVerkaufslimits sind von der Kontounterzeichnung nicht betroffen und erhöhen sich mit dem Alter des Kontos.\n\nSiehe mehr:\nhttps://bisq.wiki/Account_limits\n\n\nBitte beachten Sie: Die Beschränkungen gelten nur für die Trade-Größe. Sie können so viele Trades platzieren, wie Sie möchten.
payment.cashDeposit.info=Bitte bestätigen Sie, dass Ihre Bank Bareinzahlungen in Konten von anderen Personen erlaubt. Zum Beispiel werden diese Einzahlungen bei der Bank of America und Wells Fargo nicht mehr erlaubt. payment.cashDeposit.info=Bitte bestätigen Sie, dass Ihre Bank Bareinzahlungen in Konten von anderen Personen erlaubt. Zum Beispiel werden diese Einzahlungen bei der Bank of America und Wells Fargo nicht mehr erlaubt.

View file

@ -242,7 +242,6 @@ mainView.marketPriceWithProvider.label=Precio de mercado por {0}
mainView.marketPrice.bisqInternalPrice=Precio del último intercambio en Bisq mainView.marketPrice.bisqInternalPrice=Precio del último intercambio en Bisq
mainView.marketPrice.tooltip.bisqInternalPrice=No existe un precio de mercado disponible proveniente de fuentes externas.\nEl precio mostrado es el último precio de intercambio en Bisq para esa moneda. mainView.marketPrice.tooltip.bisqInternalPrice=No existe un precio de mercado disponible proveniente de fuentes externas.\nEl precio mostrado es el último precio de intercambio en Bisq para esa moneda.
mainView.marketPrice.tooltip=Precio de mercado ofrecido por {0}{1}\nÚltima actualización: {2}\nURL del nodo proveedor: {3} mainView.marketPrice.tooltip=Precio de mercado ofrecido por {0}{1}\nÚltima actualización: {2}\nURL del nodo proveedor: {3}
mainView.marketPrice.tooltip.altcoinExtra=Si la altcoin no está disponible en Poloniex usamos https://coinmarketcap.com
mainView.balance.available=Saldo disponible mainView.balance.available=Saldo disponible
mainView.balance.reserved=Reservado en ofertas mainView.balance.reserved=Reservado en ofertas
mainView.balance.locked=Bloqueado en intercambios mainView.balance.locked=Bloqueado en intercambios
@ -1015,7 +1014,6 @@ setting.about.providers=Proveedores de datos
setting.about.apisWithFee=Bisq usa APIs de terceros para los precios de los mercados Fiat y Altcoin así como para la estimación de tasas de minado. setting.about.apisWithFee=Bisq usa APIs de terceros para los precios de los mercados Fiat y Altcoin así como para la estimación de tasas de minado.
setting.about.apis=Bisq utiliza APIs de terceros para los precios de mercado de Fiat y Altcoin. setting.about.apis=Bisq utiliza APIs de terceros para los precios de mercado de Fiat y Altcoin.
setting.about.pricesProvided=Precios de mercado proporcionados por: setting.about.pricesProvided=Precios de mercado proporcionados por:
setting.about.pricesProviders={0}, {1} y {2}
setting.about.feeEstimation.label=Estimación de comisión de minería proporcionada por: setting.about.feeEstimation.label=Estimación de comisión de minería proporcionada por:
setting.about.versionDetails=Detalles de la versión setting.about.versionDetails=Detalles de la versión
setting.about.version=Versión de la aplicación: setting.about.version=Versión de la aplicación:
@ -1153,6 +1151,7 @@ account.password.info=Con protección por contraseña necesitará introducir su
account.seed.backup.title=Copia de seguridad de palabras semilla del monedero account.seed.backup.title=Copia de seguridad de palabras semilla del monedero
account.seed.info=Por favor apunte en un papel tanto las palabras semilla del monedero como la fecha! Puede recuperar su monedero en cualquier momento con las palabras semilla y la fecha.\nLas mismas palabras semilla se usan para el monedero BTC como BSQ\n\nDebe apuntar las palabras semillas en una hoja de papel. No la guarde en su computadora.\n\nPor favor, tenga en cuenta que las palabras semilla no son un sustituto de la copia de seguridad.\nNecesita hacer la copia de seguridad de todo el directorio de aplicación en la pantalla \"Cuenta/Copia de Seguridad\" para recuperar un estado de aplicación válido y los datos.\nImportar las palabras semilla solo se recomienda para casos de emergencia. La aplicación no será funcional sin una buena copia de seguridad de los archivos de la base de datos y las claves! account.seed.info=Por favor apunte en un papel tanto las palabras semilla del monedero como la fecha! Puede recuperar su monedero en cualquier momento con las palabras semilla y la fecha.\nLas mismas palabras semilla se usan para el monedero BTC como BSQ\n\nDebe apuntar las palabras semillas en una hoja de papel. No la guarde en su computadora.\n\nPor favor, tenga en cuenta que las palabras semilla no son un sustituto de la copia de seguridad.\nNecesita hacer la copia de seguridad de todo el directorio de aplicación en la pantalla \"Cuenta/Copia de Seguridad\" para recuperar un estado de aplicación válido y los datos.\nImportar las palabras semilla solo se recomienda para casos de emergencia. La aplicación no será funcional sin una buena copia de seguridad de los archivos de la base de datos y las claves!
account.seed.backup.warning=Please note that the seed words are NOT a replacement for a backup.\nYou need to create a backup of the whole application directory from the \"Account/Backup\" screen to recover application state and data.\nImporting seed words is only recommended for emergency cases. The application will not be functional without a proper backup of the database files and keys!\n\nSee the wiki page https://bisq.wiki/Backing_up_application_data for extended info.
account.seed.warn.noPw.msg=No ha establecido una contraseña de cartera que proteja la visualización de las palabras semilla.\n\n¿Quiere que se muestren las palabras semilla? account.seed.warn.noPw.msg=No ha establecido una contraseña de cartera que proteja la visualización de las palabras semilla.\n\n¿Quiere que se muestren las palabras semilla?
account.seed.warn.noPw.yes=Sí, y no preguntar de nuevo account.seed.warn.noPw.yes=Sí, y no preguntar de nuevo
account.seed.enterPw=Introducir contraseña para ver las palabras semilla account.seed.enterPw=Introducir contraseña para ver las palabras semilla

View file

@ -242,7 +242,6 @@ mainView.marketPriceWithProvider.label=قیمت بازار بر اساس {0}
mainView.marketPrice.bisqInternalPrice=قیمت آخرین معامله‌ی Bisq mainView.marketPrice.bisqInternalPrice=قیمت آخرین معامله‌ی Bisq
mainView.marketPrice.tooltip.bisqInternalPrice=قیمت بازارهای خارجی موجود نیست.\nقیمت نمایش داده شده، از آخرین معامله‌ی Bisq برای ارز موردنظر اتخاذ شده است. mainView.marketPrice.tooltip.bisqInternalPrice=قیمت بازارهای خارجی موجود نیست.\nقیمت نمایش داده شده، از آخرین معامله‌ی Bisq برای ارز موردنظر اتخاذ شده است.
mainView.marketPrice.tooltip=قیمت بازار توسط {0}{1} ارائه شده است\nآخرین به روز رسانی: {2}\nURL لینک Node ارائه دهنده: {3} mainView.marketPrice.tooltip=قیمت بازار توسط {0}{1} ارائه شده است\nآخرین به روز رسانی: {2}\nURL لینک Node ارائه دهنده: {3}
mainView.marketPrice.tooltip.altcoinExtra=در صورتی که آلتکوین در Poloniex موجود نباشد، از نرخ https://coinmarketcap.com استفاده می کنیم.
mainView.balance.available=موجودی در دسترس mainView.balance.available=موجودی در دسترس
mainView.balance.reserved=رزرو شده در پیشنهادها mainView.balance.reserved=رزرو شده در پیشنهادها
mainView.balance.locked=قفل شده در معاملات mainView.balance.locked=قفل شده در معاملات
@ -1015,7 +1014,6 @@ setting.about.providers=ارائه دهندگان داده
setting.about.apisWithFee=Bisq از APIهای شخص ثالث 3rd party برای قیمت های روز فیات و آلت کوین و همچنین برای برآورد هزینه تراکنش شبکه استفاده می کند. setting.about.apisWithFee=Bisq از APIهای شخص ثالث 3rd party برای قیمت های روز فیات و آلت کوین و همچنین برای برآورد هزینه تراکنش شبکه استفاده می کند.
setting.about.apis=Bisq از APIهای شخص ثالث 3rd party برای قیمت های روز فیات و آلت کوین استفاده می کند. setting.about.apis=Bisq از APIهای شخص ثالث 3rd party برای قیمت های روز فیات و آلت کوین استفاده می کند.
setting.about.pricesProvided=قیمت‌های بازار ارائه شده توسط setting.about.pricesProvided=قیمت‌های بازار ارائه شده توسط
setting.about.pricesProviders={0}, {1} و {2}
setting.about.feeEstimation.label=برآورد کارمزد استخراج ارائه شده توسط setting.about.feeEstimation.label=برآورد کارمزد استخراج ارائه شده توسط
setting.about.versionDetails=جزئیات نسخه setting.about.versionDetails=جزئیات نسخه
setting.about.version=نسخه برنامه setting.about.version=نسخه برنامه
@ -1153,6 +1151,7 @@ account.password.info=با محافظت رمزعبوری شما باید با ه
account.seed.backup.title=پشتیبان گیری از کلمات رمز خصوصی کیف های پول شما account.seed.backup.title=پشتیبان گیری از کلمات رمز خصوصی کیف های پول شما
account.seed.info=لطفا هم کلمات seed و هم تاریخ را یادداشت کنید! شما هر زمانی که بخواهید می‌توانید کیف‌پولتان را با استفاده از کلمات seed و تاریخ بازیابی کنید.\nهمین کلمات seed برای کیف‌پول‌های BTC و BSQ هم استفاده می‌شود.\n\nشما باید کلمات seed را روی یک برگ کاغذ یادداشت کنید. آنها را روی کامپیوتر خودتان ذخیره نکنید.\n\nلطفا توجه کنید که کلمات seed جایگزینی برای یک پشتیبان نیستند.\nبرای بازیابی وضعیت و داده‌های برنامه باید از طریق صفحه \"Account/Backup\" از کل پوشه برنامه پشتیبان بسازید.\nوارد کردن کلمات seed فقط در موارد اورژانسی توصیه می‌شود. برنامه بدون پشتیبان از پایگاه داده و کلیدهای مناسب درست عمل نخواهد کرد! account.seed.info=لطفا هم کلمات seed و هم تاریخ را یادداشت کنید! شما هر زمانی که بخواهید می‌توانید کیف‌پولتان را با استفاده از کلمات seed و تاریخ بازیابی کنید.\nهمین کلمات seed برای کیف‌پول‌های BTC و BSQ هم استفاده می‌شود.\n\nشما باید کلمات seed را روی یک برگ کاغذ یادداشت کنید. آنها را روی کامپیوتر خودتان ذخیره نکنید.\n\nلطفا توجه کنید که کلمات seed جایگزینی برای یک پشتیبان نیستند.\nبرای بازیابی وضعیت و داده‌های برنامه باید از طریق صفحه \"Account/Backup\" از کل پوشه برنامه پشتیبان بسازید.\nوارد کردن کلمات seed فقط در موارد اورژانسی توصیه می‌شود. برنامه بدون پشتیبان از پایگاه داده و کلیدهای مناسب درست عمل نخواهد کرد!
account.seed.backup.warning=Please note that the seed words are NOT a replacement for a backup.\nYou need to create a backup of the whole application directory from the \"Account/Backup\" screen to recover application state and data.\nImporting seed words is only recommended for emergency cases. The application will not be functional without a proper backup of the database files and keys!\n\nSee the wiki page https://bisq.wiki/Backing_up_application_data for extended info.
account.seed.warn.noPw.msg=شما یک رمز عبور کیف پول تنظیم نکرده اید که از نمایش کلمات رمز خصوصی محافظت کند.\n\nآیا می خواهید کلمات رمز خصوصی نشان داده شود؟ account.seed.warn.noPw.msg=شما یک رمز عبور کیف پول تنظیم نکرده اید که از نمایش کلمات رمز خصوصی محافظت کند.\n\nآیا می خواهید کلمات رمز خصوصی نشان داده شود؟
account.seed.warn.noPw.yes=بلی، و دوباره از من نپرس account.seed.warn.noPw.yes=بلی، و دوباره از من نپرس
account.seed.enterPw=وارد کردن رمز عبور به منظور مشاهده ی کلمات رمز خصوصی account.seed.enterPw=وارد کردن رمز عبور به منظور مشاهده ی کلمات رمز خصوصی

View file

@ -242,7 +242,6 @@ mainView.marketPriceWithProvider.label=Prix du marché par {0}
mainView.marketPrice.bisqInternalPrice=Cours de la dernière transaction Bisq mainView.marketPrice.bisqInternalPrice=Cours de la dernière transaction Bisq
mainView.marketPrice.tooltip.bisqInternalPrice=Il n'y a pas de cours de marché disponible depuis une source externe.\nLe cours affiché est celui de la dernière transaction Bisq pour cette devise. mainView.marketPrice.tooltip.bisqInternalPrice=Il n'y a pas de cours de marché disponible depuis une source externe.\nLe cours affiché est celui de la dernière transaction Bisq pour cette devise.
mainView.marketPrice.tooltip=Le prix de marché est fourni par {0}{1}\nDernière mise à jour: {2}\nURL du noeud: {3} mainView.marketPrice.tooltip=Le prix de marché est fourni par {0}{1}\nDernière mise à jour: {2}\nURL du noeud: {3}
mainView.marketPrice.tooltip.altcoinExtra=Si l'altcoin n'est pas disponible sur Poloniex nous utilisons https://coinmarketcap.com
mainView.balance.available=Solde disponible mainView.balance.available=Solde disponible
mainView.balance.reserved=Réservé en ordres mainView.balance.reserved=Réservé en ordres
mainView.balance.locked=Bloqué en transactions mainView.balance.locked=Bloqué en transactions
@ -1015,7 +1014,6 @@ setting.about.providers=Fournisseurs de données
setting.about.apisWithFee=Bisq utilise des APIs tierces ou 3rd party pour le taux de change des devises nationales et des cryptomonnaies, aussi bien que pour obtenir une estimation des frais de minage. setting.about.apisWithFee=Bisq utilise des APIs tierces ou 3rd party pour le taux de change des devises nationales et des cryptomonnaies, aussi bien que pour obtenir une estimation des frais de minage.
setting.about.apis=Bisq utilise des APIs tierces ou 3rd party pour le taux de change des devises nationales et des cryptomonnaies. setting.about.apis=Bisq utilise des APIs tierces ou 3rd party pour le taux de change des devises nationales et des cryptomonnaies.
setting.about.pricesProvided=Prix de marché fourni par setting.about.pricesProvided=Prix de marché fourni par
setting.about.pricesProviders={0}, {1} et {2}
setting.about.feeEstimation.label=Estimation des frais de minage fournie par setting.about.feeEstimation.label=Estimation des frais de minage fournie par
setting.about.versionDetails=Détails sur la version setting.about.versionDetails=Détails sur la version
setting.about.version=Version de l'application setting.about.version=Version de l'application
@ -1153,6 +1151,7 @@ account.password.info=Avec la protection par mot de passe, vous devrez entrer vo
account.seed.backup.title=Sauvegarder les mots composant la seed de votre portefeuille account.seed.backup.title=Sauvegarder les mots composant la seed de votre portefeuille
account.seed.info=Veuillez noter les mots de la seed du portefeuille ainsi que la date! Vous pouvez récupérer votre portefeuille à tout moment avec les mots de la seed et la date.\nLes mêmes mots-clés de la seed sont utilisés pour les portefeuilles BTC et BSQ.\n\nVous devriez écrire les mots de la seed sur une feuille de papier. Ne les enregistrez pas sur votre ordinateur.\n\nVeuillez noter que les mots de la seed ne remplacent PAS une sauvegarde.\nVous devez créer une sauvegarde de l'intégralité du répertoire de l'application à partir de l'écran \"Compte/Sauvergarde\" pour restaurer correctement les données de l'application.\nL'importation de mots de la seed n'est recommandée qu'en cas d'urgence. L'application ne sera pas fonctionnelle sans une sauvegarde adéquate des fichiers et des clés de la base de données ! account.seed.info=Veuillez noter les mots de la seed du portefeuille ainsi que la date! Vous pouvez récupérer votre portefeuille à tout moment avec les mots de la seed et la date.\nLes mêmes mots-clés de la seed sont utilisés pour les portefeuilles BTC et BSQ.\n\nVous devriez écrire les mots de la seed sur une feuille de papier. Ne les enregistrez pas sur votre ordinateur.\n\nVeuillez noter que les mots de la seed ne remplacent PAS une sauvegarde.\nVous devez créer une sauvegarde de l'intégralité du répertoire de l'application à partir de l'écran \"Compte/Sauvergarde\" pour restaurer correctement les données de l'application.\nL'importation de mots de la seed n'est recommandée qu'en cas d'urgence. L'application ne sera pas fonctionnelle sans une sauvegarde adéquate des fichiers et des clés de la base de données !
account.seed.backup.warning=Please note that the seed words are NOT a replacement for a backup.\nYou need to create a backup of the whole application directory from the \"Account/Backup\" screen to recover application state and data.\nImporting seed words is only recommended for emergency cases. The application will not be functional without a proper backup of the database files and keys!\n\nSee the wiki page https://bisq.wiki/Backing_up_application_data for extended info.
account.seed.warn.noPw.msg=Vous n'avez pas configuré un mot de passe de portefeuille qui protégerait l'affichage des mots composant la seed.\n\nVoulez-vous afficher les mots composant la seed? account.seed.warn.noPw.msg=Vous n'avez pas configuré un mot de passe de portefeuille qui protégerait l'affichage des mots composant la seed.\n\nVoulez-vous afficher les mots composant la seed?
account.seed.warn.noPw.yes=Oui, et ne me le demander plus à l'avenir account.seed.warn.noPw.yes=Oui, et ne me le demander plus à l'avenir
account.seed.enterPw=Entrer le mot de passe afficher les mots composant la seed account.seed.enterPw=Entrer le mot de passe afficher les mots composant la seed

View file

@ -242,7 +242,6 @@ mainView.marketPriceWithProvider.label={0} による市場価格
mainView.marketPrice.bisqInternalPrice=Bisqにおける最新の取引価格 mainView.marketPrice.bisqInternalPrice=Bisqにおける最新の取引価格
mainView.marketPrice.tooltip.bisqInternalPrice=利用可能な外部価格フィードプロバイダーからの市場価格がありません。\n表示されている価格は、その通貨の最新のBisq取引価格です。 mainView.marketPrice.tooltip.bisqInternalPrice=利用可能な外部価格フィードプロバイダーからの市場価格がありません。\n表示されている価格は、その通貨の最新のBisq取引価格です。
mainView.marketPrice.tooltip=市場価格は{0}{1}に提供されています\n最終更新: {2}\n提供者のードのURL: {3} mainView.marketPrice.tooltip=市場価格は{0}{1}に提供されています\n最終更新: {2}\n提供者のードのURL: {3}
mainView.marketPrice.tooltip.altcoinExtra=アルトコインがPoloniexで利用可能でない場合、https://coinmarketcap.com を利用します
mainView.balance.available=利用可能残高 mainView.balance.available=利用可能残高
mainView.balance.reserved=オファーのために予約済み mainView.balance.reserved=オファーのために予約済み
mainView.balance.locked=トレードにロック中 mainView.balance.locked=トレードにロック中
@ -1015,7 +1014,6 @@ setting.about.providers=データプロバイダー
setting.about.apisWithFee=Bisqは、法定通貨とアルトコインの市場価格や、マイニング料金の推定にサードパーティAPIを使用します。 setting.about.apisWithFee=Bisqは、法定通貨とアルトコインの市場価格や、マイニング料金の推定にサードパーティAPIを使用します。
setting.about.apis=Bisqは法定通貨とアルトコインの市場価格の為にサードパーティAPIを使用します。 setting.about.apis=Bisqは法定通貨とアルトコインの市場価格の為にサードパーティAPIを使用します。
setting.about.pricesProvided=市場価格を提供している: setting.about.pricesProvided=市場価格を提供している:
setting.about.pricesProviders={0}, {1} と {2}
setting.about.feeEstimation.label=推定マイニング手数料の提供: setting.about.feeEstimation.label=推定マイニング手数料の提供:
setting.about.versionDetails=バージョン詳細 setting.about.versionDetails=バージョン詳細
setting.about.version=アプリのバージョン setting.about.version=アプリのバージョン
@ -1153,6 +1151,7 @@ account.password.info=パスワード保護を使用すると、アプリケー
account.seed.backup.title=あなたのウォレットのシードワードをバックアップ account.seed.backup.title=あなたのウォレットのシードワードをバックアップ
account.seed.info=ウォレットのシードワードと日付の両方を書き留めてください!あなたはシードワードと日付でいつでもウォレットを復元することができます。\nBTCおよびBSQウォレットには同じシードワードが使用されています。\n\nあなたは一枚の紙にシードワードを書き留めるべきです。コンピュータに保存しないでください。\n\nシードワードはバックアップの代わりにはならないことに気をつけて下さい。\nアプリケーションの状態とデータを復元するには「アカウント/バックアップ」画面からアプリケーションディレクトリ全体のバックアップを作成する必要があります。\nシードワードのインポートは緊急の場合にのみ推奨されます。データベースファイルとキーの適切なバックアップがなければ、アプリケーションは機能しません account.seed.info=ウォレットのシードワードと日付の両方を書き留めてください!あなたはシードワードと日付でいつでもウォレットを復元することができます。\nBTCおよびBSQウォレットには同じシードワードが使用されています。\n\nあなたは一枚の紙にシードワードを書き留めるべきです。コンピュータに保存しないでください。\n\nシードワードはバックアップの代わりにはならないことに気をつけて下さい。\nアプリケーションの状態とデータを復元するには「アカウント/バックアップ」画面からアプリケーションディレクトリ全体のバックアップを作成する必要があります。\nシードワードのインポートは緊急の場合にのみ推奨されます。データベースファイルとキーの適切なバックアップがなければ、アプリケーションは機能しません
account.seed.backup.warning=Please note that the seed words are NOT a replacement for a backup.\nYou need to create a backup of the whole application directory from the \"Account/Backup\" screen to recover application state and data.\nImporting seed words is only recommended for emergency cases. The application will not be functional without a proper backup of the database files and keys!\n\nSee the wiki page https://bisq.wiki/Backing_up_application_data for extended info.
account.seed.warn.noPw.msg=シードワードの表示を保護するためのウォレットパスワードを設定していません。 \n\nシードワードを表示しますか account.seed.warn.noPw.msg=シードワードの表示を保護するためのウォレットパスワードを設定していません。 \n\nシードワードを表示しますか
account.seed.warn.noPw.yes=はい、そして次回から確認しないで下さい account.seed.warn.noPw.yes=はい、そして次回から確認しないで下さい
account.seed.enterPw=シードワードを見るためにパスワードを入力 account.seed.enterPw=シードワードを見るためにパスワードを入力

View file

@ -242,7 +242,6 @@ mainView.marketPriceWithProvider.label=Preço de mercado por {0}
mainView.marketPrice.bisqInternalPrice=Preço da última negociação Bisq mainView.marketPrice.bisqInternalPrice=Preço da última negociação Bisq
mainView.marketPrice.tooltip.bisqInternalPrice=Não foi encontrado preço de mercado nos provedores externos.\nO preço exibido corresponde ao último preço de negociação no Bisq para essa moeda. mainView.marketPrice.tooltip.bisqInternalPrice=Não foi encontrado preço de mercado nos provedores externos.\nO preço exibido corresponde ao último preço de negociação no Bisq para essa moeda.
mainView.marketPrice.tooltip=Preço de Mercado fornecido por {0}{1}\nÚltima atualização: {2}\nURL do provedor: {3} mainView.marketPrice.tooltip=Preço de Mercado fornecido por {0}{1}\nÚltima atualização: {2}\nURL do provedor: {3}
mainView.marketPrice.tooltip.altcoinExtra=Se a altcoin não estiver disponível na Poloniex, usaremos https://coinmarketcap.com
mainView.balance.available=Saldo disponível mainView.balance.available=Saldo disponível
mainView.balance.reserved=Reservado em ofertas mainView.balance.reserved=Reservado em ofertas
mainView.balance.locked=Travado em negociações mainView.balance.locked=Travado em negociações
@ -1015,7 +1014,6 @@ setting.about.providers=Provedores de dados
setting.about.apisWithFee=O Bisq utiliza APIs de terceiros para obter os preços de moedas fiduciárias e de altcoins, assim como para estimar a taxa de mineração. setting.about.apisWithFee=O Bisq utiliza APIs de terceiros para obter os preços de moedas fiduciárias e de altcoins, assim como para estimar a taxa de mineração.
setting.about.apis=Bisq utiliza APIs de terceiros para os preços de moedas fiduciárias e altcoins. setting.about.apis=Bisq utiliza APIs de terceiros para os preços de moedas fiduciárias e altcoins.
setting.about.pricesProvided=Preços de mercado fornecidos por setting.about.pricesProvided=Preços de mercado fornecidos por
setting.about.pricesProviders={0}, {1} e {2}
setting.about.feeEstimation.label=Estimativa da taxa de mineração fornecida por setting.about.feeEstimation.label=Estimativa da taxa de mineração fornecida por
setting.about.versionDetails=Detalhes da versão setting.about.versionDetails=Detalhes da versão
setting.about.version=Versão do programa setting.about.version=Versão do programa
@ -1153,6 +1151,7 @@ account.password.info=Ao proteger a carteira com uma senha, você precisará dig
account.seed.backup.title=Fazer backup das palavras-semente da carteira account.seed.backup.title=Fazer backup das palavras-semente da carteira
account.seed.info=Por favor, anote em um papel a data e as palavras-semente da carteira! Com essas informações, você poderá recuperar sua carteira à qualquer momento.\nA semente exibida é usada tanto para a carteira BTC quanto para a carteira BSQ.\n\nVocê deve anotá-las em uma folha de papel. Jamais anote as palavras em um arquivo no seu computador ou em seu e-mail.\n\nNote que a semente da carteira NÃO substitui um backup.\nPara fazer isso, você precisa fazer backup da pasta do Bisq na seção \"Conta/Backup\".\nA importação da semente da carteira só é recomendada em casos de emergência. O programa não funcionará corretamente se você não recuperá-lo através de um backup! account.seed.info=Por favor, anote em um papel a data e as palavras-semente da carteira! Com essas informações, você poderá recuperar sua carteira à qualquer momento.\nA semente exibida é usada tanto para a carteira BTC quanto para a carteira BSQ.\n\nVocê deve anotá-las em uma folha de papel. Jamais anote as palavras em um arquivo no seu computador ou em seu e-mail.\n\nNote que a semente da carteira NÃO substitui um backup.\nPara fazer isso, você precisa fazer backup da pasta do Bisq na seção \"Conta/Backup\".\nA importação da semente da carteira só é recomendada em casos de emergência. O programa não funcionará corretamente se você não recuperá-lo através de um backup!
account.seed.backup.warning=Please note that the seed words are NOT a replacement for a backup.\nYou need to create a backup of the whole application directory from the \"Account/Backup\" screen to recover application state and data.\nImporting seed words is only recommended for emergency cases. The application will not be functional without a proper backup of the database files and keys!\n\nSee the wiki page https://bisq.wiki/Backing_up_application_data for extended info.
account.seed.warn.noPw.msg=Você não definiu uma senha para carteira, que protegeria a exibição das palavras-semente.\n\nGostaria de exibir as palavras-semente? account.seed.warn.noPw.msg=Você não definiu uma senha para carteira, que protegeria a exibição das palavras-semente.\n\nGostaria de exibir as palavras-semente?
account.seed.warn.noPw.yes=Sim, e não me pergunte novamente account.seed.warn.noPw.yes=Sim, e não me pergunte novamente
account.seed.enterPw=Digite a senha para ver a semente da carteira account.seed.enterPw=Digite a senha para ver a semente da carteira

View file

@ -242,7 +242,6 @@ mainView.marketPriceWithProvider.label=Preço de mercado por {0}
mainView.marketPrice.bisqInternalPrice=Preço do último negócio do Bisq mainView.marketPrice.bisqInternalPrice=Preço do último negócio do Bisq
mainView.marketPrice.tooltip.bisqInternalPrice=Não há preço de mercado de fornecedores de feed de preço externos disponíveis.\nO preço exibido é o mais recente preço de negócio do Bisq para essa moeda. mainView.marketPrice.tooltip.bisqInternalPrice=Não há preço de mercado de fornecedores de feed de preço externos disponíveis.\nO preço exibido é o mais recente preço de negócio do Bisq para essa moeda.
mainView.marketPrice.tooltip=O preço de mercado é fornecido por {0} {1}\nÚltima atualização: {2}\nURL do nó do provedor: {3} mainView.marketPrice.tooltip=O preço de mercado é fornecido por {0} {1}\nÚltima atualização: {2}\nURL do nó do provedor: {3}
mainView.marketPrice.tooltip.altcoinExtra=Se a altcoin não estiver disponível na Poloniex, usaremos https://coinmarketcap.com
mainView.balance.available=Saldo disponível mainView.balance.available=Saldo disponível
mainView.balance.reserved=Reservado em ofertas mainView.balance.reserved=Reservado em ofertas
mainView.balance.locked=Bloqueado em negócios mainView.balance.locked=Bloqueado em negócios
@ -1015,7 +1014,6 @@ setting.about.providers=Provedores de dados
setting.about.apisWithFee=A Bisq usa APIs de terceiros para os preços de mercado de moedas fiduciárias e Altcoin, bem como para estimativas de taxas de mineração. setting.about.apisWithFee=A Bisq usa APIs de terceiros para os preços de mercado de moedas fiduciárias e Altcoin, bem como para estimativas de taxas de mineração.
setting.about.apis=Bisq utiliza APIs de terceiros para os preços de moedas fiduciárias e altcoins. setting.about.apis=Bisq utiliza APIs de terceiros para os preços de moedas fiduciárias e altcoins.
setting.about.pricesProvided=Preços de mercado fornecidos por setting.about.pricesProvided=Preços de mercado fornecidos por
setting.about.pricesProviders={0}, {1} e {2}
setting.about.feeEstimation.label=Taxa de mineração fornecida por setting.about.feeEstimation.label=Taxa de mineração fornecida por
setting.about.versionDetails=Detalhes da versão setting.about.versionDetails=Detalhes da versão
setting.about.version=Versão do programa setting.about.version=Versão do programa
@ -1153,6 +1151,7 @@ account.password.info=Com a proteção por senha, você precisará inserir a sua
account.seed.backup.title=Fazer backup das palavras semente da sua carteira account.seed.backup.title=Fazer backup das palavras semente da sua carteira
account.seed.info=Por favor, anote as palavras-semente da carteira e a data! Você pode recuperar sua carteira a qualquer momento com palavras-semente e a data.\nAs mesmas palavras-semente são usadas para a carteira BTC e BSQ.\n\nVocê deve anotar as palavras-semente numa folha de papel. Não as guarde no seu computador.\n\nPor favor, note que as palavras-semente não são um substituto para um backup.\nVocê precisa criar um backup de todo o diretório do programa a partir do ecrã \"Conta/Backup\" para recuperar o estado e os dados do programa.\nA importação de palavras-semente é recomendada apenas para casos de emergência. O programa não será funcional sem um backup adequado dos arquivos da base de dados e das chaves! account.seed.info=Por favor, anote as palavras-semente da carteira e a data! Você pode recuperar sua carteira a qualquer momento com palavras-semente e a data.\nAs mesmas palavras-semente são usadas para a carteira BTC e BSQ.\n\nVocê deve anotar as palavras-semente numa folha de papel. Não as guarde no seu computador.\n\nPor favor, note que as palavras-semente não são um substituto para um backup.\nVocê precisa criar um backup de todo o diretório do programa a partir do ecrã \"Conta/Backup\" para recuperar o estado e os dados do programa.\nA importação de palavras-semente é recomendada apenas para casos de emergência. O programa não será funcional sem um backup adequado dos arquivos da base de dados e das chaves!
account.seed.backup.warning=Please note that the seed words are NOT a replacement for a backup.\nYou need to create a backup of the whole application directory from the \"Account/Backup\" screen to recover application state and data.\nImporting seed words is only recommended for emergency cases. The application will not be functional without a proper backup of the database files and keys!\n\nSee the wiki page https://bisq.wiki/Backing_up_application_data for extended info.
account.seed.warn.noPw.msg=Você não definiu uma senha da carteira que protegeria a exibição das palavras-semente.\n\nVocê quer exibir as palavras-semente? account.seed.warn.noPw.msg=Você não definiu uma senha da carteira que protegeria a exibição das palavras-semente.\n\nVocê quer exibir as palavras-semente?
account.seed.warn.noPw.yes=Sim, e não me pergunte novamente account.seed.warn.noPw.yes=Sim, e não me pergunte novamente
account.seed.enterPw=Digite a senha para ver palavras-semente account.seed.enterPw=Digite a senha para ver palavras-semente

View file

@ -242,7 +242,6 @@ mainView.marketPriceWithProvider.label=Рыночный курс {0}
mainView.marketPrice.bisqInternalPrice=Курс последней сделки в Bisq mainView.marketPrice.bisqInternalPrice=Курс последней сделки в Bisq
mainView.marketPrice.tooltip.bisqInternalPrice=Нет данных от источника рыночного курса.\nПредоставлен курс последней сделки в Bisq для этой валютной пары. mainView.marketPrice.tooltip.bisqInternalPrice=Нет данных от источника рыночного курса.\nПредоставлен курс последней сделки в Bisq для этой валютной пары.
mainView.marketPrice.tooltip=Рыночный курс предоставлен {0}{1}\nОбновление: {2}\nURL источника данных: {3} mainView.marketPrice.tooltip=Рыночный курс предоставлен {0}{1}\nОбновление: {2}\nURL источника данных: {3}
mainView.marketPrice.tooltip.altcoinExtra=Если альткойн недоступен на Poloniex, используется https://coinmarketcap.com
mainView.balance.available=Доступный баланс mainView.balance.available=Доступный баланс
mainView.balance.reserved=Выделено на предложения mainView.balance.reserved=Выделено на предложения
mainView.balance.locked=Используется в сделках mainView.balance.locked=Используется в сделках
@ -1015,7 +1014,6 @@ setting.about.providers=Источники данных
setting.about.apisWithFee=Bisq использует сторонние API для определения рыночного курса валют и альткойнов, а также расчёта комиссии майнера. setting.about.apisWithFee=Bisq использует сторонние API для определения рыночного курса валют и альткойнов, а также расчёта комиссии майнера.
setting.about.apis=Bisq использует сторонние API для определения рыночного курса валют и альткойнов. setting.about.apis=Bisq использует сторонние API для определения рыночного курса валют и альткойнов.
setting.about.pricesProvided=Рыночный курс предоставлен setting.about.pricesProvided=Рыночный курс предоставлен
setting.about.pricesProviders={0}, {1} и {2}
setting.about.feeEstimation.label=Расчёт комиссии майнера предоставлен setting.about.feeEstimation.label=Расчёт комиссии майнера предоставлен
setting.about.versionDetails=Подробности версии setting.about.versionDetails=Подробности версии
setting.about.version=Версия приложения setting.about.version=Версия приложения
@ -1153,6 +1151,7 @@ account.password.info=При использовании пароля его не
account.seed.backup.title=Сохраните мнемоническую фразу для вашего кошелька account.seed.backup.title=Сохраните мнемоническую фразу для вашего кошелька
account.seed.info=Запишите мнемоническую фразу для кошелька и дату создания его! Используя эти данные, вы сможете восстановить ваш кошелёк когда угодно.\nДля обоих кошельков, BTC и BSQ, используется одна и та же мнемоническая фраза.\n\nВам следует записать её на бумаге и не хранить на компьютере.\n\nМнемоническая фраза НЕ заменяет резервную копию.\nВам следует сделать резервную копию всего каталога приложения в разделе \«Счёт/Резервное копирование\» для восстановления состояния приложения и данных.\nИмпорт мнемонической фразы рекомендуется только в экстренных случаях. Приложение не будет функционировать должным образом без наличия резервной копии файлов базы данных и ключей! account.seed.info=Запишите мнемоническую фразу для кошелька и дату создания его! Используя эти данные, вы сможете восстановить ваш кошелёк когда угодно.\nДля обоих кошельков, BTC и BSQ, используется одна и та же мнемоническая фраза.\n\nВам следует записать её на бумаге и не хранить на компьютере.\n\nМнемоническая фраза НЕ заменяет резервную копию.\nВам следует сделать резервную копию всего каталога приложения в разделе \«Счёт/Резервное копирование\» для восстановления состояния приложения и данных.\nИмпорт мнемонической фразы рекомендуется только в экстренных случаях. Приложение не будет функционировать должным образом без наличия резервной копии файлов базы данных и ключей!
account.seed.backup.warning=Please note that the seed words are NOT a replacement for a backup.\nYou need to create a backup of the whole application directory from the \"Account/Backup\" screen to recover application state and data.\nImporting seed words is only recommended for emergency cases. The application will not be functional without a proper backup of the database files and keys!\n\nSee the wiki page https://bisq.wiki/Backing_up_application_data for extended info.
account.seed.warn.noPw.msg=Вы не установили пароль от кошелька для защиты мнемонической фразы.\n\nОтобразить мнемоническую фразу на экране? account.seed.warn.noPw.msg=Вы не установили пароль от кошелька для защиты мнемонической фразы.\n\nОтобразить мнемоническую фразу на экране?
account.seed.warn.noPw.yes=Да и не спрашивать снова account.seed.warn.noPw.yes=Да и не спрашивать снова
account.seed.enterPw=Введите пароль, чтобы увидеть мнемоническую фразу account.seed.enterPw=Введите пароль, чтобы увидеть мнемоническую фразу

View file

@ -242,7 +242,6 @@ mainView.marketPriceWithProvider.label=ราคาตลาดโดย {0}
mainView.marketPrice.bisqInternalPrice=ราคาของการซื้อขาย Bisq ล่าสุด mainView.marketPrice.bisqInternalPrice=ราคาของการซื้อขาย Bisq ล่าสุด
mainView.marketPrice.tooltip.bisqInternalPrice=ไม่มีราคาตลาดจากผู้ให้บริการด้านราคาภายนอก\nราคาที่แสดงเป็นราคาล่าสุดของ Bisq สำหรับสกุลเงินนั้น mainView.marketPrice.tooltip.bisqInternalPrice=ไม่มีราคาตลาดจากผู้ให้บริการด้านราคาภายนอก\nราคาที่แสดงเป็นราคาล่าสุดของ Bisq สำหรับสกุลเงินนั้น
mainView.marketPrice.tooltip=ราคาตลาดจัดทำโดย {0} {1} \nอัปเดตล่าสุด: {2} \nnode URL ของผู้ให้บริการ: {3} mainView.marketPrice.tooltip=ราคาตลาดจัดทำโดย {0} {1} \nอัปเดตล่าสุด: {2} \nnode URL ของผู้ให้บริการ: {3}
mainView.marketPrice.tooltip.altcoinExtra=หาก altcoin ไม่สามารถใช้งานได้ที่ Poloniex ทางเราใช้ https://coinmarketcap.com
mainView.balance.available=ยอดคงเหลือที่พร้อมใช้งาน mainView.balance.available=ยอดคงเหลือที่พร้อมใช้งาน
mainView.balance.reserved=ข้อเสนอได้รับการจองแล้ว mainView.balance.reserved=ข้อเสนอได้รับการจองแล้ว
mainView.balance.locked=ล็อคในการซื้อขาย mainView.balance.locked=ล็อคในการซื้อขาย
@ -1015,7 +1014,6 @@ setting.about.providers=ผู้ให้บริการข้อมูล
setting.about.apisWithFee=Bisq ใช้ APIs ของบุคคลที่ 3 สำหรับราคาตลาดของ Fiat และ Altcoin ตลอดจนการประมาณค่าการขุด setting.about.apisWithFee=Bisq ใช้ APIs ของบุคคลที่ 3 สำหรับราคาตลาดของ Fiat และ Altcoin ตลอดจนการประมาณค่าการขุด
setting.about.apis=Bisq ใช้ APIs ของบุคคลที่ 3 สำหรับ Fiat และ Altcoin ในราคาตลาด setting.about.apis=Bisq ใช้ APIs ของบุคคลที่ 3 สำหรับ Fiat และ Altcoin ในราคาตลาด
setting.about.pricesProvided=ราคาตลาดจัดโดย setting.about.pricesProvided=ราคาตลาดจัดโดย
setting.about.pricesProviders={0}, {1} และ {2}
setting.about.feeEstimation.label=การประมาณค่าธรรมเนียมการขุดโดย setting.about.feeEstimation.label=การประมาณค่าธรรมเนียมการขุดโดย
setting.about.versionDetails=รายละเอียดของเวอร์ชั่น setting.about.versionDetails=รายละเอียดของเวอร์ชั่น
setting.about.version=เวอร์ชั่นของแอปพลิเคชั่น setting.about.version=เวอร์ชั่นของแอปพลิเคชั่น
@ -1153,6 +1151,7 @@ account.password.info=ด้วยระบบป้องกันรหัส
account.seed.backup.title=สำรองข้อมูล wallet โค้ดของคุณ account.seed.backup.title=สำรองข้อมูล wallet โค้ดของคุณ
account.seed.info=โปรดเขียนรหัสสำรองข้อมูล wallet และวันที่! คุณสามารถกู้ข้อมูล wallet ของคุณได้ทุกเมื่อด้วย รหัสสำรองข้อมูล wallet และวันที่\nรหัสสำรองข้อมูล ใช้ทั้ง BTC และ BSQ wallet\n\nคุณควรเขียนรหัสสำรองข้อมูล wallet ลงบนแผ่นกระดาษและไม่บันทึกไว้ในคอมพิวเตอร์ของคุณ\n\nโปรดทราบว่า รหัสสำรองข้อมูล wallet ไม่ได้แทนการสำรองข้อมูล\nคุณจำเป็นต้องสำรองข้อมูลสารบบแอ็พพลิเคชั่นทั้งหมดที่หน้าจอ \"บัญชี / การสำรองข้อมูล \" เพื่อกู้คืนสถานะแอ็พพลิเคชั่นและข้อมูลที่ถูกต้อง\nการนำเข้ารหัสสำรองข้อมูล wallet เป็นคำแนะนำเฉพาะสำหรับกรณีฉุกเฉินเท่านั้น แอพพลิเคชั่นจะไม่สามารถใช้งานได้หากไม่มีไฟล์สำรองฐานข้อมูลและคีย์ที่ถูกต้อง! account.seed.info=โปรดเขียนรหัสสำรองข้อมูล wallet และวันที่! คุณสามารถกู้ข้อมูล wallet ของคุณได้ทุกเมื่อด้วย รหัสสำรองข้อมูล wallet และวันที่\nรหัสสำรองข้อมูล ใช้ทั้ง BTC และ BSQ wallet\n\nคุณควรเขียนรหัสสำรองข้อมูล wallet ลงบนแผ่นกระดาษและไม่บันทึกไว้ในคอมพิวเตอร์ของคุณ\n\nโปรดทราบว่า รหัสสำรองข้อมูล wallet ไม่ได้แทนการสำรองข้อมูล\nคุณจำเป็นต้องสำรองข้อมูลสารบบแอ็พพลิเคชั่นทั้งหมดที่หน้าจอ \"บัญชี / การสำรองข้อมูล \" เพื่อกู้คืนสถานะแอ็พพลิเคชั่นและข้อมูลที่ถูกต้อง\nการนำเข้ารหัสสำรองข้อมูล wallet เป็นคำแนะนำเฉพาะสำหรับกรณีฉุกเฉินเท่านั้น แอพพลิเคชั่นจะไม่สามารถใช้งานได้หากไม่มีไฟล์สำรองฐานข้อมูลและคีย์ที่ถูกต้อง!
account.seed.backup.warning=Please note that the seed words are NOT a replacement for a backup.\nYou need to create a backup of the whole application directory from the \"Account/Backup\" screen to recover application state and data.\nImporting seed words is only recommended for emergency cases. The application will not be functional without a proper backup of the database files and keys!\n\nSee the wiki page https://bisq.wiki/Backing_up_application_data for extended info.
account.seed.warn.noPw.msg=คุณยังไม่ได้ตั้งรหัสผ่าน wallet ซึ่งจะช่วยป้องกันการแสดงผลของรหัสสำรองข้อมูล wallet \n\nคุณต้องการแสดงรหัสสำรองข้อมูล wallet หรือไม่ account.seed.warn.noPw.msg=คุณยังไม่ได้ตั้งรหัสผ่าน wallet ซึ่งจะช่วยป้องกันการแสดงผลของรหัสสำรองข้อมูล wallet \n\nคุณต้องการแสดงรหัสสำรองข้อมูล wallet หรือไม่
account.seed.warn.noPw.yes=ใช่ และไม่ต้องถามฉันอีก account.seed.warn.noPw.yes=ใช่ และไม่ต้องถามฉันอีก
account.seed.enterPw=ป้อนรหัสผ่านเพื่อดูรหัสสำรองข้อมูล wallet account.seed.enterPw=ป้อนรหัสผ่านเพื่อดูรหัสสำรองข้อมูล wallet

View file

@ -242,7 +242,6 @@ mainView.marketPriceWithProvider.label=Giá thị trường theo {0}
mainView.marketPrice.bisqInternalPrice=Giá giao dịch Bisq gần nhất mainView.marketPrice.bisqInternalPrice=Giá giao dịch Bisq gần nhất
mainView.marketPrice.tooltip.bisqInternalPrice=Không có giá thị trường từ nhà cung cấp bên ngoài.\nGiá hiển thị là giá giao dịch Bisq gần nhất với đồng tiền này. mainView.marketPrice.tooltip.bisqInternalPrice=Không có giá thị trường từ nhà cung cấp bên ngoài.\nGiá hiển thị là giá giao dịch Bisq gần nhất với đồng tiền này.
mainView.marketPrice.tooltip=Giá thị trường được cung cấp bởi {0}{1}\nCập nhật mới nhất: {2}\nURL nút nhà cung cấp: {3} mainView.marketPrice.tooltip=Giá thị trường được cung cấp bởi {0}{1}\nCập nhật mới nhất: {2}\nURL nút nhà cung cấp: {3}
mainView.marketPrice.tooltip.altcoinExtra=Nếu altcoin không có trên Poloniex, sử dụng https://coinmarketcap.com
mainView.balance.available=Số dư hiện có mainView.balance.available=Số dư hiện có
mainView.balance.reserved=Phần được bảo lưu trong báo giá mainView.balance.reserved=Phần được bảo lưu trong báo giá
mainView.balance.locked=Khóa trong giao dịch mainView.balance.locked=Khóa trong giao dịch
@ -1015,7 +1014,6 @@ setting.about.providers=Nhà cung cấp dữ liệu
setting.about.apisWithFee=Bisq sử dụng API bên thứ 3 để ước tính giá thị trường Fiat và Altcoin cũng như phí đào. setting.about.apisWithFee=Bisq sử dụng API bên thứ 3 để ước tính giá thị trường Fiat và Altcoin cũng như phí đào.
setting.about.apis=Bisq sử dụng API bên thứ 3 để ước tính giá thị trường Fiat và Altcoin. setting.about.apis=Bisq sử dụng API bên thứ 3 để ước tính giá thị trường Fiat và Altcoin.
setting.about.pricesProvided=Giá thị trường cung cấp bởi setting.about.pricesProvided=Giá thị trường cung cấp bởi
setting.about.pricesProviders={0}, {1} và {2}
setting.about.feeEstimation.label=Ước tính phí đào cung cấp bởi setting.about.feeEstimation.label=Ước tính phí đào cung cấp bởi
setting.about.versionDetails=Thông tin về phiên bản setting.about.versionDetails=Thông tin về phiên bản
setting.about.version=Phiên bản ứng dụng setting.about.version=Phiên bản ứng dụng
@ -1153,6 +1151,7 @@ account.password.info=Bằng cách bảo vệ với mật khẩu, bạn cần nh
account.seed.backup.title=Sao lưu dự phòng từ khởi tạo ví của bạn account.seed.backup.title=Sao lưu dự phòng từ khởi tạo ví của bạn
account.seed.info=Hãy viết ra từ khởi tạo ví của bạn và ngày! Bạn có thể khôi phục ví của bạn bất cứ lúc nào với các từ khởi tạo và ngày này.\nTừ khởi tạo được sử dụng chung cho cả ví BTC và BSQ.\n\nBạn nên viết các từ khởi tạo ra tờ giấy. Không được lưu trên máy tính.\n\nLưu ý rằng từ khởi tạo KHÔNG PHẢI là phương án thay thế cho sao lưu dự phòng.\nBạn cần sao lưu dự phòng toàn bộ thư mục của ứng dụng tại màn hình \"Tài khoản/Sao lưu dự phòng\" để khôi phục trạng thái và dữ liệu ứng dụng.\nNhập từ khởi tạo chỉ được thực hiện trong tình huống khẩn cấp. Ứng dụng sẽ không hoạt động mà không có dự phòng các file dữ liệu và khóa phù hợp! account.seed.info=Hãy viết ra từ khởi tạo ví của bạn và ngày! Bạn có thể khôi phục ví của bạn bất cứ lúc nào với các từ khởi tạo và ngày này.\nTừ khởi tạo được sử dụng chung cho cả ví BTC và BSQ.\n\nBạn nên viết các từ khởi tạo ra tờ giấy. Không được lưu trên máy tính.\n\nLưu ý rằng từ khởi tạo KHÔNG PHẢI là phương án thay thế cho sao lưu dự phòng.\nBạn cần sao lưu dự phòng toàn bộ thư mục của ứng dụng tại màn hình \"Tài khoản/Sao lưu dự phòng\" để khôi phục trạng thái và dữ liệu ứng dụng.\nNhập từ khởi tạo chỉ được thực hiện trong tình huống khẩn cấp. Ứng dụng sẽ không hoạt động mà không có dự phòng các file dữ liệu và khóa phù hợp!
account.seed.backup.warning=Please note that the seed words are NOT a replacement for a backup.\nYou need to create a backup of the whole application directory from the \"Account/Backup\" screen to recover application state and data.\nImporting seed words is only recommended for emergency cases. The application will not be functional without a proper backup of the database files and keys!\n\nSee the wiki page https://bisq.wiki/Backing_up_application_data for extended info.
account.seed.warn.noPw.msg=Bạn đã tạo mật khẩu ví để bảo vệ tránh hiển thị Seed words.\n\nBạn có muốn hiển thị Seed words? account.seed.warn.noPw.msg=Bạn đã tạo mật khẩu ví để bảo vệ tránh hiển thị Seed words.\n\nBạn có muốn hiển thị Seed words?
account.seed.warn.noPw.yes=Có và không hỏi lại account.seed.warn.noPw.yes=Có và không hỏi lại
account.seed.enterPw=Nhập mật khẩu để xem seed words account.seed.enterPw=Nhập mật khẩu để xem seed words

View file

@ -242,7 +242,6 @@ mainView.marketPriceWithProvider.label=交易所价格提供商:{0}
mainView.marketPrice.bisqInternalPrice=最新 Bisq 交易的价格 mainView.marketPrice.bisqInternalPrice=最新 Bisq 交易的价格
mainView.marketPrice.tooltip.bisqInternalPrice=外部交易所供应商没有可用的市场价格。\n显示的价格是该货币的最新 Bisq 交易价格。 mainView.marketPrice.tooltip.bisqInternalPrice=外部交易所供应商没有可用的市场价格。\n显示的价格是该货币的最新 Bisq 交易价格。
mainView.marketPrice.tooltip=交易所价格提供者 {0}{1}\n最后更新{2}\n提供者节点 URL{3} mainView.marketPrice.tooltip=交易所价格提供者 {0}{1}\n最后更新{2}\n提供者节点 URL{3}
mainView.marketPrice.tooltip.altcoinExtra=如果数字货币在 Poloniex 不可用,我们使用 https://coinmarketcap.com
mainView.balance.available=可用余额 mainView.balance.available=可用余额
mainView.balance.reserved=保证金 mainView.balance.reserved=保证金
mainView.balance.locked=冻结余额 mainView.balance.locked=冻结余额
@ -1015,7 +1014,6 @@ setting.about.providers=数据提供商
setting.about.apisWithFee=Bisq 使用第三方 API 获取法定货币与虚拟币的市场价以及矿工手续费的估价。 setting.about.apisWithFee=Bisq 使用第三方 API 获取法定货币与虚拟币的市场价以及矿工手续费的估价。
setting.about.apis=Bisq 使用第三方 API 获取法定货币与虚拟币的市场价。 setting.about.apis=Bisq 使用第三方 API 获取法定货币与虚拟币的市场价。
setting.about.pricesProvided=交易所价格提供商 setting.about.pricesProvided=交易所价格提供商
setting.about.pricesProviders={0}、{1} 和 {2}
setting.about.feeEstimation.label=矿工手续费估算提供商 setting.about.feeEstimation.label=矿工手续费估算提供商
setting.about.versionDetails=版本详情 setting.about.versionDetails=版本详情
setting.about.version=应用程序版本 setting.about.version=应用程序版本
@ -1153,6 +1151,7 @@ account.password.info=使用密码保护,您需要在将比特币从钱包中
account.seed.backup.title=备份您的钱包还原密钥 account.seed.backup.title=备份您的钱包还原密钥
account.seed.info=请写下钱包还原密钥和时间!\n您可以通过还原密钥和时间在任何时候恢复您的钱包。\n还原密钥用于 BTC 和 BSQ 钱包。\n\n您应该在一张纸上写下还原密钥并且不要保存它们在您的电脑上。\n请注意还原密钥并不能代替备份。\n您需要备份完整的应用程序目录在”账户/备份“界面去恢复有效的应用程序状态和数据。 account.seed.info=请写下钱包还原密钥和时间!\n您可以通过还原密钥和时间在任何时候恢复您的钱包。\n还原密钥用于 BTC 和 BSQ 钱包。\n\n您应该在一张纸上写下还原密钥并且不要保存它们在您的电脑上。\n请注意还原密钥并不能代替备份。\n您需要备份完整的应用程序目录在”账户/备份“界面去恢复有效的应用程序状态和数据。
account.seed.backup.warning=Please note that the seed words are NOT a replacement for a backup.\nYou need to create a backup of the whole application directory from the \"Account/Backup\" screen to recover application state and data.\nImporting seed words is only recommended for emergency cases. The application will not be functional without a proper backup of the database files and keys!\n\nSee the wiki page https://bisq.wiki/Backing_up_application_data for extended info.
account.seed.warn.noPw.msg=您还没有设置一个可以保护还原密钥显示的钱包密码。\n\n要显示还原密钥吗 account.seed.warn.noPw.msg=您还没有设置一个可以保护还原密钥显示的钱包密码。\n\n要显示还原密钥吗
account.seed.warn.noPw.yes=是的,不要再问我 account.seed.warn.noPw.yes=是的,不要再问我
account.seed.enterPw=输入密码查看还原密钥 account.seed.enterPw=输入密码查看还原密钥

View file

@ -242,7 +242,6 @@ mainView.marketPriceWithProvider.label=交易所價格提供商:{0}
mainView.marketPrice.bisqInternalPrice=最新 Bisq 交易的價格 mainView.marketPrice.bisqInternalPrice=最新 Bisq 交易的價格
mainView.marketPrice.tooltip.bisqInternalPrice=外部交易所供應商沒有可用的市場價格。\n顯示的價格是該貨幣的最新 Bisq 交易價格。 mainView.marketPrice.tooltip.bisqInternalPrice=外部交易所供應商沒有可用的市場價格。\n顯示的價格是該貨幣的最新 Bisq 交易價格。
mainView.marketPrice.tooltip=交易所價格提供者 {0}{1}\n最後更新{2}\n提供者節點 URL{3} mainView.marketPrice.tooltip=交易所價格提供者 {0}{1}\n最後更新{2}\n提供者節點 URL{3}
mainView.marketPrice.tooltip.altcoinExtra=如果數字貨幣在 Poloniex 不可用,我們使用 https://coinmarketcap.com
mainView.balance.available=可用餘額 mainView.balance.available=可用餘額
mainView.balance.reserved=保證金 mainView.balance.reserved=保證金
mainView.balance.locked=凍結餘額 mainView.balance.locked=凍結餘額
@ -1015,7 +1014,6 @@ setting.about.providers=資料提供商
setting.about.apisWithFee=Bisq 使用第三方 API 獲取法定貨幣與虛擬幣的市場價以及礦工手續費的估價。 setting.about.apisWithFee=Bisq 使用第三方 API 獲取法定貨幣與虛擬幣的市場價以及礦工手續費的估價。
setting.about.apis=Bisq 使用第三方 API 獲取法定貨幣與虛擬幣的市場價。 setting.about.apis=Bisq 使用第三方 API 獲取法定貨幣與虛擬幣的市場價。
setting.about.pricesProvided=交易所價格提供商 setting.about.pricesProvided=交易所價格提供商
setting.about.pricesProviders={0}、{1} 和 {2}
setting.about.feeEstimation.label=礦工手續費估算提供商 setting.about.feeEstimation.label=礦工手續費估算提供商
setting.about.versionDetails=版本詳情 setting.about.versionDetails=版本詳情
setting.about.version=應用程式版本 setting.about.version=應用程式版本
@ -1153,6 +1151,7 @@ account.password.info=使用密碼保護,您需要在將比特幣從錢包中
account.seed.backup.title=備份您的錢包還原金鑰 account.seed.backup.title=備份您的錢包還原金鑰
account.seed.info=請寫下錢包還原金鑰和時間!\n您可以通過還原金鑰和時間在任何時候恢復您的錢包。\n還原金鑰用於 BTC 和 BSQ 錢包。\n\n您應該在一張紙上寫下還原金鑰並且不要儲存它們在您的電腦上。\n請注意還原金鑰並不能代替備份。\n您需要備份完整的應用程式目錄在”賬戶/備份“介面去恢復有效的應用程式狀態和資料。 account.seed.info=請寫下錢包還原金鑰和時間!\n您可以通過還原金鑰和時間在任何時候恢復您的錢包。\n還原金鑰用於 BTC 和 BSQ 錢包。\n\n您應該在一張紙上寫下還原金鑰並且不要儲存它們在您的電腦上。\n請注意還原金鑰並不能代替備份。\n您需要備份完整的應用程式目錄在”賬戶/備份“介面去恢復有效的應用程式狀態和資料。
account.seed.backup.warning=Please note that the seed words are NOT a replacement for a backup.\nYou need to create a backup of the whole application directory from the \"Account/Backup\" screen to recover application state and data.\nImporting seed words is only recommended for emergency cases. The application will not be functional without a proper backup of the database files and keys!\n\nSee the wiki page https://bisq.wiki/Backing_up_application_data for extended info.
account.seed.warn.noPw.msg=您還沒有設定一個可以保護還原金鑰顯示的錢包密碼。\n\n要顯示還原金鑰嗎 account.seed.warn.noPw.msg=您還沒有設定一個可以保護還原金鑰顯示的錢包密碼。\n\n要顯示還原金鑰嗎
account.seed.warn.noPw.yes=是的,不要再問我 account.seed.warn.noPw.yes=是的,不要再問我
account.seed.enterPw=輸入密碼檢視還原金鑰 account.seed.enterPw=輸入密碼檢視還原金鑰

View file

@ -1,6 +1,6 @@
TXT CHECKPOINTS 1 TXT CHECKPOINTS 1
0 0
315 317
AAAAAAAAB+EH4QfhAAAH4AEAAABjl7tqvU/FIcDT9gcbVlA4nwtFUbxAtOawZzBpAAAAAKzkcK7NqciBjI/ldojNKncrWleVSgDfBCCn3VRrbSxXaw5/Sf//AB0z8Bkv AAAAAAAAB+EH4QfhAAAH4AEAAABjl7tqvU/FIcDT9gcbVlA4nwtFUbxAtOawZzBpAAAAAKzkcK7NqciBjI/ldojNKncrWleVSgDfBCCn3VRrbSxXaw5/Sf//AB0z8Bkv
AAAAAAAAD8EPwQ/BAAAPwAEAAADfP83Sx8MZ9RsrnZCvqzAwqB2Ma+ZesNAJrTfwAAAAACwESaNKhvRgz6WuE7UFdFk1xwzfRY/OIdIOPzX5yaAdjnWUSf//AB0GrNq5 AAAAAAAAD8EPwQ/BAAAPwAEAAADfP83Sx8MZ9RsrnZCvqzAwqB2Ma+ZesNAJrTfwAAAAACwESaNKhvRgz6WuE7UFdFk1xwzfRY/OIdIOPzX5yaAdjnWUSf//AB0GrNq5
AAAAAAAAF6EXoRehAAAXoAEAAADonWzAaUAKd30XT3NnHKobZMnLOuHdzm/xtehsAAAAAD8cUJA6NBIHHcqPHLc4IrfHw+6mjCGu3e+wRO81EvpnMVqrSf//AB1ffy8G AAAAAAAAF6EXoRehAAAXoAEAAADonWzAaUAKd30XT3NnHKobZMnLOuHdzm/xtehsAAAAAD8cUJA6NBIHHcqPHLc4IrfHw+6mjCGu3e+wRO81EvpnMVqrSf//AB1ffy8G
@ -316,3 +316,5 @@ DyhWrFlPqEc/c9+uAAmZAAAgACDcrvMMuRSy9GQxh8BFNt6wIDWYhvU4EQAAAAAAAAAAABVOP5QKoWx9
D5uvExILinkBRJMsAAmg4ADg/yfO2pclcPlRE5XVKCPZeF63mn30v+yHDgAAAAAAAAAAAPaDA4g+h9tjjVWtJ587WMHb07Mcfa1BoDJCANQd+z94wJDEXvaXEhf5l8cN D5uvExILinkBRJMsAAmg4ADg/yfO2pclcPlRE5XVKCPZeF63mn30v+yHDgAAAAAAAAAAAPaDA4g+h9tjjVWtJ587WMHb07Mcfa1BoDJCANQd+z94wJDEXvaXEhf5l8cN
EAgacoNqeblmwryDAAmowAAAACBrw4jKPLZgZpVWF50edwCXFDSCydrEBwAAAAAAAAAAAJzD7FqR8uJdE2JDv+AVjncVcclu70I3QzG/aH2H/bHffOnYXjV/FBdyiZsc EAgacoNqeblmwryDAAmowAAAACBrw4jKPLZgZpVWF50edwCXFDSCydrEBwAAAAAAAAAAAJzD7FqR8uJdE2JDv+AVjncVcclu70I3QzG/aH2H/bHffOnYXjV/FBdyiZsc
EGp3iH6JaGGCuz8LAAmwoAAAQCAgBboCfncZ/s4WkKcsLjW+V4vlPQoDAwAAAAAAAAAAALs0nxXJbP7VZ+Fc3dhRpL6RN6QfYphMw8iCNdqEKGL6e/joXvLUERcyQVAj EGp3iH6JaGGCuz8LAAmwoAAAQCAgBboCfncZ/s4WkKcsLjW+V4vlPQoDAwAAAAAAAAAAALs0nxXJbP7VZ+Fc3dhRpL6RN6QfYphMw8iCNdqEKGL6e/joXvLUERcyQVAj
ENuF9IkxgIodMy/3AAm4gAAAACCTOuNmt+7x2vxFMkHRbDxRElZiABP9DAAAAAAAAAAAAAERAwV0KNK6qPCLxwDpQCkrbHi2jFc/Kldv0N5pJMkE+XP7XhnVERet5rYy
EUyU1REypM3OngUzAAnAYAAAgCB898coFK4fvpBHu54gmeu1eEuspbAwCAAAAAAAAAAAAHQ93L5j0nbFj5FI6biHU0sUuxN+qzGCaK4mc2P3nQYZpEoMXxU6EBeWCMCJ

View file

@ -8,7 +8,7 @@
# pull base image # pull base image
FROM openjdk:8-jdk FROM openjdk:8-jdk
ENV version 1.3.6-SNAPSHOT ENV version 1.3.7
RUN apt-get update && apt-get install -y --no-install-recommends openjfx && rm -rf /var/lib/apt/lists/* && RUN apt-get update && apt-get install -y --no-install-recommends openjfx && rm -rf /var/lib/apt/lists/* &&
apt-get install -y vim fakeroot apt-get install -y vim fakeroot

View file

@ -6,7 +6,7 @@
# - Update version below # - Update version below
# - Ensure JAVA_HOME below is pointing to OracleJDK 10 directory # - Ensure JAVA_HOME below is pointing to OracleJDK 10 directory
version=1.3.6-SNAPSHOT version=1.3.7
version_base=$(echo $version | awk -F'[_-]' '{print $1}') version_base=$(echo $version | awk -F'[_-]' '{print $1}')
if [ ! -f "$JAVA_HOME/bin/javapackager" ]; then if [ ! -f "$JAVA_HOME/bin/javapackager" ]; then
if [ -d "/usr/lib/jvm/jdk-10.0.2" ]; then if [ -d "/usr/lib/jvm/jdk-10.0.2" ]; then

View file

@ -4,7 +4,7 @@
# Prior to running this script: # Prior to running this script:
# - Update version below # - Update version below
version=1.3.6-SNAPSHOT version=1.3.7
base_dir=$( cd "$(dirname "$0")" ; pwd -P )/../../.. base_dir=$( cd "$(dirname "$0")" ; pwd -P )/../../..
package_dir=$base_dir/desktop/package package_dir=$base_dir/desktop/package
release_dir=$base_dir/desktop/release/$version release_dir=$base_dir/desktop/release/$version

View file

@ -5,10 +5,10 @@
<!-- See: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html --> <!-- See: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -->
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.3.6</string> <string>1.3.7</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.3.6</string> <string>1.3.7</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>Bisq</string> <string>Bisq</string>

View file

@ -6,7 +6,7 @@ mkdir -p deploy
set -e set -e
version="1.3.6-SNAPSHOT" version="1.3.7"
cd .. cd ..
./gradlew :desktop:build -x test shadowJar ./gradlew :desktop:build -x test shadowJar

View file

@ -2,7 +2,7 @@
cd ../../ cd ../../
version="1.3.6-SNAPSHOT" version="1.3.7"
target_dir="releases/$version" target_dir="releases/$version"

View file

@ -2,8 +2,8 @@
cd $(dirname $0)/../../../ cd $(dirname $0)/../../../
oldVersion=1.3.5 oldVersion=1.3.6
newVersion=1.3.6 newVersion=1.3.7
find . -type f \( -name "finalize.sh" \ find . -type f \( -name "finalize.sh" \
-o -name "create_app.sh" \ -o -name "create_app.sh" \

View file

@ -11,7 +11,7 @@
@echo off @echo off
set version=1.3.6-SNAPSHOT set version=1.3.7
if not exist "%JAVA_HOME%\bin\javapackager.exe" ( if not exist "%JAVA_HOME%\bin\javapackager.exe" (
if not exist "%ProgramFiles%\Java\jdk-10.0.2" ( if not exist "%ProgramFiles%\Java\jdk-10.0.2" (
echo Javapackager not found. Update JAVA_HOME variable to point to OracleJDK. echo Javapackager not found. Update JAVA_HOME variable to point to OracleJDK.

View file

@ -6,7 +6,7 @@
@echo off @echo off
set version=1.3.6-SNAPSHOT set version=1.3.7
set release_dir=%~dp0..\..\..\releases\%version% set release_dir=%~dp0..\..\..\releases\%version%
set package_dir=%~dp0.. set package_dir=%~dp0..

View file

@ -47,6 +47,7 @@ import bisq.common.BisqException;
import bisq.core.locale.GlobalSettings; import bisq.core.locale.GlobalSettings;
import bisq.core.locale.LanguageUtil; import bisq.core.locale.LanguageUtil;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.core.provider.price.MarketPrice;
import bisq.common.Timer; import bisq.common.Timer;
import bisq.common.UserThread; import bisq.common.UserThread;
@ -95,6 +96,7 @@ import javafx.beans.value.ChangeListener;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.Date;
import java.util.Locale; import java.util.Locale;
import lombok.Setter; import lombok.Setter;
@ -325,8 +327,9 @@ public class MainView extends InitializableView<StackPane, MainViewModel>
primaryNav.getStyleClass().add("nav-primary"); primaryNav.getStyleClass().add("nav-primary");
HBox.setHgrow(primaryNav, Priority.SOMETIMES); HBox.setHgrow(primaryNav, Priority.SOMETIMES);
HBox secondaryNav = new HBox(supportButtonWithBadge, getNavigationSpacer(), settingsButtonWithBadge, HBox secondaryNav = new HBox(supportButtonWithBadge, getNavigationSeparator(),
getNavigationSpacer(), accountButtonWithBadge, getNavigationSpacer(), daoButtonWithBadge); settingsButtonWithBadge, getNavigationSeparator(), accountButtonWithBadge,
getNavigationSeparator(), daoButtonWithBadge);
secondaryNav.getStyleClass().add("nav-secondary"); secondaryNav.getStyleClass().add("nav-secondary");
HBox.setHgrow(secondaryNav, Priority.SOMETIMES); HBox.setHgrow(secondaryNav, Priority.SOMETIMES);
@ -515,7 +518,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel>
private void updateMarketPriceLabel(Label label) { private void updateMarketPriceLabel(Label label) {
if (model.getIsPriceAvailable().get()) { if (model.getIsPriceAvailable().get()) {
if (model.getIsExternallyProvidedPrice().get()) { if (model.getIsExternallyProvidedPrice().get()) {
label.setText(Res.get("mainView.marketPriceWithProvider.label", getPriceProvider())); label.setText(Res.get("mainView.marketPriceWithProvider.label", "Bisq Price Index"));
label.setTooltip(new Tooltip(getPriceProviderTooltipString())); label.setTooltip(new Tooltip(getPriceProviderTooltipString()));
} else { } else {
label.setText(Res.get("mainView.marketPrice.bisqInternalPrice")); label.setText(Res.get("mainView.marketPrice.bisqInternalPrice"));
@ -531,22 +534,14 @@ public class MainView extends InitializableView<StackPane, MainViewModel>
@NotNull @NotNull
private String getPriceProviderTooltipString() { private String getPriceProviderTooltipString() {
String res; String selectedCurrencyCode = model.getPriceFeedService().getCurrencyCode();
if (model.getIsFiatCurrencyPriceFeedSelected().get()) { MarketPrice selectedMarketPrice = model.getPriceFeedService().getMarketPrice(selectedCurrencyCode);
res = Res.get("mainView.marketPrice.tooltip",
"https://bitcoinaverage.com", return Res.get("mainView.marketPrice.tooltip",
"Bisq Price Index for " + selectedCurrencyCode,
"", "",
DisplayUtils.formatTime(model.getPriceFeedService().getLastRequestTimeStampBtcAverage()), DisplayUtils.formatTime(new Date(selectedMarketPrice.getTimestampSec())),
model.getPriceFeedService().getProviderNodeAddress()); model.getPriceFeedService().getProviderNodeAddress());
} else {
String altcoinExtra = "\n" + Res.get("mainView.marketPrice.tooltip.altcoinExtra");
res = Res.get("mainView.marketPrice.tooltip",
"https://poloniex.com",
altcoinExtra,
DisplayUtils.formatTime(model.getPriceFeedService().getLastRequestTimeStampPoloniex()),
model.getPriceFeedService().getProviderNodeAddress());
}
return res;
} }
private VBox createSplashScreen() { private VBox createSplashScreen() {

View file

@ -163,7 +163,23 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
seedWordsTextArea.getStyleClass().remove("validation-error"); seedWordsTextArea.getStyleClass().remove("validation-error");
restoreDatePicker.getStyleClass().remove("validation-error"); restoreDatePicker.getStyleClass().remove("validation-error");
String key = "showBackupWarningAtSeedPhrase";
if (DontShowAgainLookup.showAgain(key)) {
new Popup().warning(Res.get("account.seed.backup.warning"))
.onAction(() -> {
showSeedPhrase();
})
.actionButtonText(Res.get("shared.iUnderstand"))
.useIUnderstandButton()
.dontShowAgainId(key)
.hideCloseButton()
.show();
} else {
showSeedPhrase();
}
}
public void showSeedPhrase() {
DeterministicSeed keyChainSeed = btcWalletService.getKeyChainSeed(); DeterministicSeed keyChainSeed = btcWalletService.getKeyChainSeed();
// wallet creation date is not encrypted // wallet creation date is not encrypted
walletCreationDate = Instant.ofEpochSecond(walletsManager.getChainSeedCreationTimeSeconds()).atZone(ZoneId.systemDefault()).toLocalDate(); walletCreationDate = Instant.ofEpochSecond(walletsManager.getChainSeedCreationTimeSeconds()).atZone(ZoneId.systemDefault()).toLocalDate();

View file

@ -718,8 +718,14 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
return totalToPayAsCoin; return totalToPayAsCoin;
} }
Coin getBsqBalance() { Coin getUsableBsqBalance() {
return bsqWalletService.getAvailableConfirmedBalance(); // we have to keep a minimum amount of BSQ == bitcoin dust limit
// otherwise there would be dust violations for change UTXOs
// essentially means the minimum usable balance of BSQ is 5.46
Coin usableBsqBalance = bsqWalletService.getAvailableConfirmedBalance().subtract(Restrictions.getMinNonDustOutput());
if (usableBsqBalance.isNegative())
usableBsqBalance = Coin.ZERO;
return usableBsqBalance;
} }
public void setMarketPriceAvailable(boolean marketPriceAvailable) { public void setMarketPriceAvailable(boolean marketPriceAvailable) {

View file

@ -373,9 +373,9 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
String message = null; String message = null;
if (makerFee != null) { if (makerFee != null) {
message = Res.get("popup.warning.insufficientBsqFundsForBtcFeePayment", message = Res.get("popup.warning.insufficientBsqFundsForBtcFeePayment",
bsqFormatter.formatCoinWithCode(makerFee.subtract(model.getDataModel().getBsqBalance()))); bsqFormatter.formatCoinWithCode(makerFee.subtract(model.getDataModel().getUsableBsqBalance())));
} else if (model.getDataModel().getBsqBalance().isZero()) } else if (model.getDataModel().getUsableBsqBalance().isZero())
message = Res.get("popup.warning.noBsqFundsForBtcFeePayment"); message = Res.get("popup.warning.noBsqFundsForBtcFeePayment");
if (message != null) if (message != null)
@ -1109,9 +1109,9 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
String missingBsq = null; String missingBsq = null;
if (makerFee != null) { if (makerFee != null) {
missingBsq = Res.get("popup.warning.insufficientBsqFundsForBtcFeePayment", missingBsq = Res.get("popup.warning.insufficientBsqFundsForBtcFeePayment",
bsqFormatter.formatCoinWithCode(makerFee.subtract(model.getDataModel().getBsqBalance()))); bsqFormatter.formatCoinWithCode(makerFee.subtract(model.getDataModel().getUsableBsqBalance())));
} else if (model.getDataModel().getBsqBalance().isZero()) { } else if (model.getDataModel().getUsableBsqBalance().isZero()) {
missingBsq = Res.get("popup.warning.noBsqFundsForBtcFeePayment"); missingBsq = Res.get("popup.warning.noBsqFundsForBtcFeePayment");
} }

View file

@ -1153,6 +1153,10 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
String info; String info;
String timeSinceSigning; String timeSinceSigning;
boolean needsSigning = PaymentMethod.hasChargebackRisk(
item.getOffer().getPaymentMethod(), item.getOffer().getCurrencyCode());
if (needsSigning) {
if (accountAgeWitnessService.hasSignedWitness(item.getOffer())) { if (accountAgeWitnessService.hasSignedWitness(item.getOffer())) {
AccountAgeWitnessService.SignState signState = accountAgeWitnessService.getSignState(item.getOffer()); AccountAgeWitnessService.SignState signState = accountAgeWitnessService.getSignState(item.getOffer());
icon = GUIUtil.getIconForSignState(signState); icon = GUIUtil.getIconForSignState(signState);
@ -1163,9 +1167,6 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
timeSinceSigning = Res.get("offerbook.timeSinceSigning.daysSinceSigning", timeSinceSigning = Res.get("offerbook.timeSinceSigning.daysSinceSigning",
daysSinceSigning); daysSinceSigning);
} else { } else {
boolean needsSigning = PaymentMethod.hasChargebackRisk(
item.getOffer().getPaymentMethod(), item.getOffer().getCurrencyCode());
if (needsSigning) {
AccountAgeWitnessService.SignState signState = accountAgeWitnessService.getSignState(item.getOffer()); AccountAgeWitnessService.SignState signState = accountAgeWitnessService.getSignState(item.getOffer());
icon = GUIUtil.getIconForSignState(signState); icon = GUIUtil.getIconForSignState(signState);
@ -1180,12 +1181,13 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
info = Res.get("shared.notSigned"); info = Res.get("shared.notSigned");
timeSinceSigning = Res.get("offerbook.timeSinceSigning.notSigned"); timeSinceSigning = Res.get("offerbook.timeSinceSigning.notSigned");
} }
}
} else { } else {
icon = MaterialDesignIcon.INFORMATION_OUTLINE; icon = MaterialDesignIcon.INFORMATION_OUTLINE;
info = Res.get("shared.notSigned.noNeed"); info = Res.get("shared.notSigned.noNeed");
timeSinceSigning = Res.get("offerbook.timeSinceSigning.notSigned.noNeed"); timeSinceSigning = Res.get("offerbook.timeSinceSigning.notSigned.noNeed");
} }
}
InfoAutoTooltipLabel label = new InfoAutoTooltipLabel(timeSinceSigning, icon, ContentDisplay.RIGHT, info); InfoAutoTooltipLabel label = new InfoAutoTooltipLabel(timeSinceSigning, icon, ContentDisplay.RIGHT, info);
setGraphic(label); setGraphic(label);

View file

@ -639,8 +639,14 @@ class TakeOfferDataModel extends OfferDataModel {
return offer.getSellerSecurityDeposit(); return offer.getSellerSecurityDeposit();
} }
public Coin getBsqBalance() { public Coin getUsableBsqBalance() {
return bsqWalletService.getAvailableConfirmedBalance(); // we have to keep a minimum amount of BSQ == bitcoin dust limit
// otherwise there would be dust violations for change UTXOs
// essentially means the minimum usable balance of BSQ is 5.46
Coin usableBsqBalance = bsqWalletService.getAvailableConfirmedBalance().subtract(Restrictions.getMinNonDustOutput());
if (usableBsqBalance.isNegative())
usableBsqBalance = Coin.ZERO;
return usableBsqBalance;
} }
public boolean isHalCashAccount() { public boolean isHalCashAccount() {

View file

@ -927,9 +927,9 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
String missingBsq = null; String missingBsq = null;
if (takerFee != null) { if (takerFee != null) {
missingBsq = Res.get("popup.warning.insufficientBsqFundsForBtcFeePayment", missingBsq = Res.get("popup.warning.insufficientBsqFundsForBtcFeePayment",
bsqFormatter.formatCoinWithCode(takerFee.subtract(model.dataModel.getBsqBalance()))); bsqFormatter.formatCoinWithCode(takerFee.subtract(model.dataModel.getUsableBsqBalance())));
} else if (model.dataModel.getBsqBalance().isZero()) { } else if (model.dataModel.getUsableBsqBalance().isZero()) {
missingBsq = Res.get("popup.warning.noBsqFundsForBtcFeePayment"); missingBsq = Res.get("popup.warning.noBsqFundsForBtcFeePayment");
} }
@ -1225,9 +1225,9 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
String message = null; String message = null;
if (takerFee != null) if (takerFee != null)
message = Res.get("popup.warning.insufficientBsqFundsForBtcFeePayment", message = Res.get("popup.warning.insufficientBsqFundsForBtcFeePayment",
bsqFormatter.formatCoinWithCode(takerFee.subtract(model.dataModel.getBsqBalance()))); bsqFormatter.formatCoinWithCode(takerFee.subtract(model.dataModel.getUsableBsqBalance())));
else if (model.dataModel.getBsqBalance().isZero()) else if (model.dataModel.getUsableBsqBalance().isZero())
message = Res.get("popup.warning.noBsqFundsForBtcFeePayment"); message = Res.get("popup.warning.noBsqFundsForBtcFeePayment");
if (message != null) if (message != null)

View file

@ -285,29 +285,6 @@ public class ProposalResultsWindow extends TabbedOverlay<ProposalResultsWindow>
}); });
votesTableView.getColumns().add(column); votesTableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.results.votes.table.header.stakeAndMerit"));
column.setSortable(false);
column.setMinWidth(100);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<VoteListItem, VoteListItem> call(
TableColumn<VoteListItem, VoteListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final VoteListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getMeritAndStake());
else
setText("");
}
};
}
});
votesTableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.results.votes.table.header.merit")); column = new AutoTooltipTableColumn<>(Res.get("dao.results.votes.table.header.merit"));
column.setSortable(false); column.setSortable(false);
column.setMinWidth(100); column.setMinWidth(100);
@ -354,6 +331,29 @@ public class ProposalResultsWindow extends TabbedOverlay<ProposalResultsWindow>
}); });
votesTableView.getColumns().add(column); votesTableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.results.votes.table.header.stakeAndMerit"));
column.setSortable(false);
column.setMinWidth(100);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<VoteListItem, VoteListItem> call(
TableColumn<VoteListItem, VoteListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final VoteListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getMeritAndStake());
else
setText("");
}
};
}
});
votesTableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.results.votes.table.header.vote")); column = new AutoTooltipTableColumn<>(Res.get("dao.results.votes.table.header.vote"));
column.setSortable(false); column.setSortable(false);
column.setMinWidth(50); column.setMinWidth(50);

View file

@ -78,10 +78,8 @@ public class AboutView extends ActivatableView<GridPane, Void> {
label = addLabel(root, gridRow, Res.get(isBtc ? "setting.about.apisWithFee" : "setting.about.apis"), Layout.TWICE_FIRST_ROW_AND_GROUP_DISTANCE); label = addLabel(root, gridRow, Res.get(isBtc ? "setting.about.apisWithFee" : "setting.about.apis"), Layout.TWICE_FIRST_ROW_AND_GROUP_DISTANCE);
label.setWrapText(true); label.setWrapText(true);
GridPane.setHalignment(label, HPos.LEFT); GridPane.setHalignment(label, HPos.LEFT);
addCompactTopLabelTextField(root, ++gridRow, Res.get("setting.about.pricesProvided"), Res.get("setting.about.pricesProviders", addCompactTopLabelTextField(root, ++gridRow, Res.get("setting.about.pricesProvided"),
"BitcoinAverage (https://bitcoinaverage.com)", "Bisq Price Index (https://bisq.wiki/Bisq_Price_Index)");
"Poloniex (https://poloniex.com)",
"Coinmarketcap (https://coinmarketcap.com)"));
if (isBtc) if (isBtc)
addCompactTopLabelTextField(root, ++gridRow, Res.get("setting.about.feeEstimation.label"), "mempool.space (https://mempool.space)"); addCompactTopLabelTextField(root, ++gridRow, Res.get("setting.about.feeEstimation.label"), "mempool.space (https://mempool.space)");

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -5,7 +5,6 @@ Run the following commands:
heroku create heroku create
heroku buildpacks:add heroku/gradle heroku buildpacks:add heroku/gradle
heroku config:set BITCOIN_AVG_PUBKEY=[your pubkey] BITCOIN_AVG_PRIVKEY=[your privkey]
git push heroku master git push heroku master
curl https://your-app-123456.herokuapp.com/getAllMarketPrices curl https://your-app-123456.herokuapp.com/getAllMarketPrices

View file

@ -24,7 +24,6 @@ Operating a production pricenode is a valuable service to the Bisq network, and
To run a pricenode, you will need: To run a pricenode, you will need:
- [BitcoinAverage API keys](https://bitcoinaverage.com/en/plans). Free plans are fine for local development or personal nodes; paid plans should be used for well-known production nodes.
- JDK 8 if you want to build and run a node locally. - JDK 8 if you want to build and run a node locally.
- The `tor` binary (e.g. `brew install tor`) if you want to run a hidden service locally. - The `tor` binary (e.g. `brew install tor`) if you want to run a hidden service locally.
@ -40,21 +39,6 @@ curl -s https://raw.githubusercontent.com/bisq-network/bisq/master/pricenode/ins
At the end of the installer script, it should print your Tor onion hostname. At the end of the installer script, it should print your Tor onion hostname.
### Setting your BitcoinAverage API keys
Open `/etc/default/bisq-pricenode.env` in a text editor and look for these lines:
```bash
BITCOIN_AVG_PUBKEY=foo
BITCOIN_AVG_PRIVKEY=bar
```
Add your pubkey and privkey and then reload/restart bisq-pricenode service:
```bash
systemctl daemon-reload
systemctl restart bisq-pricenode
```
### Test ### Test
To manually test endpoints, run each of the following: To manually test endpoints, run each of the following:

View file

@ -1,3 +1 @@
BITCOIN_AVG_PUBKEY=foo
BITCOIN_AVG_PRIVKEY=bar
JAVA_OPTS="" JAVA_OPTS=""

View file

@ -5,7 +5,7 @@ After=network.target
[Service] [Service]
SyslogIdentifier=bisq-pricenode SyslogIdentifier=bisq-pricenode
EnvironmentFile=/etc/default/bisq-pricenode.env EnvironmentFile=/etc/default/bisq-pricenode.env
ExecStart=/bisq/bisq/bisq-pricenode 2 2 ExecStart=/bisq/bisq/bisq-pricenode 2
ExecStop=/bin/kill -TERM ${MAINPID} ExecStop=/bin/kill -TERM ${MAINPID}
Restart=on-failure Restart=on-failure

View file

@ -2,7 +2,7 @@
while true while true
do do
echo `date` "(Re)-starting node" echo `date` "(Re)-starting node"
BITCOIN_AVG_PUBKEY=$BTCAVERAGE_PUBKEY BITCOIN_AVG_PRIVKEY=$BTCAVERAGE_PRIVKEY java -jar ./build/libs/bisq-pricenode.jar 2 2 java -jar ./build/libs/bisq-pricenode.jar 2 2
echo `date` "node terminated unexpectedly!!" echo `date` "node terminated unexpectedly!!"
sleep 3 sleep 3
done done

View file

@ -36,7 +36,7 @@ sudo -H -i -u "${ROOT_USER}" DEBIAN_FRONTEND=noninteractive apt-get upgrade -qq
echo "[*] Installing Git LFS" echo "[*] Installing Git LFS"
sudo -H -i -u "${ROOT_USER}" curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash sudo -H -i -u "${ROOT_USER}" curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash
sudo -H -i -u "${ROOT_USER}" apt-get install git-lfs sudo -H -i -u "${ROOT_USER}" apt-get -y install git-lfs
sudo -H -i -u "${ROOT_USER}" git lfs install sudo -H -i -u "${ROOT_USER}" git lfs install
echo "[*] Installing Tor" echo "[*] Installing Tor"
@ -46,7 +46,7 @@ echo "[*] Adding Tor configuration"
if ! grep "${BISQ_TORHS}" /etc/tor/torrc >/dev/null 2>&1;then if ! grep "${BISQ_TORHS}" /etc/tor/torrc >/dev/null 2>&1;then
sudo -H -i -u "${ROOT_USER}" sh -c "echo HiddenServiceDir ${TOR_RESOURCES}/${BISQ_TORHS}/ >> ${TOR_CONF}" sudo -H -i -u "${ROOT_USER}" sh -c "echo HiddenServiceDir ${TOR_RESOURCES}/${BISQ_TORHS}/ >> ${TOR_CONF}"
sudo -H -i -u "${ROOT_USER}" sh -c "echo HiddenServicePort 80 127.0.0.1:8080 >> ${TOR_CONF}" sudo -H -i -u "${ROOT_USER}" sh -c "echo HiddenServicePort 80 127.0.0.1:8080 >> ${TOR_CONF}"
sudo -H -i -u "${ROOT_USER}" sh -c "echo HiddenServiceVersion 2 >> ${TOR_CONF}" sudo -H -i -u "${ROOT_USER}" sh -c "echo HiddenServiceVersion 3 >> ${TOR_CONF}"
fi fi
echo "[*] Creating Bisq user with Tor access" echo "[*] Creating Bisq user with Tor access"
@ -60,8 +60,8 @@ echo "[*] Cloning Bisq repo"
sudo -H -i -u "${BISQ_USER}" git config --global advice.detachedHead false sudo -H -i -u "${BISQ_USER}" git config --global advice.detachedHead false
sudo -H -i -u "${BISQ_USER}" git clone --branch "${BISQ_REPO_TAG}" "${BISQ_REPO_URL}" "${BISQ_HOME}/${BISQ_REPO_NAME}" sudo -H -i -u "${BISQ_USER}" git clone --branch "${BISQ_REPO_TAG}" "${BISQ_REPO_URL}" "${BISQ_HOME}/${BISQ_REPO_NAME}"
echo "[*] Installing OpenJDK 10.0.2 from Bisq repo" echo "[*] Installing OpenJDK 11"
sudo -H -i -u "${ROOT_USER}" "${BISQ_HOME}/${BISQ_REPO_NAME}/scripts/install_java.sh" sudo -H -i -u "${ROOT_USER}" apt-get install -qq -y openjdk-11-jdk
echo "[*] Checking out Bisq ${BISQ_LATEST_RELEASE}" echo "[*] Checking out Bisq ${BISQ_LATEST_RELEASE}"
sudo -H -i -u "${BISQ_USER}" sh -c "cd ${BISQ_HOME}/${BISQ_REPO_NAME} && git checkout ${BISQ_LATEST_RELEASE}" sudo -H -i -u "${BISQ_USER}" sh -c "cd ${BISQ_HOME}/${BISQ_REPO_NAME} && git checkout ${BISQ_LATEST_RELEASE}"

View file

@ -19,24 +19,54 @@ package bisq.price.spot;
import bisq.price.PriceProvider; import bisq.price.PriceProvider;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.TradeCurrency;
import org.knowm.xchange.Exchange;
import org.knowm.xchange.ExchangeFactory;
import org.knowm.xchange.currency.Currency;
import org.knowm.xchange.currency.CurrencyPair;
import org.knowm.xchange.dto.marketdata.Ticker;
import org.knowm.xchange.exceptions.ExchangeException;
import org.knowm.xchange.exceptions.NotYetImplementedForExchangeException;
import org.knowm.xchange.service.marketdata.MarketDataService;
import org.knowm.xchange.service.marketdata.params.CurrencyPairsParam;
import org.knowm.xchange.service.marketdata.params.Params;
import java.time.Duration; import java.time.Duration;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/** /**
* Abstract base class for providers of bitcoin {@link ExchangeRate} data. Implementations * Abstract base class for providers of bitcoin {@link ExchangeRate} data. Implementations
* are marked with the {@link org.springframework.stereotype.Component} annotation in * are marked with the {@link org.springframework.stereotype.Component} annotation in
* order to be discovered via classpath scanning. Implementations are also marked with the * order to be discovered via classpath scanning. If multiple
* {@link org.springframework.core.annotation.Order} annotation to determine their * {@link ExchangeRateProvider}s retrieve rates for the same currency, then the
* precedence over each other in the case of two or more services returning exchange rate * {@link ExchangeRateService} will average them out and expose an aggregate rate.
* data for the same currency pair. In such cases, results from the provider with the
* higher order value will take precedence over the provider with a lower value,
* presuming that such providers are being iterated over in an ordered list.
* *
* @see ExchangeRateService#ExchangeRateService(java.util.List) * @see ExchangeRateService#getAllMarketPrices()
*/ */
public abstract class ExchangeRateProvider extends PriceProvider<Set<ExchangeRate>> { public abstract class ExchangeRateProvider extends PriceProvider<Set<ExchangeRate>> {
public static final Set<String> SUPPORTED_CRYPTO_CURRENCIES = CurrencyUtil.getAllSortedCryptoCurrencies().stream()
.map(TradeCurrency::getCode)
.collect(Collectors.toSet());
public static final Set<String> SUPPORTED_FIAT_CURRENCIES = CurrencyUtil.getAllSortedFiatCurrencies().stream()
.map(TradeCurrency::getCode)
.collect(Collectors.toSet());
private final String name; private final String name;
private final String prefix; private final String prefix;
@ -60,4 +90,196 @@ public abstract class ExchangeRateProvider extends PriceProvider<Set<ExchangeRat
.filter(e -> "USD".equals(e.getCurrency()) || "LTC".equals(e.getCurrency())) .filter(e -> "USD".equals(e.getCurrency()) || "LTC".equals(e.getCurrency()))
.forEach(e -> log.info("BTC/{}: {}", e.getCurrency(), e.getPrice())); .forEach(e -> log.info("BTC/{}: {}", e.getCurrency(), e.getPrice()));
} }
/**
* @param exchangeClass Class of the {@link Exchange} for which the rates should be
* polled
* @return Exchange rates for Bisq-supported fiat currencies and altcoins in the
* specified {@link Exchange}
*
* @see CurrencyUtil#getAllSortedFiatCurrencies()
* @see CurrencyUtil#getAllSortedCryptoCurrencies()
*/
protected Set<ExchangeRate> doGet(Class<? extends Exchange> exchangeClass) {
Set<ExchangeRate> result = new HashSet<ExchangeRate>();
// Initialize XChange objects
Exchange exchange = ExchangeFactory.INSTANCE.createExchange(exchangeClass.getName());
MarketDataService marketDataService = exchange.getMarketDataService();
// Retrieve all currency pairs supported by the exchange
List<CurrencyPair> allCurrencyPairsOnExchange = exchange.getExchangeSymbols();
// Find out which currency pairs we are interested in polling ("desired pairs")
// This will be the intersection of:
// 1) the pairs available on the exchange, and
// 2) the pairs Bisq considers relevant / valid
// This will result in two lists of desired pairs (fiat and alts)
// Find the desired fiat pairs (pair format is BTC-FIAT)
List<CurrencyPair> desiredFiatPairs = allCurrencyPairsOnExchange.stream()
.filter(cp -> cp.base.equals(Currency.BTC))
.filter(cp -> SUPPORTED_FIAT_CURRENCIES.contains(cp.counter.getCurrencyCode()))
.collect(Collectors.toList());
// Find the desired altcoin pairs (pair format is ALT-BTC)
List<CurrencyPair> desiredCryptoPairs = allCurrencyPairsOnExchange.stream()
.filter(cp -> cp.counter.equals(Currency.BTC))
.filter(cp -> SUPPORTED_CRYPTO_CURRENCIES.contains(cp.base.getCurrencyCode()))
.collect(Collectors.toList());
// Retrieve in bulk all tickers offered by the exchange
// The benefits of this approach (vs polling each ticker) are twofold:
// 1) the polling of the exchange is faster (one HTTP call vs several)
// 2) it's easier to stay below any API rate limits the exchange might have
List<Ticker> tickersRetrievedFromExchange = new ArrayList<>();
try {
tickersRetrievedFromExchange = marketDataService.getTickers(new CurrencyPairsParam() {
/**
* The {@link MarketDataService#getTickers(Params)} interface requires a
* {@link CurrencyPairsParam} argument when polling for tickers in bulk.
* This parameter is meant to indicate a list of currency pairs for which
* the tickers should be polled. However, the actual implementations for
* the different exchanges differ, for example:
* - some will ignore it (and retrieve all available tickers)
* - some will require it (and will fail if a null or empty list is given)
* - some will properly handle it
*
* We take a simplistic approach, namely:
* - for providers that require such a filter, specify one
* - for all others, do not specify one
*
* We make this distinction using
* {@link ExchangeRateProvider#requiresFilterDuringBulkTickerRetrieval}
*
* @return Filter (list of desired currency pairs) to be used during bulk
* ticker retrieval
*/
@Override
public Collection<CurrencyPair> getCurrencyPairs() {
// If required by the exchange implementation, specify a filter
// (list of pairs which should be retrieved)
if (requiresFilterDuringBulkTickerRetrieval()) {
return Stream.of(desiredFiatPairs, desiredCryptoPairs)
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
// Otherwise, specify an empty list, indicating that the API should
// simply return all available tickers
return Collections.emptyList();
}
});
if (tickersRetrievedFromExchange.isEmpty()) {
// If the bulk ticker retrieval went through, but no tickers were
// retrieved, this is a strong indication that this specific exchange
// needs a specific list of pairs given as argument, for bulk retrieval to
// work. See requiresFilterDuringBulkTickerRetrieval()
throw new IllegalArgumentException("No tickers retrieved, " +
"exchange requires explicit filter argument during bulk retrieval?");
}
} catch (NotYetImplementedForExchangeException e) {
// Thrown when a provider has no marketDataService.getTickers() implementation
// either because the exchange API does not provide it, or because it has not
// been implemented yet in the knowm xchange library
// In this case (retrieval of bulk tickers is not possible) retrieve the
// tickers one by one
List<Ticker> finalTickersRetrievedFromExchange = tickersRetrievedFromExchange;
Stream.of(desiredFiatPairs, desiredCryptoPairs)
.flatMap(Collection::stream)
.collect(Collectors.toList())
.forEach(cp -> {
try {
// This is done in a loop, and can therefore result in a burst
// of API calls. Some exchanges do not allow bursts
// A simplistic solution is to delay every call by 1 second
// TODO Switch to using a more elegant solution (per exchange)
// like ResilienceSpecification (needs knowm xchange libs v5)
if (getMarketDataCallDelay() > 0) {
Thread.sleep(getMarketDataCallDelay());
}
Ticker ticker = marketDataService.getTicker(cp);
finalTickersRetrievedFromExchange.add(ticker);
} catch (IOException | InterruptedException ioException) {
ioException.printStackTrace();
log.error("Could not query tickers for " + getName(), e);
}
});
} catch (ExchangeException | // Errors reported by the exchange (rate limit, etc)
IOException | // Errors while trying to connect to the API (timeouts, etc)
// Potential error when integrating new exchange (hints that exchange
// provider implementation needs to overwrite
// requiresFilterDuringBulkTickerRetrieval() and have it return true )
IllegalArgumentException e) {
// Catch and handle all other possible exceptions
// If there was a problem with polling this exchange, return right away,
// since there are no results to parse and process
log.error("Could not query tickers for provider " + getName(), e);
return result;
}
// Create an ExchangeRate for each desired currency pair ticker that was retrieved
Predicate<Ticker> isDesiredFiatPair = t -> desiredFiatPairs.contains(t.getCurrencyPair());
Predicate<Ticker> isDesiredCryptoPair = t -> desiredCryptoPairs.contains(t.getCurrencyPair());
tickersRetrievedFromExchange.stream()
.filter(isDesiredFiatPair.or(isDesiredCryptoPair)) // Only consider desired pairs
.forEach(t -> {
// All tickers here match all requirements
// We have two kinds of currency pairs, BTC-FIAT and ALT-BTC
// In the first one, BTC is the first currency of the pair
// In the second type, BTC is listed as the second currency
// Distinguish between the two and create ExchangeRates accordingly
// In every Bisq ExchangeRate, BTC is one currency in the pair
// Extract the other currency from the ticker, to create ExchangeRates
String otherExchangeRateCurrency;
if (t.getCurrencyPair().base.equals(Currency.BTC)) {
otherExchangeRateCurrency = t.getCurrencyPair().counter.getCurrencyCode();
} else {
otherExchangeRateCurrency = t.getCurrencyPair().base.getCurrencyCode();
}
result.add(new ExchangeRate(
otherExchangeRateCurrency,
t.getLast(),
// Some exchanges do not provide timestamps
t.getTimestamp() == null ? new Date() : t.getTimestamp(),
this.getName()
));
});
return result;
}
/**
* Specifies optional delay between certain kind of API calls that can result in
* bursts. We want to avoid bursts, because this can cause certain exchanges to
* temporarily restrict access to the pricenode IP.
*
* @return Amount of milliseconds of delay between marketDataService.getTicker calls.
* By default 0, but can be overwritten by each provider.
*/
protected long getMarketDataCallDelay() {
return 0;
}
/**
* @return Whether or not the bulk retrieval of tickers from the exchange requires an
* explicit filter (list of desired pairs) or not. If true, the
* {@link MarketDataService#getTickers(Params)} call will be constructed and given as
* argument, which acts as a filter indicating for which pairs the ticker should be
* retrieved. If false, {@link MarketDataService#getTickers(Params)} will be called
* with an empty argument, indicating that the API should simply return all available
* tickers on the exchange
*/
protected boolean requiresFilterDuringBulkTickerRetrieval() {
return false;
}
} }

View file

@ -17,20 +17,25 @@
package bisq.price.spot; package bisq.price.spot;
import bisq.price.spot.providers.BitcoinAverage;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.OptionalDouble;
import java.util.Set; import java.util.Set;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import static java.util.Arrays.asList;
/** /**
* High-level {@link ExchangeRate} data operations. * High-level {@link ExchangeRate} data operations.
*/ */
@ -53,32 +58,105 @@ class ExchangeRateService {
public Map<String, Object> getAllMarketPrices() { public Map<String, Object> getAllMarketPrices() {
Map<String, Object> metadata = new LinkedHashMap<>(); Map<String, Object> metadata = new LinkedHashMap<>();
Map<String, ExchangeRate> allExchangeRates = new LinkedHashMap<>(); Map<String, ExchangeRate> aggregateExchangeRates = getAggregateExchangeRates();
providers.forEach(p -> { providers.forEach(p -> {
Set<ExchangeRate> exchangeRates = p.get(); Set<ExchangeRate> exchangeRates = p.get();
// Specific metadata fields for specific providers are expected by the client,
// mostly for historical reasons
// Therefore, add metadata fields for all known providers
// Rates are encapsulated in the "data" map below
metadata.putAll(getMetadata(p, exchangeRates)); metadata.putAll(getMetadata(p, exchangeRates));
exchangeRates.forEach(e ->
allExchangeRates.put(e.getCurrency(), e)
);
}); });
return new LinkedHashMap<String, Object>() {{ LinkedHashMap<String, Object> result = new LinkedHashMap<>();
putAll(metadata); result.putAll(metadata);
// Use a sorted list by currency code to make comparision of json data between different // Use a sorted list by currency code to make comparision of json data between
// price nodes easier // different price nodes easier
List<ExchangeRate> values = new ArrayList<>(allExchangeRates.values()); List<ExchangeRate> values = new ArrayList<>(aggregateExchangeRates.values());
values.sort(Comparator.comparing(ExchangeRate::getCurrency)); values.sort(Comparator.comparing(ExchangeRate::getCurrency));
put("data", values); result.put("data", values);
}};
return result;
}
/**
* For each currency, create an aggregate {@link ExchangeRate} based on the currency's
* rates from all providers. If multiple providers have rates for the currency, then
* aggregate price = average of retrieved prices. If a single provider has rates for
* the currency, then aggregate price = the rate from that provider.
*
* @return Aggregate {@link ExchangeRate}s based on info from all providers, indexed
* by currency code
*/
private Map<String, ExchangeRate> getAggregateExchangeRates() {
Map<String, ExchangeRate> aggregateExchangeRates = new HashMap<>();
// Query all providers and collect all exchange rates, grouped by currency code
// key = currency code
// value = list of exchange rates
Map<String, List<ExchangeRate>> currencyCodeToExchangeRates = getCurrencyCodeToExchangeRates();
// For each currency code, calculate aggregate rate
currencyCodeToExchangeRates.forEach((currencyCode, exchangeRateList) -> {
if (exchangeRateList.isEmpty()) {
// If the map was built incorrectly and this currency points to an empty
// list of rates, skip it
return;
}
ExchangeRate aggregateExchangeRate;
if (exchangeRateList.size() == 1) {
// If a single provider has rates for this currency, then aggregate = rate
// from that provider
aggregateExchangeRate = exchangeRateList.get(0);
} else {
// If multiple providers have rates for this currency, then
// aggregate = average of the rates
OptionalDouble opt = exchangeRateList.stream().mapToDouble(ExchangeRate::getPrice).average();
// List size > 1, so opt is always set
double priceAvg = opt.orElseThrow(IllegalStateException::new);
aggregateExchangeRate = new ExchangeRate(
currencyCode,
BigDecimal.valueOf(priceAvg),
new Date(), // timestamp = time when avg is calculated
"Bisq-Aggregate");
}
aggregateExchangeRates.put(aggregateExchangeRate.getCurrency(), aggregateExchangeRate);
});
return aggregateExchangeRates;
}
/**
* @return All {@link ExchangeRate}s from all providers, grouped by currency code
*/
private Map<String, List<ExchangeRate>> getCurrencyCodeToExchangeRates() {
Map<String, List<ExchangeRate>> currencyCodeToExchangeRates = new HashMap<>();
for (ExchangeRateProvider p : providers) {
for (ExchangeRate exchangeRate : p.get()) {
String currencyCode = exchangeRate.getCurrency();
if (currencyCodeToExchangeRates.containsKey(currencyCode)) {
List<ExchangeRate> l = new ArrayList<>(currencyCodeToExchangeRates.get(currencyCode));
l.add(exchangeRate);
currencyCodeToExchangeRates.put(currencyCode, l);
} else {
currencyCodeToExchangeRates.put(currencyCode, asList(exchangeRate));
}
}
}
return currencyCodeToExchangeRates;
} }
private Map<String, Object> getMetadata(ExchangeRateProvider provider, Set<ExchangeRate> exchangeRates) { private Map<String, Object> getMetadata(ExchangeRateProvider provider, Set<ExchangeRate> exchangeRates) {
Map<String, Object> metadata = new LinkedHashMap<>(); Map<String, Object> metadata = new LinkedHashMap<>();
// In case a provider is not available we still want to deliver the data of the other providers, so we catch // In case a provider is not available we still want to deliver the data of the
// a possible exception and leave timestamp at 0. The Bisq app will check if the timestamp is in a tolerance // other providers, so we catch a possible exception and leave timestamp at 0. The
// window and if it is too old it will show that the price is not available. // Bisq app will check if the timestamp is in a tolerance window and if it is too
// old it will show that the price is not available.
long timestamp = 0; long timestamp = 0;
try { try {
timestamp = getTimestamp(provider, exchangeRates); timestamp = getTimestamp(provider, exchangeRates);
@ -88,10 +166,6 @@ class ExchangeRateService {
t.printStackTrace(); t.printStackTrace();
} }
if (provider instanceof BitcoinAverage.Local) {
metadata.put("btcAverageTs", timestamp);
}
String prefix = provider.getPrefix(); String prefix = provider.getPrefix();
metadata.put(prefix + "Ts", timestamp); metadata.put(prefix + "Ts", timestamp);
metadata.put(prefix + "Count", exchangeRates.size()); metadata.put(prefix + "Count", exchangeRates.size());

View file

@ -0,0 +1,45 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.btcmarkets.BTCMarketsExchange;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class BTCMarkets extends ExchangeRateProvider {
public BTCMarkets() {
super("BTCMARKETS", "btcmarkets", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: AUD
// Supported alts: ETH, LTC
return doGet(BTCMarketsExchange.class);
}
}

View file

@ -0,0 +1,45 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.binance.BinanceExchange;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Binance extends ExchangeRateProvider {
public Binance() {
super("BINANCE", "binance", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: EUR, GBP, NGN, RUB, TRY, UAH, ZAR
// Supported alts: BEAM, DAI, DASH, DCR, DOGE, ETC, ETH, LTC, NAV, PIVX, XMR, XZC,
// ZEC, ZEN
return doGet(BinanceExchange.class);
}
}

View file

@ -0,0 +1,44 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.bitbay.BitbayExchange;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Bitbay extends ExchangeRateProvider {
public Bitbay() {
super("BITBAY", "bitbay", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: EUR, GBP, PLN, USD
// Supported alts: DASH, ETH, LTC
return doGet(BitbayExchange.class);
}
}

View file

@ -19,141 +19,38 @@ package bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate; import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider; import bisq.price.spot.ExchangeRateProvider;
import bisq.common.util.Hex;
import org.knowm.xchange.bitcoinaverage.dto.marketdata.BitcoinAverageTicker;
import org.knowm.xchange.bitcoinaverage.dto.marketdata.BitcoinAverageTickers;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.http.RequestEntity;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import com.google.common.base.Charsets;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration; import java.time.Duration;
import java.time.Instant;
import java.util.Map; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
/** /**
* See the BitcoinAverage API documentation at https://apiv2.bitcoinaverage.com/#ticker-data-all * Stub implementation (similar to #CoinMarketCap) for backward compatibility with legacy
* Bisq clients
*/ */
public abstract class BitcoinAverage extends ExchangeRateProvider { @Component
class BitcoinAverage extends ExchangeRateProvider {
/** public BitcoinAverage() {
* Max number of requests allowed per month on the BitcoinAverage developer plan. // Simulate a deactivated BitcoinAverage provider
* Note the actual max value is 45,000; we use the more conservative value below to // We still need the class to exist and be registered as a provider though,
* ensure we do not exceed it. See https://bitcoinaverage.com/en/plans. // because the returned data structure must contain the "btcAverageTs" key
*/ // for backward compatibility with Bisq clients which hardcode that key
private static final double MAX_REQUESTS_PER_MONTH = 42_514; super("BA", "btcAverage", Duration.ofMinutes(100));
private final RestTemplate restTemplate = new RestTemplate();
private final String symbolSet;
private String pubKey;
private Mac mac;
/**
* @param symbolSet "global" or "local"; see https://apiv2.bitcoinaverage.com/#supported-currencies
*/
public BitcoinAverage(String name, String prefix, double pctMaxRequests, String symbolSet, Environment env) {
super(name, prefix, refreshIntervalFor(pctMaxRequests));
this.symbolSet = symbolSet;
this.pubKey = env.getRequiredProperty("BITCOIN_AVG_PUBKEY");
this.mac = initMac(env.getRequiredProperty("BITCOIN_AVG_PRIVKEY"));
} }
/**
* @see CoinMarketCap#doGet()
* @return
*/
@Override @Override
public Set<ExchangeRate> doGet() { public Set<ExchangeRate> doGet() {
return getTickersKeyedByCurrency().entrySet().stream() HashSet<ExchangeRate> exchangeRates = new HashSet<>();
.filter(e -> supportedCurrency(e.getKey())) exchangeRates.add(new ExchangeRate("NON_EXISTING_SYMBOL_BA", 0, 0L, getName()));
.map(e -> return exchangeRates;
new ExchangeRate(
e.getKey(),
e.getValue().getLast(),
e.getValue().getTimestamp(),
this.getName()
)
)
.collect(Collectors.toSet());
}
private boolean supportedCurrency(String currencyCode) {
// ignore Venezuelan bolivars as the "official" exchange rate is just wishful thinking
// we should use this API with a custom provider instead: http://api.bitcoinvenezuela.com/1
return !"VEF".equals(currencyCode);
}
private Map<String, BitcoinAverageTicker> getTickersKeyedByCurrency() {
// go from a map with keys like "BTCUSD", "BTCVEF"
return getTickersKeyedByCurrencyPair().entrySet().stream()
// to a map with keys like "USD", "VEF"
.collect(Collectors.toMap(e -> e.getKey().substring(3), Map.Entry::getValue));
}
private Map<String, BitcoinAverageTicker> getTickersKeyedByCurrencyPair() {
return restTemplate.exchange(
RequestEntity
.get(UriComponentsBuilder
.fromUriString("https://apiv2.bitcoinaverage.com/indices/{symbol-set}/ticker/all?crypto=BTC")
.buildAndExpand(symbolSet)
.toUri())
.header("X-signature", getAuthSignature())
.build(),
BitcoinAverageTickers.class
).getBody().getTickers();
}
protected String getAuthSignature() {
String payload = String.format("%s.%s", Instant.now().getEpochSecond(), pubKey);
return String.format("%s.%s", payload, Hex.encode(mac.doFinal(payload.getBytes(Charsets.UTF_8))));
}
private static Mac initMac(String privKey) {
String algorithm = "HmacSHA256";
SecretKey secretKey = new SecretKeySpec(privKey.getBytes(Charsets.UTF_8), algorithm);
try {
Mac mac = Mac.getInstance(algorithm);
mac.init(secretKey);
return mac;
} catch (NoSuchAlgorithmException | InvalidKeyException ex) {
throw new RuntimeException(ex);
}
}
private static Duration refreshIntervalFor(double pctMaxRequests) {
long requestsPerMonth = (long) (MAX_REQUESTS_PER_MONTH * pctMaxRequests);
return Duration.ofDays(31).dividedBy(requestsPerMonth);
}
@Component
@Order(1)
public static class Global extends BitcoinAverage {
public Global(Environment env) {
super("BTCA_G", "btcAverageG", 0.3, "global", env);
}
}
@Component
@Order(2)
public static class Local extends BitcoinAverage {
public Local(Environment env) {
super("BTCA_L", "btcAverageL", 0.7, "local", env);
}
} }
} }

View file

@ -0,0 +1,44 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.bitfinex.BitfinexExchange;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Bitfinex extends ExchangeRateProvider {
public Bitfinex() {
super("BITFINEX", "bitfinex", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: EUR, GBP, JPY, USD
// Supported alts: DAI, ETC, ETH, LTC, XMR, ZEC
return doGet(BitfinexExchange.class);
}
}

View file

@ -0,0 +1,44 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.bitflyer.BitflyerExchange;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Bitflyer extends ExchangeRateProvider {
public Bitflyer() {
super("BITFLYER", "bitflyer", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: JPY
// Supported alts: ETH
return doGet(BitflyerExchange.class);
}
}

View file

@ -0,0 +1,102 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import bisq.price.util.bitpay.BitpayMarketData;
import bisq.price.util.bitpay.BitpayTicker;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.RequestEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.time.Duration;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Stream;
@Component
class Bitpay extends ExchangeRateProvider {
private final RestTemplate restTemplate = new RestTemplate();
public Bitpay() {
super("BITPAY", "bitpay", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
Set<ExchangeRate> result = new HashSet<ExchangeRate>();
Predicate<BitpayTicker> isDesiredFiatPair = t -> SUPPORTED_FIAT_CURRENCIES.contains(t.getCode());
Predicate<BitpayTicker> isDesiredCryptoPair = t -> SUPPORTED_CRYPTO_CURRENCIES.contains(t.getCode());
getTickers()
.filter(isDesiredFiatPair.or(isDesiredCryptoPair))
.forEach(ticker -> {
boolean useInverseRate = false;
if (SUPPORTED_CRYPTO_CURRENCIES.contains(ticker.getCode())) {
// Use inverse rate for alts, because the API returns the
// conversion rate in the opposite direction than what we need
// API returns the BTC/Alt rate, we need the Alt/BTC rate
useInverseRate = true;
}
BigDecimal rate = ticker.getRate();
// Find the inverse rate, while using enough decimals to reflect very
// small exchange rates
BigDecimal inverseRate = (rate.compareTo(BigDecimal.ZERO) > 0) ?
BigDecimal.ONE.divide(rate, 8, RoundingMode.HALF_UP) :
BigDecimal.ZERO;
result.add(new ExchangeRate(
ticker.getCode(),
(useInverseRate ? inverseRate : rate),
new Date(),
this.getName()
));
});
return result;
}
private Stream<BitpayTicker> getTickers() {
BitpayMarketData marketData = restTemplate.exchange(
RequestEntity
.get(UriComponentsBuilder
.fromUriString("https://bitpay.com/rates").build()
.toUri())
.build(),
new ParameterizedTypeReference<BitpayMarketData>() {
}
).getBody();
return Arrays.stream(marketData.getData());
}
}

View file

@ -0,0 +1,44 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.bitstamp.BitstampExchange;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Bitstamp extends ExchangeRateProvider {
public Bitstamp() {
super("BITSTAMP", "bitstamp", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: EUR, GBP, USD
// Supported alts: ETH, LTC
return doGet(BitstampExchange.class);
}
}

View file

@ -0,0 +1,49 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.cexio.CexIOExchange;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class CexIO extends ExchangeRateProvider {
public CexIO() {
super("CexIO", "cexio", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: EUR, GBP, RUB, USD
// Supported alts: DASH, ETH, LTC
return doGet(CexIOExchange.class);
}
@Override
protected boolean requiresFilterDuringBulkTickerRetrieval() {
return true;
}
}

View file

@ -0,0 +1,104 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import bisq.price.util.coingecko.CoinGeckoMarketData;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.RequestEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.time.Duration;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@Component
class CoinGecko extends ExchangeRateProvider {
private final RestTemplate restTemplate = new RestTemplate();
public CoinGecko() {
super("COINGECKO", "coingecko", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Rate limit for the CoinGecko API is 10 calls each second per IP address
// We retrieve all rates in bulk, so we only make 1 call per provider poll
Set<ExchangeRate> result = new HashSet<ExchangeRate>();
Predicate<Map.Entry> isDesiredFiatPair = t -> SUPPORTED_FIAT_CURRENCIES.contains(t.getKey());
Predicate<Map.Entry> isDesiredCryptoPair = t -> SUPPORTED_CRYPTO_CURRENCIES.contains(t.getKey());
getMarketData().getRates().entrySet().stream()
.filter(isDesiredFiatPair.or(isDesiredCryptoPair))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
.forEach((key, ticker) -> {
boolean useInverseRate = false;
if (SUPPORTED_CRYPTO_CURRENCIES.contains(key)) {
// Use inverse rate for alts, because the API returns the
// conversion rate in the opposite direction than what we need
// API returns the BTC/Alt rate, we need the Alt/BTC rate
useInverseRate = true;
}
BigDecimal rate = ticker.getValue();
// Find the inverse rate, while using enough decimals to reflect very
// small exchange rates
BigDecimal inverseRate = (rate.compareTo(BigDecimal.ZERO) > 0) ?
BigDecimal.ONE.divide(rate, 8, RoundingMode.HALF_UP) :
BigDecimal.ZERO;
result.add(new ExchangeRate(
key,
(useInverseRate ? inverseRate : rate),
new Date(),
this.getName()
));
});
return result;
}
private CoinGeckoMarketData getMarketData() {
return restTemplate.exchange(
RequestEntity
.get(UriComponentsBuilder
.fromUriString("https://api.coingecko.com/api/v3/exchange_rates").build()
.toUri())
.build(),
new ParameterizedTypeReference<CoinGeckoMarketData>() {
}
).getBody();
}
}

View file

@ -20,7 +20,6 @@ package bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate; import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider; import bisq.price.spot.ExchangeRateProvider;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.time.Duration; import java.time.Duration;
@ -32,7 +31,6 @@ import java.util.Set;
* Stub implementation of CoinMarketCap price provider to prevent NullPointerExceptions within legacy clients * Stub implementation of CoinMarketCap price provider to prevent NullPointerExceptions within legacy clients
*/ */
@Component @Component
@Order(3)
class CoinMarketCap extends ExchangeRateProvider { class CoinMarketCap extends ExchangeRateProvider {
public CoinMarketCap() { public CoinMarketCap() {

View file

@ -0,0 +1,45 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.coinmate.CoinmateExchange;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Coinmate extends ExchangeRateProvider {
public Coinmate() {
super("Coinmate", "coinmate", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: CZK, EUR
// Supported alts: DASH, ETH, LTC
return doGet(CoinmateExchange.class);
}
}

View file

@ -0,0 +1,45 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.coinone.CoinoneExchange;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Coinone extends ExchangeRateProvider {
public Coinone() {
super("COINONE", "coinone", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: KRW
// Supported alts: -
return doGet(CoinoneExchange.class);
}
}

View file

@ -0,0 +1,101 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import bisq.price.util.coinpaprika.CoinpaprikaMarketData;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.RequestEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.time.Duration;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@Component
class Coinpaprika extends ExchangeRateProvider {
private final RestTemplate restTemplate = new RestTemplate();
/**
* Used to determine the currencies in which the BTC price can be quoted. There seems
* to be no programatic way to retrieve it, so we get the value from the API
* documentation (see "quotes" param decsribed at
* https://api.coinpaprika.com/#operation/getTickersById ). The hardcoded value below
* is the list of allowed values as per the API documentation, but without BTC and ETH
* as it makes no sense to quote the BTC price in them.
*/
private final static String SUPPORTED_CURRENCIES =
("USD, EUR, PLN, KRW, GBP, CAD, JPY, RUB, TRY, NZD, AUD, CHF, UAH, HKD, " +
"SGD, NGN, PHP, MXN, BRL, THB, CLP, CNY, CZK, DKK, HUF, IDR, ILS," +
"INR, MYR, NOK, PKR, SEK, TWD, ZAR, VND, BOB, COP, PEN, ARS, ISK")
.replace(" ", ""); // Strip any spaces
public Coinpaprika() {
super("COINPAPRIKA", "coinpaprika", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Single IP address can send less than 10 requests per second
// We make only 1 API call per provider poll, so we're not at risk of reaching it
Set<ExchangeRate> result = new HashSet<ExchangeRate>();
Predicate<Map.Entry> isDesiredFiatPair = t -> SUPPORTED_FIAT_CURRENCIES.contains(t.getKey());
getMarketData().getQuotes().entrySet().stream()
.filter(isDesiredFiatPair)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
.forEach((key, ticker) -> {
result.add(new ExchangeRate(
key,
ticker.getPrice(),
new Date(),
this.getName()
));
});
return result;
}
private CoinpaprikaMarketData getMarketData() {
return restTemplate.exchange(
RequestEntity
.get(UriComponentsBuilder
.fromUriString(
"https://api.coinpaprika.com/v1/tickers/btc-bitcoin?quotes=" +
SUPPORTED_CURRENCIES).build()
.toUri())
.build(),
new ParameterizedTypeReference<CoinpaprikaMarketData>() {
}
).getBody();
}
}

View file

@ -0,0 +1,46 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.exmo.ExmoExchange;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Exmo extends ExchangeRateProvider {
public Exmo() {
// API rate limit = 10 calls / second from the same IP ( see https://exmo.com/en/api )
super("EXMO", "exmo", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: EUR, PLN, RUB, UAH (Ukrainian hryvnia), USD
// Supported alts: DASH, DOGE, ETC, ETH, LTC, XMR, ZEC
return doGet(ExmoExchange.class);
}
}

View file

@ -0,0 +1,46 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.hitbtc.v2.HitbtcExchange;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Hitbtc extends ExchangeRateProvider {
public Hitbtc() {
super("HITBTC", "hitbtc", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: USD
// Supported alts: AEON, BTM, DASH, DCR, DOGE, EMC, ETC, ETH, GRIN, LTC, NAV,
// PART, XMR, XRC, XZC, ZEC, ZEN
return doGet(HitbtcExchange.class);
}
}

View file

@ -0,0 +1,45 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.huobi.HuobiExchange;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Huobi extends ExchangeRateProvider {
public Huobi() {
super("HUOBI", "huobi", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: -
// Supported alts: BTM, DASH, DCR, DOGE, ETC, ETH, FAIR, LTC, XMR, XZC, ZEC, ZEN
return doGet(HuobiExchange.class);
}
}

View file

@ -0,0 +1,45 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.independentreserve.IndependentReserveExchange;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class IndependentReserve extends ExchangeRateProvider {
public IndependentReserve() {
super("IndependentReserve", "independentreserve", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: AUD, NZD (New Zealand Dollar), USD
// Supported alts: -
return doGet(IndependentReserveExchange.class);
}
}

View file

@ -0,0 +1,49 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.kraken.KrakenExchange;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Kraken extends ExchangeRateProvider {
public Kraken() {
super("KRAKEN", "kraken", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: AUD, CAD, CHF, EUR, GBP, JPY, USD
// Supported alts: DASH, DOGE, ETC, ETH, LTC, XMR, ZEC
return doGet(KrakenExchange.class);
}
@Override
protected boolean requiresFilterDuringBulkTickerRetrieval() {
return true;
}
}

View file

@ -0,0 +1,52 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.luno.LunoExchange;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Luno extends ExchangeRateProvider {
public Luno() {
super("LUNO", "luno", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: IDR (Indonesian rupiah), MYR (Malaysian ringgit),
// NGN (Nigerian Naira), ZAR (South African rand)
// Supported alts: -
return doGet(LunoExchange.class);
}
@Override
protected long getMarketDataCallDelay() {
// Luno allows only 1 MarketData call per second
// (see https://www.luno.com/en/developers/api )
return 1000;
}
}

View file

@ -0,0 +1,45 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.mercadobitcoin.MercadoBitcoinExchange;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class MercadoBitcoin extends ExchangeRateProvider {
public MercadoBitcoin() {
super("MercadoBitcoin", "mercadobitcoin", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: BRL (Brazilian Real)
// Supported alts: -
return doGet(MercadoBitcoinExchange.class);
}
}

View file

@ -0,0 +1,44 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.paribu.ParibuExchange;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Paribu extends ExchangeRateProvider {
public Paribu() {
super("PARIBU", "paribu", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: TRY (Turkish Lira)
// Supported alts: -
return doGet(ParibuExchange.class);
}
}

View file

@ -19,76 +19,26 @@ package bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate; import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider; import bisq.price.spot.ExchangeRateProvider;
import bisq.price.util.Altcoins;
import org.knowm.xchange.currency.Currency; import org.knowm.xchange.poloniex.PoloniexExchange;
import org.knowm.xchange.currency.CurrencyPair;
import org.knowm.xchange.poloniex.dto.marketdata.PoloniexMarketData;
import org.knowm.xchange.poloniex.dto.marketdata.PoloniexTicker;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.annotation.Order;
import org.springframework.http.RequestEntity;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.time.Duration; import java.time.Duration;
import java.util.Date;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Component @Component
@Order(4)
class Poloniex extends ExchangeRateProvider { class Poloniex extends ExchangeRateProvider {
private final RestTemplate restTemplate = new RestTemplate();
public Poloniex() { public Poloniex() {
super("POLO", "poloniex", Duration.ofMinutes(1)); super("POLO", "poloniex", Duration.ofMinutes(1));
} }
@Override @Override
public Set<ExchangeRate> doGet() { public Set<ExchangeRate> doGet() {
Date timestamp = new Date(); // Poloniex tickers don't include their own timestamp // Supported fiat: -
// Supported alts: DASH, DCR, DOGE, ETC, ETH, LTC, XMR, ZEC
return getTickers() return doGet(PoloniexExchange.class);
.filter(t -> t.getCurrencyPair().base.equals(Currency.BTC))
.filter(t -> Altcoins.ALL_SUPPORTED.contains(t.getCurrencyPair().counter.getCurrencyCode()))
.map(t ->
new ExchangeRate(
t.getCurrencyPair().counter.getCurrencyCode(),
t.getPoloniexMarketData().getLast(),
timestamp,
this.getName()
)
)
.collect(Collectors.toSet());
}
private Stream<PoloniexTicker> getTickers() {
return getTickersKeyedByCurrencyPair().entrySet().stream()
.map(e -> {
String pair = e.getKey();
PoloniexMarketData data = e.getValue();
String[] symbols = pair.split("_"); // e.g. BTC_USD => [BTC, USD]
return new PoloniexTicker(data, new CurrencyPair(symbols[0], symbols[1]));
});
}
private Map<String, PoloniexMarketData> getTickersKeyedByCurrencyPair() {
return restTemplate.exchange(
RequestEntity
.get(UriComponentsBuilder
.fromUriString("https://poloniex.com/public?command=returnTicker").build()
.toUri())
.build(),
new ParameterizedTypeReference<Map<String, PoloniexMarketData>>() {
}
).getBody();
} }
} }

View file

@ -0,0 +1,49 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.quoine.QuoineExchange;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Quoine extends ExchangeRateProvider {
public Quoine() {
super("QUOINE", "quoine", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: AUD, CNY, EUR, HKD, IDR, INR, JPY, PHP, SGD, USD
// Supported alts: ETH
return doGet(QuoineExchange.class);
}
@Override
protected boolean requiresFilterDuringBulkTickerRetrieval() {
return true;
}
}

View file

@ -0,0 +1,13 @@
package bisq.price.util.bitpay;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class BitpayMarketData {
private BitpayTicker[] data;
}

View file

@ -0,0 +1,18 @@
package bisq.price.util.bitpay;
import java.math.BigDecimal;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class BitpayTicker {
private String code;
private String name;
private BigDecimal rate;
}

View file

@ -0,0 +1,22 @@
package bisq.price.util.coingecko;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class CoinGeckoMarketData {
private Map<String, CoinGeckoTicker> rates;
public void setRates(Map<String, CoinGeckoTicker> rates) {
// Convert keys to uppercase ("usd" -> "USD") when deserializing API response
this.rates = rates.entrySet().stream()
.collect(Collectors.toMap(entry -> entry.getKey().toUpperCase(), entry -> entry.getValue()));
}
}

View file

@ -0,0 +1,20 @@
package bisq.price.util.coingecko;
import java.math.BigDecimal;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class CoinGeckoTicker {
private String name;
private String unit;
private BigDecimal value;
private String type;
}

View file

@ -0,0 +1,17 @@
package bisq.price.util.coinpaprika;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class CoinpaprikaMarketData {
// All other json fields can be ignored, we don't need them
private Map<String, CoinpaprikaTicker> quotes;
}

View file

@ -0,0 +1,16 @@
package bisq.price.util.coinpaprika;
import java.math.BigDecimal;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class CoinpaprikaTicker {
// All other json fields can be ignored, we don't need them
private BigDecimal price;
}

View file

@ -4,6 +4,6 @@ spring.jackson.serialization.indent_output=true
# and set it to hostname exposing the fee estimation API # and set it to hostname exposing the fee estimation API
bisq.price.mining.providers.mempoolHostname.1=mempool.space bisq.price.mining.providers.mempoolHostname.1=mempool.space
bisq.price.mining.providers.mempoolHostname.2=mempool.emzy.de bisq.price.mining.providers.mempoolHostname.2=mempool.emzy.de
# bisq.price.mining.providers.mempoolHostname.3=someHostOrIP bisq.price.mining.providers.mempoolHostname.3=mempool.ninja
# bisq.price.mining.providers.mempoolHostname.4=someHostOrIP # bisq.price.mining.providers.mempoolHostname.4=someHostOrIP
# bisq.price.mining.providers.mempoolHostname.5=someHostOrIP # bisq.price.mining.providers.mempoolHostname.5=someHostOrIP

View file

@ -0,0 +1,66 @@
package bisq.price;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import com.google.common.collect.Sets;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import static org.junit.Assert.assertTrue;
@Slf4j
public abstract class AbstractExchangeRateProviderTest {
protected void doGet_successfulCall(ExchangeRateProvider exchangeProvider) {
// Use the XChange library to call the provider API, in order to retrieve the
// exchange rates. If the API call fails, or the response body cannot be parsed,
// the test will fail with an exception
Set<ExchangeRate> retrievedExchangeRates = exchangeProvider.doGet();
// Log the valid exchange rates which were retrieved
// Useful when running the tests, to easily identify which exchanges provide
// useful pairs
retrievedExchangeRates.forEach(e -> log.info("Found exchange rate " + e.toString()));
// Sanity checks
assertTrue(retrievedExchangeRates.size() > 0);
checkProviderCurrencyPairs(retrievedExchangeRates);
}
/**
* Check that every retrieved currency pair is between BTC and either
* A) a fiat currency on the list of Bisq-supported fiat currencies, or
* B) an altcoin on the list of Bisq-supported altcoins
*
* @param retrievedExchangeRates Exchange rates retrieved from the provider
*/
private void checkProviderCurrencyPairs(Set<ExchangeRate> retrievedExchangeRates) {
Set<String> retrievedRatesCurrencies = retrievedExchangeRates.stream()
.map(ExchangeRate::getCurrency)
.collect(Collectors.toSet());
Set<String> supportedFiatCurrenciesRetrieved = ExchangeRateProvider.SUPPORTED_FIAT_CURRENCIES.stream()
.filter(f -> retrievedRatesCurrencies.contains(f))
.collect(Collectors.toCollection(TreeSet::new));
log.info("Retrieved rates for supported fiat currencies: " + supportedFiatCurrenciesRetrieved);
Set<String> supportedCryptoCurrenciesRetrieved = ExchangeRateProvider.SUPPORTED_CRYPTO_CURRENCIES.stream()
.filter(c -> retrievedRatesCurrencies.contains(c))
.collect(Collectors.toCollection(TreeSet::new));
log.info("Retrieved rates for supported altcoins: " + supportedCryptoCurrenciesRetrieved);
Set<String> supportedCurrencies = Sets.union(
ExchangeRateProvider.SUPPORTED_CRYPTO_CURRENCIES,
ExchangeRateProvider.SUPPORTED_FIAT_CURRENCIES);
Set unsupportedCurrencies = Sets.difference(retrievedRatesCurrencies, supportedCurrencies);
assertTrue("Retrieved exchange rates contain unsupported currencies: " + unsupportedCurrencies,
unsupportedCurrencies.isEmpty());
}
}

View file

@ -17,15 +17,22 @@
package bisq.price.spot; package bisq.price.spot;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.OptionalDouble;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -74,8 +81,7 @@ public class ExchangeRateServiceTest {
Map<String, Object> retrievedData = service.getAllMarketPrices(); Map<String, Object> retrievedData = service.getAllMarketPrices();
doSanityChecksForRetrievedDataSingleProvider( doSanityChecksForRetrievedDataSingleProvider(retrievedData, dummyProvider, numberOfCurrencyPairsOnExchange);
retrievedData, dummyProvider.getPrefix(), numberOfCurrencyPairsOnExchange);
// No exchange rates provided by this exchange, two things should happen // No exchange rates provided by this exchange, two things should happen
// A) the timestamp should be set to 0 // A) the timestamp should be set to 0
@ -101,15 +107,14 @@ public class ExchangeRateServiceTest {
Map<String, Object> retrievedData = service.getAllMarketPrices(); Map<String, Object> retrievedData = service.getAllMarketPrices();
doSanityChecksForRetrievedDataSingleProvider( doSanityChecksForRetrievedDataSingleProvider(retrievedData, dummyProvider, numberOfCurrencyPairsOnExchange);
retrievedData, dummyProvider.getPrefix(), numberOfCurrencyPairsOnExchange);
// One rate was provided by this provider, so the timestamp should not be 0 // One rate was provided by this provider, so the timestamp should not be 0
assertNotEquals(0L, retrievedData.get(dummyProvider.getPrefix() + "Ts")); assertNotEquals(0L, retrievedData.get(dummyProvider.getPrefix() + "Ts"));
} }
@Test @Test
public void getAllMarketPrices_withMultipleProviders() { public void getAllMarketPrices_withMultipleProviders_differentCurrencyCodes() {
int numberOfCurrencyPairsOnExchange = 1; int numberOfCurrencyPairsOnExchange = 1;
ExchangeRateProvider dummyProvider1 = buildDummyExchangeRateProvider(numberOfCurrencyPairsOnExchange); ExchangeRateProvider dummyProvider1 = buildDummyExchangeRateProvider(numberOfCurrencyPairsOnExchange);
ExchangeRateProvider dummyProvider2 = buildDummyExchangeRateProvider(numberOfCurrencyPairsOnExchange); ExchangeRateProvider dummyProvider2 = buildDummyExchangeRateProvider(numberOfCurrencyPairsOnExchange);
@ -117,8 +122,7 @@ public class ExchangeRateServiceTest {
Map<String, Object> retrievedData = service.getAllMarketPrices(); Map<String, Object> retrievedData = service.getAllMarketPrices();
doSanityChecksForRetrievedDataMultipleProviders(retrievedData, doSanityChecksForRetrievedDataMultipleProviders(retrievedData, asList(dummyProvider1, dummyProvider2));
asList(dummyProvider1.getPrefix(), dummyProvider2.getPrefix()));
// One rate was provided by each provider in this service, so the timestamp // One rate was provided by each provider in this service, so the timestamp
// (for both providers) should not be 0 // (for both providers) should not be 0
@ -126,21 +130,49 @@ public class ExchangeRateServiceTest {
assertNotEquals(0L, retrievedData.get(dummyProvider2.getPrefix() + "Ts")); assertNotEquals(0L, retrievedData.get(dummyProvider2.getPrefix() + "Ts"));
} }
/**
* Tests the scenario when multiple providers have rates for the same currencies
*/
@Test
public void getAllMarketPrices_withMultipleProviders_overlappingCurrencyCodes() {
// List of currencies for which multiple providers will have exchange rates
Set<String> rateCurrencyCodes = Sets.newHashSet("CURRENCY-1", "CURRENCY-2", "CURRENCY-3");
// Create several dummy providers, each providing their own rates for the same set of currencies
ExchangeRateProvider dummyProvider1 = buildDummyExchangeRateProvider(rateCurrencyCodes);
ExchangeRateProvider dummyProvider2 = buildDummyExchangeRateProvider(rateCurrencyCodes);
ExchangeRateService service = new ExchangeRateService(asList(dummyProvider1, dummyProvider2));
Map<String, Object> retrievedData = service.getAllMarketPrices();
doSanityChecksForRetrievedDataMultipleProviders(retrievedData, asList(dummyProvider1, dummyProvider2));
// At least one rate was provided by each provider in this service, so the
// timestamp (for both providers) should not be 0
assertNotEquals(0L, retrievedData.get(dummyProvider1.getPrefix() + "Ts"));
assertNotEquals(0L, retrievedData.get(dummyProvider2.getPrefix() + "Ts"));
}
/** /**
* Performs generic sanity checks on the response format and contents. * Performs generic sanity checks on the response format and contents.
* *
* @param retrievedData Response data retrieved from the {@link ExchangeRateService} * @param retrievedData Response data retrieved from the {@link ExchangeRateService}
* @param providerPrefix {@link ExchangeRateProvider#getPrefix()} * @param provider {@link ExchangeRateProvider} available to the
* @param numberOfCurrencyPairsOnExchange Number of currency pairs this exchange was initiated with * {@link ExchangeRateService}
* @param numberOfCurrencyPairsOnExchange Number of currency pairs this exchange was
* initiated with
*/ */
private void doSanityChecksForRetrievedDataSingleProvider(Map<String, Object> retrievedData, private void doSanityChecksForRetrievedDataSingleProvider(Map<String, Object> retrievedData,
String providerPrefix, ExchangeRateProvider provider,
int numberOfCurrencyPairsOnExchange) { int numberOfCurrencyPairsOnExchange) {
// Check response structure // Check response structure
doSanityChecksForRetrievedDataMultipleProviders(retrievedData, asList(providerPrefix)); doSanityChecksForRetrievedDataMultipleProviders(retrievedData, asList(provider));
// Check that the amount of provided exchange rates matches expected value // Check that the amount of provided exchange rates matches expected value
// For one provider, the amount of rates of that provider should be the total amount of rates in the response // For one provider, the amount of rates of that provider should be the total
// amount of rates in the response
List<String> retrievedMarketPricesData = (List<String>) retrievedData.get("data"); List<String> retrievedMarketPricesData = (List<String>) retrievedData.get("data");
assertEquals(numberOfCurrencyPairsOnExchange, retrievedMarketPricesData.size()); assertEquals(numberOfCurrencyPairsOnExchange, retrievedMarketPricesData.size());
} }
@ -149,26 +181,81 @@ public class ExchangeRateServiceTest {
* Performs generic sanity checks on the response format and contents. * Performs generic sanity checks on the response format and contents.
* *
* @param retrievedData Response data retrieved from the {@link ExchangeRateService} * @param retrievedData Response data retrieved from the {@link ExchangeRateService}
* @param providerPrefixes List of all {@link ExchangeRateProvider#getPrefix()} the * @param providers List of all {@link ExchangeRateProvider#getPrefix()} the
* {@link ExchangeRateService} uses * {@link ExchangeRateService} uses
*/ */
private void doSanityChecksForRetrievedDataMultipleProviders(Map<String, Object> retrievedData, private void doSanityChecksForRetrievedDataMultipleProviders(Map<String, Object> retrievedData,
List<String> providerPrefixes) { List<ExchangeRateProvider> providers) {
// Check the correct amount of entries were present in the service response: // Check the correct amount of entries were present in the service response:
// The timestamp and the count fields are per provider, so N providers means N // The timestamp and the count fields are per provider, so N providers means N
// times those fields timestamp (x N) + count (x N) + price data (stored as a list // times those fields timestamp (x N) + count (x N) + price data (stored as a list
// under the key "data"). So expected size is Nx2 + 1. // under the key "data"). So expected size is Nx2 + 1.
int n = providerPrefixes.size(); int n = providers.size();
assertEquals(n * 2 + 1, retrievedData.size()); assertEquals(n * 2 + 1, retrievedData.size());
for (String providerPrefix : providerPrefixes) { for (ExchangeRateProvider provider : providers) {
String providerPrefix = provider.getPrefix();
assertNotNull(retrievedData.get(providerPrefix + "Ts")); assertNotNull(retrievedData.get(providerPrefix + "Ts"));
assertNotNull(retrievedData.get(providerPrefix + "Count")); assertNotNull(retrievedData.get(providerPrefix + "Count"));
} }
assertNotNull(retrievedData.get("data"));
// TODO Add checks for the case when rates for the same currency pair is retrieved from multiple providers // Check validity of the data field
List<ExchangeRate> retrievedRates = (List<ExchangeRate>) retrievedData.get("data");
assertNotNull(retrievedRates);
// It should contain no duplicate ExchangeRate objects
int uniqueRates = Sets.newHashSet(retrievedRates).size();
int totalRates = retrievedRates.size();
assertEquals(uniqueRates, totalRates, "Found duplicate rates in data field");
// There should be only one ExchangeRate per currency
// In other words, even if multiple providers return rates for the same currency,
// the ExchangeRateService should expose only one (aggregate) ExchangeRate for
// that currency
Map<String, ExchangeRate> currencyCodeToExchangeRateFromService = retrievedRates.stream()
.collect(Collectors.toMap(
ExchangeRate::getCurrency, exchangeRate -> exchangeRate
));
int uniqueCurrencyCodes = currencyCodeToExchangeRateFromService.keySet().size();
assertEquals(uniqueCurrencyCodes, uniqueRates, "Found currency code with multiple exchange rates");
// Collect all ExchangeRates from all providers and group them by currency code
Map<String, List<ExchangeRate>> currencyCodeToExchangeRatesFromProviders = new HashMap<>();
for (ExchangeRateProvider p : providers) {
for (ExchangeRate exchangeRate : p.get()) {
String currencyCode = exchangeRate.getCurrency();
if (currencyCodeToExchangeRatesFromProviders.containsKey(currencyCode)) {
List<ExchangeRate> l = new ArrayList<>(currencyCodeToExchangeRatesFromProviders.get(currencyCode));
l.add(exchangeRate);
currencyCodeToExchangeRatesFromProviders.put(currencyCode, l);
} else {
currencyCodeToExchangeRatesFromProviders.put(currencyCode, asList(exchangeRate));
}
}
} }
// For each ExchangeRate which is covered by multiple providers, ensure the rate
// value is an average
currencyCodeToExchangeRatesFromProviders.forEach((currencyCode, exchangeRateList) -> {
ExchangeRate rateFromService = currencyCodeToExchangeRateFromService.get(currencyCode);
double priceFromService = rateFromService.getPrice();
OptionalDouble opt = exchangeRateList.stream().mapToDouble(ExchangeRate::getPrice).average();
double priceAvgFromProviders = opt.getAsDouble();
// Ensure that the ExchangeRateService correctly aggregates exchange rates
// from multiple providers. If multiple providers contain rates for a
// currency, the service should return a single aggregate rate
// Expected value for aggregate rate = avg(provider rates)
// This formula works for any number of providers for a specific currency
assertEquals(priceFromService, priceAvgFromProviders, "Service returned incorrect aggregate rate");
});
}
/**
* @param numberOfRatesAvailable Number of exchange rates this provider returns
* @return Dummy {@link ExchangeRateProvider} providing rates for
* "numberOfRatesAvailable" random currency codes
*/
private ExchangeRateProvider buildDummyExchangeRateProvider(int numberOfRatesAvailable) { private ExchangeRateProvider buildDummyExchangeRateProvider(int numberOfRatesAvailable) {
ExchangeRateProvider dummyProvider = new ExchangeRateProvider( ExchangeRateProvider dummyProvider = new ExchangeRateProvider(
"ExchangeName-" + getRandomAlphaNumericString(5), "ExchangeName-" + getRandomAlphaNumericString(5),
@ -187,8 +274,44 @@ public class ExchangeRateServiceTest {
// Simulate the required amount of rates // Simulate the required amount of rates
for (int i = 0; i < numberOfRatesAvailable; i++) { for (int i = 0; i < numberOfRatesAvailable; i++) {
exchangeRates.add(new ExchangeRate( exchangeRates.add(new ExchangeRate(
"DUM-" + getRandomAlphaNumericString(3), // random symbol, avoid duplicates // random symbol, avoid duplicates
0, "DUM-" + getRandomAlphaNumericString(3),
RandomUtils.nextDouble(1, 1000), // random price
System.currentTimeMillis(),
getName())); // ExchangeRateProvider name
}
return exchangeRates;
}
};
// Initialize provider
dummyProvider.start();
dummyProvider.stop();
return dummyProvider;
}
private ExchangeRateProvider buildDummyExchangeRateProvider(Set<String> rateCurrencyCodes) {
ExchangeRateProvider dummyProvider = new ExchangeRateProvider(
"ExchangeName-" + getRandomAlphaNumericString(5),
"EXCH-" + getRandomAlphaNumericString(3),
Duration.ofDays(1)) {
@Override
public boolean isRunning() {
return true;
}
@Override
protected Set<ExchangeRate> doGet() {
HashSet<ExchangeRate> exchangeRates = new HashSet<>();
// Simulate the required amount of rates
for (String rateCurrencyCode : rateCurrencyCodes) {
exchangeRates.add(new ExchangeRate(
rateCurrencyCode,
RandomUtils.nextDouble(1, 1000), // random price
System.currentTimeMillis(), System.currentTimeMillis(),
getName())); // ExchangeRateProvider name getName())); // ExchangeRateProvider name
} }

View file

@ -0,0 +1,34 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.AbstractExchangeRateProviderTest;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
@Slf4j
public class BTCMarketsTest extends AbstractExchangeRateProviderTest {
@Test
public void doGet_successfulCall() {
doGet_successfulCall(new BTCMarkets());
}
}

View file

@ -0,0 +1,34 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.AbstractExchangeRateProviderTest;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
@Slf4j
public class BinanceTest extends AbstractExchangeRateProviderTest {
@Test
public void doGet_successfulCall() {
doGet_successfulCall(new Binance());
}
}

View file

@ -0,0 +1,34 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.AbstractExchangeRateProviderTest;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
@Slf4j
public class BitbayTest extends AbstractExchangeRateProviderTest {
@Test
public void doGet_successfulCall() {
doGet_successfulCall(new Bitbay());
}
}

View file

@ -0,0 +1,34 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.AbstractExchangeRateProviderTest;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
@Slf4j
public class BitfinexTest extends AbstractExchangeRateProviderTest {
@Test
public void doGet_successfulCall() {
doGet_successfulCall(new Bitfinex());
}
}

View file

@ -0,0 +1,34 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.AbstractExchangeRateProviderTest;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
@Slf4j
public class BitflyerTest extends AbstractExchangeRateProviderTest {
@Test
public void doGet_successfulCall() {
doGet_successfulCall(new Bitflyer());
}
}

View file

@ -0,0 +1,34 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.AbstractExchangeRateProviderTest;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
@Slf4j
public class BitpayTest extends AbstractExchangeRateProviderTest {
@Test
public void doGet_successfulCall() {
doGet_successfulCall(new Bitpay());
}
}

View file

@ -0,0 +1,34 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.AbstractExchangeRateProviderTest;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
@Slf4j
public class BitstampTest extends AbstractExchangeRateProviderTest {
@Test
public void doGet_successfulCall() {
doGet_successfulCall(new Bitstamp());
}
}

View file

@ -0,0 +1,34 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.AbstractExchangeRateProviderTest;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
@Slf4j
public class CexIOTest extends AbstractExchangeRateProviderTest {
@Test
public void doGet_successfulCall() {
doGet_successfulCall(new CexIO());
}
}

View file

@ -0,0 +1,34 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.AbstractExchangeRateProviderTest;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
@Slf4j
public class CoinGeckoTest extends AbstractExchangeRateProviderTest {
@Test
public void doGet_successfulCall() {
doGet_successfulCall(new CoinGecko());
}
}

View file

@ -0,0 +1,34 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.AbstractExchangeRateProviderTest;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
@Slf4j
public class CoinmateTest extends AbstractExchangeRateProviderTest {
@Test
public void doGet_successfulCall() {
doGet_successfulCall(new Coinmate());
}
}

View file

@ -0,0 +1,34 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.AbstractExchangeRateProviderTest;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
@Slf4j
public class CoinoneTest extends AbstractExchangeRateProviderTest {
@Test
public void doGet_successfulCall() {
doGet_successfulCall(new Coinone());
}
}

Some files were not shown because too many files have changed in this diff Show more