mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-12-25 07:19:30 -05:00
support buying xmr without deposit or fee using passphrase
This commit is contained in:
parent
ece3b0fec0
commit
775fbc41c2
@ -43,7 +43,7 @@ import java.util.stream.Collectors;
|
|||||||
import static haveno.apitest.config.ApiTestConfig.BTC;
|
import static haveno.apitest.config.ApiTestConfig.BTC;
|
||||||
import static haveno.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateMeterInterceptorConfig;
|
import static haveno.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateMeterInterceptorConfig;
|
||||||
import static haveno.cli.table.builder.TableType.BTC_BALANCE_TBL;
|
import static haveno.cli.table.builder.TableType.BTC_BALANCE_TBL;
|
||||||
import static haveno.core.xmr.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
import static haveno.core.xmr.wallet.Restrictions.getDefaultSecurityDepositAsPercent;
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static java.util.Arrays.stream;
|
import static java.util.Arrays.stream;
|
||||||
@ -157,8 +157,8 @@ public class MethodTest extends ApiTestCase {
|
|||||||
return haveno.core.payment.PaymentAccount.fromProto(paymentAccount, CORE_PROTO_RESOLVER);
|
return haveno.core.payment.PaymentAccount.fromProto(paymentAccount, CORE_PROTO_RESOLVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Supplier<Double> defaultBuyerSecurityDepositPct = () -> {
|
public static final Supplier<Double> defaultSecurityDepositPct = () -> {
|
||||||
var defaultPct = BigDecimal.valueOf(getDefaultBuyerSecurityDepositAsPercent());
|
var defaultPct = BigDecimal.valueOf(getDefaultSecurityDepositAsPercent());
|
||||||
if (defaultPct.precision() != 2)
|
if (defaultPct.precision() != 2)
|
||||||
throw new IllegalStateException(format(
|
throw new IllegalStateException(format(
|
||||||
"Unexpected decimal precision, expected 2 but actual is %d%n."
|
"Unexpected decimal precision, expected 2 but actual is %d%n."
|
||||||
|
@ -47,7 +47,7 @@ public class CancelOfferTest extends AbstractOfferTest {
|
|||||||
10000000L,
|
10000000L,
|
||||||
10000000L,
|
10000000L,
|
||||||
0.00,
|
0.00,
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
paymentAccountId,
|
paymentAccountId,
|
||||||
NO_TRIGGER_PRICE);
|
NO_TRIGGER_PRICE);
|
||||||
};
|
};
|
||||||
|
@ -49,7 +49,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
|||||||
10_000_000L,
|
10_000_000L,
|
||||||
10_000_000L,
|
10_000_000L,
|
||||||
"36000",
|
"36000",
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
audAccount.getId());
|
audAccount.getId());
|
||||||
log.debug("Offer #1:\n{}", toOfferTable.apply(newOffer));
|
log.debug("Offer #1:\n{}", toOfferTable.apply(newOffer));
|
||||||
assertTrue(newOffer.getIsMyOffer());
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
@ -97,7 +97,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
|||||||
10_000_000L,
|
10_000_000L,
|
||||||
10_000_000L,
|
10_000_000L,
|
||||||
"30000.1234",
|
"30000.1234",
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
usdAccount.getId());
|
usdAccount.getId());
|
||||||
log.debug("Offer #2:\n{}", toOfferTable.apply(newOffer));
|
log.debug("Offer #2:\n{}", toOfferTable.apply(newOffer));
|
||||||
assertTrue(newOffer.getIsMyOffer());
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
@ -145,7 +145,7 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
|||||||
10_000_000L,
|
10_000_000L,
|
||||||
5_000_000L,
|
5_000_000L,
|
||||||
"29500.1234",
|
"29500.1234",
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
eurAccount.getId());
|
eurAccount.getId());
|
||||||
log.debug("Offer #3:\n{}", toOfferTable.apply(newOffer));
|
log.debug("Offer #3:\n{}", toOfferTable.apply(newOffer));
|
||||||
assertTrue(newOffer.getIsMyOffer());
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
@ -66,7 +66,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
|||||||
10_000_000L,
|
10_000_000L,
|
||||||
10_000_000L,
|
10_000_000L,
|
||||||
priceMarginPctInput,
|
priceMarginPctInput,
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
usdAccount.getId(),
|
usdAccount.getId(),
|
||||||
NO_TRIGGER_PRICE);
|
NO_TRIGGER_PRICE);
|
||||||
log.debug("Offer #1:\n{}", toOfferTable.apply(newOffer));
|
log.debug("Offer #1:\n{}", toOfferTable.apply(newOffer));
|
||||||
@ -114,7 +114,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
|||||||
10_000_000L,
|
10_000_000L,
|
||||||
10_000_000L,
|
10_000_000L,
|
||||||
priceMarginPctInput,
|
priceMarginPctInput,
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
nzdAccount.getId(),
|
nzdAccount.getId(),
|
||||||
NO_TRIGGER_PRICE);
|
NO_TRIGGER_PRICE);
|
||||||
log.debug("Offer #2:\n{}", toOfferTable.apply(newOffer));
|
log.debug("Offer #2:\n{}", toOfferTable.apply(newOffer));
|
||||||
@ -162,7 +162,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
|||||||
10_000_000L,
|
10_000_000L,
|
||||||
5_000_000L,
|
5_000_000L,
|
||||||
priceMarginPctInput,
|
priceMarginPctInput,
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
gbpAccount.getId(),
|
gbpAccount.getId(),
|
||||||
NO_TRIGGER_PRICE);
|
NO_TRIGGER_PRICE);
|
||||||
log.debug("Offer #3:\n{}", toOfferTable.apply(newOffer));
|
log.debug("Offer #3:\n{}", toOfferTable.apply(newOffer));
|
||||||
@ -210,7 +210,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
|||||||
10_000_000L,
|
10_000_000L,
|
||||||
5_000_000L,
|
5_000_000L,
|
||||||
priceMarginPctInput,
|
priceMarginPctInput,
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
brlAccount.getId(),
|
brlAccount.getId(),
|
||||||
NO_TRIGGER_PRICE);
|
NO_TRIGGER_PRICE);
|
||||||
log.debug("Offer #4:\n{}", toOfferTable.apply(newOffer));
|
log.debug("Offer #4:\n{}", toOfferTable.apply(newOffer));
|
||||||
@ -259,7 +259,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
|||||||
10_000_000L,
|
10_000_000L,
|
||||||
5_000_000L,
|
5_000_000L,
|
||||||
0.0,
|
0.0,
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
usdAccount.getId(),
|
usdAccount.getId(),
|
||||||
triggerPrice);
|
triggerPrice);
|
||||||
assertTrue(newOffer.getIsMyOffer());
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
@ -62,7 +62,7 @@ public class CreateXMROffersTest extends AbstractOfferTest {
|
|||||||
100_000_000L,
|
100_000_000L,
|
||||||
75_000_000L,
|
75_000_000L,
|
||||||
"0.005", // FIXED PRICE IN BTC FOR 1 XMR
|
"0.005", // FIXED PRICE IN BTC FOR 1 XMR
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
alicesXmrAcct.getId());
|
alicesXmrAcct.getId());
|
||||||
log.debug("Sell XMR (Buy BTC) offer:\n{}", toOfferTable.apply(newOffer));
|
log.debug("Sell XMR (Buy BTC) offer:\n{}", toOfferTable.apply(newOffer));
|
||||||
assertTrue(newOffer.getIsMyOffer());
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
@ -108,7 +108,7 @@ public class CreateXMROffersTest extends AbstractOfferTest {
|
|||||||
100_000_000L,
|
100_000_000L,
|
||||||
50_000_000L,
|
50_000_000L,
|
||||||
"0.005", // FIXED PRICE IN BTC (satoshis) FOR 1 XMR
|
"0.005", // FIXED PRICE IN BTC (satoshis) FOR 1 XMR
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
alicesXmrAcct.getId());
|
alicesXmrAcct.getId());
|
||||||
log.debug("Buy XMR (Sell BTC) offer:\n{}", toOfferTable.apply(newOffer));
|
log.debug("Buy XMR (Sell BTC) offer:\n{}", toOfferTable.apply(newOffer));
|
||||||
assertTrue(newOffer.getIsMyOffer());
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
@ -156,7 +156,7 @@ public class CreateXMROffersTest extends AbstractOfferTest {
|
|||||||
100_000_000L,
|
100_000_000L,
|
||||||
75_000_000L,
|
75_000_000L,
|
||||||
priceMarginPctInput,
|
priceMarginPctInput,
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
alicesXmrAcct.getId(),
|
alicesXmrAcct.getId(),
|
||||||
triggerPrice);
|
triggerPrice);
|
||||||
log.debug("Pending Sell XMR (Buy BTC) offer:\n{}", toOfferTable.apply(newOffer));
|
log.debug("Pending Sell XMR (Buy BTC) offer:\n{}", toOfferTable.apply(newOffer));
|
||||||
@ -211,7 +211,7 @@ public class CreateXMROffersTest extends AbstractOfferTest {
|
|||||||
100_000_000L,
|
100_000_000L,
|
||||||
50_000_000L,
|
50_000_000L,
|
||||||
priceMarginPctInput,
|
priceMarginPctInput,
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
alicesXmrAcct.getId(),
|
alicesXmrAcct.getId(),
|
||||||
NO_TRIGGER_PRICE);
|
NO_TRIGGER_PRICE);
|
||||||
log.debug("Buy XMR (Sell BTC) offer:\n{}", toOfferTable.apply(newOffer));
|
log.debug("Buy XMR (Sell BTC) offer:\n{}", toOfferTable.apply(newOffer));
|
||||||
|
@ -47,7 +47,7 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
|
|||||||
100000000000L, // exceeds amount limit
|
100000000000L, // exceeds amount limit
|
||||||
100000000000L,
|
100000000000L,
|
||||||
"10000.0000",
|
"10000.0000",
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
usdAccount.getId()));
|
usdAccount.getId()));
|
||||||
assertEquals("UNKNOWN: An error occurred at task: ValidateOffer", exception.getMessage());
|
assertEquals("UNKNOWN: An error occurred at task: ValidateOffer", exception.getMessage());
|
||||||
}
|
}
|
||||||
@ -63,7 +63,7 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
|
|||||||
10000000L,
|
10000000L,
|
||||||
10000000L,
|
10000000L,
|
||||||
"40000.0000",
|
"40000.0000",
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
chfAccount.getId()));
|
chfAccount.getId()));
|
||||||
String expectedError = format("UNKNOWN: cannot create EUR offer with payment account %s", chfAccount.getId());
|
String expectedError = format("UNKNOWN: cannot create EUR offer with payment account %s", chfAccount.getId());
|
||||||
assertEquals(expectedError, exception.getMessage());
|
assertEquals(expectedError, exception.getMessage());
|
||||||
@ -80,7 +80,7 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
|
|||||||
10000000L,
|
10000000L,
|
||||||
10000000L,
|
10000000L,
|
||||||
"63000.0000",
|
"63000.0000",
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
audAccount.getId()));
|
audAccount.getId()));
|
||||||
String expectedError = format("UNKNOWN: cannot create CAD offer with payment account %s", audAccount.getId());
|
String expectedError = format("UNKNOWN: cannot create CAD offer with payment account %s", audAccount.getId());
|
||||||
assertEquals(expectedError, exception.getMessage());
|
assertEquals(expectedError, exception.getMessage());
|
||||||
|
@ -52,7 +52,7 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
|||||||
12_500_000L,
|
12_500_000L,
|
||||||
12_500_000L, // min-amount = amount
|
12_500_000L, // min-amount = amount
|
||||||
0.00,
|
0.00,
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
alicesUsdAccount.getId(),
|
alicesUsdAccount.getId(),
|
||||||
NO_TRIGGER_PRICE);
|
NO_TRIGGER_PRICE);
|
||||||
var offerId = alicesOffer.getId();
|
var offerId = alicesOffer.getId();
|
||||||
|
@ -96,7 +96,7 @@ public class TakeBuyBTCOfferWithNationalBankAcctTest extends AbstractTradeTest {
|
|||||||
1_000_000L,
|
1_000_000L,
|
||||||
1_000_000L, // min-amount = amount
|
1_000_000L, // min-amount = amount
|
||||||
0.00,
|
0.00,
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
alicesPaymentAccount.getId(),
|
alicesPaymentAccount.getId(),
|
||||||
NO_TRIGGER_PRICE);
|
NO_TRIGGER_PRICE);
|
||||||
var offerId = alicesOffer.getId();
|
var offerId = alicesOffer.getId();
|
||||||
|
@ -65,7 +65,7 @@ public class TakeBuyXMROfferTest extends AbstractTradeTest {
|
|||||||
15_000_000L,
|
15_000_000L,
|
||||||
7_500_000L,
|
7_500_000L,
|
||||||
"0.00455500", // FIXED PRICE IN BTC (satoshis) FOR 1 XMR
|
"0.00455500", // FIXED PRICE IN BTC (satoshis) FOR 1 XMR
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
alicesXmrAcct.getId());
|
alicesXmrAcct.getId());
|
||||||
log.debug("Alice's BUY XMR (SELL BTC) Offer:\n{}", new TableBuilder(OFFER_TBL, alicesOffer).build());
|
log.debug("Alice's BUY XMR (SELL BTC) Offer:\n{}", new TableBuilder(OFFER_TBL, alicesOffer).build());
|
||||||
genBtcBlocksThenWait(1, 5000);
|
genBtcBlocksThenWait(1, 5000);
|
||||||
|
@ -58,7 +58,7 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
|||||||
12_500_000L,
|
12_500_000L,
|
||||||
12_500_000L, // min-amount = amount
|
12_500_000L, // min-amount = amount
|
||||||
0.00,
|
0.00,
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
alicesUsdAccount.getId(),
|
alicesUsdAccount.getId(),
|
||||||
NO_TRIGGER_PRICE);
|
NO_TRIGGER_PRICE);
|
||||||
var offerId = alicesOffer.getId();
|
var offerId = alicesOffer.getId();
|
||||||
|
@ -71,7 +71,7 @@ public class TakeSellXMROfferTest extends AbstractTradeTest {
|
|||||||
20_000_000L,
|
20_000_000L,
|
||||||
10_500_000L,
|
10_500_000L,
|
||||||
priceMarginPctInput,
|
priceMarginPctInput,
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
alicesXmrAcct.getId(),
|
alicesXmrAcct.getId(),
|
||||||
NO_TRIGGER_PRICE);
|
NO_TRIGGER_PRICE);
|
||||||
log.debug("Alice's SELL XMR (BUY BTC) Offer:\n{}", new TableBuilder(OFFER_TBL, alicesOffer).build());
|
log.debug("Alice's SELL XMR (BUY BTC) Offer:\n{}", new TableBuilder(OFFER_TBL, alicesOffer).build());
|
||||||
|
@ -57,7 +57,7 @@ public class LongRunningOfferDeactivationTest extends AbstractOfferTest {
|
|||||||
1_000_000,
|
1_000_000,
|
||||||
1_000_000,
|
1_000_000,
|
||||||
0.00,
|
0.00,
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
paymentAcct.getId(),
|
paymentAcct.getId(),
|
||||||
triggerPrice);
|
triggerPrice);
|
||||||
log.info("SELL offer {} created with margin based price {}.",
|
log.info("SELL offer {} created with margin based price {}.",
|
||||||
@ -103,7 +103,7 @@ public class LongRunningOfferDeactivationTest extends AbstractOfferTest {
|
|||||||
1_000_000,
|
1_000_000,
|
||||||
1_000_000,
|
1_000_000,
|
||||||
0.00,
|
0.00,
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
paymentAcct.getId(),
|
paymentAcct.getId(),
|
||||||
triggerPrice);
|
triggerPrice);
|
||||||
log.info("BUY offer {} created with margin based price {}.",
|
log.info("BUY offer {} created with margin based price {}.",
|
||||||
|
@ -28,7 +28,7 @@ import java.text.DecimalFormat;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import static haveno.apitest.method.offer.AbstractOfferTest.defaultBuyerSecurityDepositPct;
|
import static haveno.apitest.method.offer.AbstractOfferTest.defaultSecurityDepositPct;
|
||||||
import static haveno.cli.CurrencyFormat.formatInternalFiatPrice;
|
import static haveno.cli.CurrencyFormat.formatInternalFiatPrice;
|
||||||
import static haveno.cli.CurrencyFormat.formatSatoshis;
|
import static haveno.cli.CurrencyFormat.formatSatoshis;
|
||||||
import static haveno.common.util.MathUtils.scaleDownByPowerOf10;
|
import static haveno.common.util.MathUtils.scaleDownByPowerOf10;
|
||||||
@ -119,7 +119,7 @@ public class RandomOffer {
|
|||||||
amount,
|
amount,
|
||||||
minAmount,
|
minAmount,
|
||||||
priceMargin,
|
priceMargin,
|
||||||
defaultBuyerSecurityDepositPct.get(),
|
defaultSecurityDepositPct.get(),
|
||||||
"0" /*no trigger price*/);
|
"0" /*no trigger price*/);
|
||||||
} else {
|
} else {
|
||||||
this.offer = botClient.createOfferAtFixedPrice(paymentAccount,
|
this.offer = botClient.createOfferAtFixedPrice(paymentAccount,
|
||||||
@ -128,7 +128,7 @@ public class RandomOffer {
|
|||||||
amount,
|
amount,
|
||||||
minAmount,
|
minAmount,
|
||||||
fixedOfferPrice,
|
fixedOfferPrice,
|
||||||
defaultBuyerSecurityDepositPct.get());
|
defaultSecurityDepositPct.get());
|
||||||
}
|
}
|
||||||
this.id = offer.getId();
|
this.id = offer.getId();
|
||||||
return this;
|
return this;
|
||||||
|
@ -81,7 +81,7 @@ public class OffersServiceRequest {
|
|||||||
.setUseMarketBasedPrice(useMarketBasedPrice)
|
.setUseMarketBasedPrice(useMarketBasedPrice)
|
||||||
.setPrice(fixedPrice)
|
.setPrice(fixedPrice)
|
||||||
.setMarketPriceMarginPct(marketPriceMarginPct)
|
.setMarketPriceMarginPct(marketPriceMarginPct)
|
||||||
.setBuyerSecurityDepositPct(securityDepositPct)
|
.setSecurityDepositPct(securityDepositPct)
|
||||||
.setPaymentAccountId(paymentAcctId)
|
.setPaymentAccountId(paymentAcctId)
|
||||||
.setTriggerPrice(triggerPrice)
|
.setTriggerPrice(triggerPrice)
|
||||||
.build();
|
.build();
|
||||||
|
@ -40,6 +40,7 @@ import haveno.core.offer.OfferDirection;
|
|||||||
import haveno.core.offer.OfferRestrictions;
|
import haveno.core.offer.OfferRestrictions;
|
||||||
import haveno.core.payment.ChargeBackRisk;
|
import haveno.core.payment.ChargeBackRisk;
|
||||||
import haveno.core.payment.PaymentAccount;
|
import haveno.core.payment.PaymentAccount;
|
||||||
|
import haveno.core.payment.TradeLimits;
|
||||||
import haveno.core.payment.payload.PaymentAccountPayload;
|
import haveno.core.payment.payload.PaymentAccountPayload;
|
||||||
import haveno.core.payment.payload.PaymentMethod;
|
import haveno.core.payment.payload.PaymentMethod;
|
||||||
import haveno.core.support.dispute.Dispute;
|
import haveno.core.support.dispute.Dispute;
|
||||||
@ -498,10 +499,15 @@ public class AccountAgeWitnessService {
|
|||||||
return getAccountAge(getMyWitness(paymentAccountPayload), new Date());
|
return getAccountAge(getMyWitness(paymentAccountPayload), new Date());
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getMyTradeLimit(PaymentAccount paymentAccount, String currencyCode, OfferDirection direction) {
|
public long getMyTradeLimit(PaymentAccount paymentAccount, String currencyCode, OfferDirection direction, boolean buyerAsTakerWithoutDeposit) {
|
||||||
if (paymentAccount == null)
|
if (paymentAccount == null)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
if (buyerAsTakerWithoutDeposit) {
|
||||||
|
TradeLimits tradeLimits = new TradeLimits();
|
||||||
|
return tradeLimits.getMaxTradeLimitBuyerAsTakerWithoutDeposit().longValueExact();
|
||||||
|
}
|
||||||
|
|
||||||
AccountAgeWitness accountAgeWitness = getMyWitness(paymentAccount.getPaymentAccountPayload());
|
AccountAgeWitness accountAgeWitness = getMyWitness(paymentAccount.getPaymentAccountPayload());
|
||||||
BigInteger maxTradeLimit = paymentAccount.getPaymentMethod().getMaxTradeLimit(currencyCode);
|
BigInteger maxTradeLimit = paymentAccount.getPaymentMethod().getMaxTradeLimit(currencyCode);
|
||||||
if (hasTradeLimitException(accountAgeWitness)) {
|
if (hasTradeLimitException(accountAgeWitness)) {
|
||||||
|
@ -419,10 +419,12 @@ public class CoreApi {
|
|||||||
double marketPriceMargin,
|
double marketPriceMargin,
|
||||||
long amountAsLong,
|
long amountAsLong,
|
||||||
long minAmountAsLong,
|
long minAmountAsLong,
|
||||||
double buyerSecurityDeposit,
|
double securityDepositPct,
|
||||||
String triggerPriceAsString,
|
String triggerPriceAsString,
|
||||||
boolean reserveExactAmount,
|
boolean reserveExactAmount,
|
||||||
String paymentAccountId,
|
String paymentAccountId,
|
||||||
|
boolean isPrivateOffer,
|
||||||
|
boolean buyerAsTakerWithoutDeposit,
|
||||||
Consumer<Offer> resultHandler,
|
Consumer<Offer> resultHandler,
|
||||||
ErrorMessageHandler errorMessageHandler) {
|
ErrorMessageHandler errorMessageHandler) {
|
||||||
coreOffersService.postOffer(currencyCode,
|
coreOffersService.postOffer(currencyCode,
|
||||||
@ -432,10 +434,12 @@ public class CoreApi {
|
|||||||
marketPriceMargin,
|
marketPriceMargin,
|
||||||
amountAsLong,
|
amountAsLong,
|
||||||
minAmountAsLong,
|
minAmountAsLong,
|
||||||
buyerSecurityDeposit,
|
securityDepositPct,
|
||||||
triggerPriceAsString,
|
triggerPriceAsString,
|
||||||
reserveExactAmount,
|
reserveExactAmount,
|
||||||
paymentAccountId,
|
paymentAccountId,
|
||||||
|
isPrivateOffer,
|
||||||
|
buyerAsTakerWithoutDeposit,
|
||||||
resultHandler,
|
resultHandler,
|
||||||
errorMessageHandler);
|
errorMessageHandler);
|
||||||
}
|
}
|
||||||
@ -448,8 +452,10 @@ public class CoreApi {
|
|||||||
double marketPriceMargin,
|
double marketPriceMargin,
|
||||||
BigInteger amount,
|
BigInteger amount,
|
||||||
BigInteger minAmount,
|
BigInteger minAmount,
|
||||||
double buyerSecurityDeposit,
|
double securityDepositPct,
|
||||||
PaymentAccount paymentAccount) {
|
PaymentAccount paymentAccount,
|
||||||
|
boolean isPrivateOffer,
|
||||||
|
boolean buyerAsTakerWithoutDeposit) {
|
||||||
return coreOffersService.editOffer(offerId,
|
return coreOffersService.editOffer(offerId,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
direction,
|
direction,
|
||||||
@ -458,8 +464,10 @@ public class CoreApi {
|
|||||||
marketPriceMargin,
|
marketPriceMargin,
|
||||||
amount,
|
amount,
|
||||||
minAmount,
|
minAmount,
|
||||||
buyerSecurityDeposit,
|
securityDepositPct,
|
||||||
paymentAccount);
|
paymentAccount,
|
||||||
|
isPrivateOffer,
|
||||||
|
buyerAsTakerWithoutDeposit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancelOffer(String id, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
public void cancelOffer(String id, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||||
@ -535,9 +543,11 @@ public class CoreApi {
|
|||||||
public void takeOffer(String offerId,
|
public void takeOffer(String offerId,
|
||||||
String paymentAccountId,
|
String paymentAccountId,
|
||||||
long amountAsLong,
|
long amountAsLong,
|
||||||
|
String challenge,
|
||||||
Consumer<Trade> resultHandler,
|
Consumer<Trade> resultHandler,
|
||||||
ErrorMessageHandler errorMessageHandler) {
|
ErrorMessageHandler errorMessageHandler) {
|
||||||
Offer offer = coreOffersService.getOffer(offerId);
|
Offer offer = coreOffersService.getOffer(offerId);
|
||||||
|
offer.setChallenge(challenge);
|
||||||
coreTradesService.takeOffer(offer, paymentAccountId, amountAsLong, resultHandler, errorMessageHandler);
|
coreTradesService.takeOffer(offer, paymentAccountId, amountAsLong, resultHandler, errorMessageHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,11 +62,12 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class CoreDisputesService {
|
public class CoreDisputesService {
|
||||||
|
|
||||||
public enum DisputePayout {
|
// TODO: persist in DisputeResult?
|
||||||
|
public enum PayoutSuggestion {
|
||||||
BUYER_GETS_TRADE_AMOUNT,
|
BUYER_GETS_TRADE_AMOUNT,
|
||||||
BUYER_GETS_ALL, // used in desktop
|
BUYER_GETS_ALL,
|
||||||
SELLER_GETS_TRADE_AMOUNT,
|
SELLER_GETS_TRADE_AMOUNT,
|
||||||
SELLER_GETS_ALL, // used in desktop
|
SELLER_GETS_ALL,
|
||||||
CUSTOM
|
CUSTOM
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,17 +173,17 @@ public class CoreDisputesService {
|
|||||||
// create dispute result
|
// create dispute result
|
||||||
var closeDate = new Date();
|
var closeDate = new Date();
|
||||||
var winnerDisputeResult = createDisputeResult(winningDispute, winner, reason, summaryNotes, closeDate);
|
var winnerDisputeResult = createDisputeResult(winningDispute, winner, reason, summaryNotes, closeDate);
|
||||||
DisputePayout payout;
|
PayoutSuggestion payoutSuggestion;
|
||||||
if (customWinnerAmount > 0) {
|
if (customWinnerAmount > 0) {
|
||||||
payout = DisputePayout.CUSTOM;
|
payoutSuggestion = PayoutSuggestion.CUSTOM;
|
||||||
} else if (winner == DisputeResult.Winner.BUYER) {
|
} else if (winner == DisputeResult.Winner.BUYER) {
|
||||||
payout = DisputePayout.BUYER_GETS_TRADE_AMOUNT;
|
payoutSuggestion = PayoutSuggestion.BUYER_GETS_TRADE_AMOUNT;
|
||||||
} else if (winner == DisputeResult.Winner.SELLER) {
|
} else if (winner == DisputeResult.Winner.SELLER) {
|
||||||
payout = DisputePayout.SELLER_GETS_TRADE_AMOUNT;
|
payoutSuggestion = PayoutSuggestion.SELLER_GETS_TRADE_AMOUNT;
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("Unexpected DisputeResult.Winner: " + winner);
|
throw new IllegalStateException("Unexpected DisputeResult.Winner: " + winner);
|
||||||
}
|
}
|
||||||
applyPayoutAmountsToDisputeResult(payout, winningDispute, winnerDisputeResult, customWinnerAmount);
|
applyPayoutAmountsToDisputeResult(payoutSuggestion, winningDispute, winnerDisputeResult, customWinnerAmount);
|
||||||
|
|
||||||
// close winning dispute ticket
|
// close winning dispute ticket
|
||||||
closeDisputeTicket(arbitrationManager, winningDispute, winnerDisputeResult, () -> {
|
closeDisputeTicket(arbitrationManager, winningDispute, winnerDisputeResult, () -> {
|
||||||
@ -227,26 +228,26 @@ public class CoreDisputesService {
|
|||||||
* Sets payout amounts given a payout type. If custom is selected, the winner gets a custom amount, and the peer
|
* Sets payout amounts given a payout type. If custom is selected, the winner gets a custom amount, and the peer
|
||||||
* receives the remaining amount minus the mining fee.
|
* receives the remaining amount minus the mining fee.
|
||||||
*/
|
*/
|
||||||
public void applyPayoutAmountsToDisputeResult(DisputePayout payout, Dispute dispute, DisputeResult disputeResult, long customWinnerAmount) {
|
public void applyPayoutAmountsToDisputeResult(PayoutSuggestion payoutSuggestion, Dispute dispute, DisputeResult disputeResult, long customWinnerAmount) {
|
||||||
Contract contract = dispute.getContract();
|
Contract contract = dispute.getContract();
|
||||||
Trade trade = tradeManager.getTrade(dispute.getTradeId());
|
Trade trade = tradeManager.getTrade(dispute.getTradeId());
|
||||||
BigInteger buyerSecurityDeposit = trade.getBuyer().getSecurityDeposit();
|
BigInteger buyerSecurityDeposit = trade.getBuyer().getSecurityDeposit();
|
||||||
BigInteger sellerSecurityDeposit = trade.getSeller().getSecurityDeposit();
|
BigInteger sellerSecurityDeposit = trade.getSeller().getSecurityDeposit();
|
||||||
BigInteger tradeAmount = contract.getTradeAmount();
|
BigInteger tradeAmount = contract.getTradeAmount();
|
||||||
disputeResult.setSubtractFeeFrom(DisputeResult.SubtractFeeFrom.BUYER_AND_SELLER);
|
disputeResult.setSubtractFeeFrom(DisputeResult.SubtractFeeFrom.BUYER_AND_SELLER);
|
||||||
if (payout == DisputePayout.BUYER_GETS_TRADE_AMOUNT) {
|
if (payoutSuggestion == PayoutSuggestion.BUYER_GETS_TRADE_AMOUNT) {
|
||||||
disputeResult.setBuyerPayoutAmountBeforeCost(tradeAmount.add(buyerSecurityDeposit));
|
disputeResult.setBuyerPayoutAmountBeforeCost(tradeAmount.add(buyerSecurityDeposit));
|
||||||
disputeResult.setSellerPayoutAmountBeforeCost(sellerSecurityDeposit);
|
disputeResult.setSellerPayoutAmountBeforeCost(sellerSecurityDeposit);
|
||||||
} else if (payout == DisputePayout.BUYER_GETS_ALL) {
|
} else if (payoutSuggestion == PayoutSuggestion.BUYER_GETS_ALL) {
|
||||||
disputeResult.setBuyerPayoutAmountBeforeCost(tradeAmount.add(buyerSecurityDeposit).add(sellerSecurityDeposit)); // TODO (woodser): apply min payout to incentivize loser? (see post v1.1.7)
|
disputeResult.setBuyerPayoutAmountBeforeCost(tradeAmount.add(buyerSecurityDeposit).add(sellerSecurityDeposit)); // TODO (woodser): apply min payout to incentivize loser? (see post v1.1.7)
|
||||||
disputeResult.setSellerPayoutAmountBeforeCost(BigInteger.ZERO);
|
disputeResult.setSellerPayoutAmountBeforeCost(BigInteger.ZERO);
|
||||||
} else if (payout == DisputePayout.SELLER_GETS_TRADE_AMOUNT) {
|
} else if (payoutSuggestion == PayoutSuggestion.SELLER_GETS_TRADE_AMOUNT) {
|
||||||
disputeResult.setBuyerPayoutAmountBeforeCost(buyerSecurityDeposit);
|
disputeResult.setBuyerPayoutAmountBeforeCost(buyerSecurityDeposit);
|
||||||
disputeResult.setSellerPayoutAmountBeforeCost(tradeAmount.add(sellerSecurityDeposit));
|
disputeResult.setSellerPayoutAmountBeforeCost(tradeAmount.add(sellerSecurityDeposit));
|
||||||
} else if (payout == DisputePayout.SELLER_GETS_ALL) {
|
} else if (payoutSuggestion == PayoutSuggestion.SELLER_GETS_ALL) {
|
||||||
disputeResult.setBuyerPayoutAmountBeforeCost(BigInteger.ZERO);
|
disputeResult.setBuyerPayoutAmountBeforeCost(BigInteger.ZERO);
|
||||||
disputeResult.setSellerPayoutAmountBeforeCost(tradeAmount.add(sellerSecurityDeposit).add(buyerSecurityDeposit));
|
disputeResult.setSellerPayoutAmountBeforeCost(tradeAmount.add(sellerSecurityDeposit).add(buyerSecurityDeposit));
|
||||||
} else if (payout == DisputePayout.CUSTOM) {
|
} else if (payoutSuggestion == PayoutSuggestion.CUSTOM) {
|
||||||
if (customWinnerAmount > trade.getWallet().getBalance().longValueExact()) throw new RuntimeException("Winner payout is more than the trade wallet's balance");
|
if (customWinnerAmount > trade.getWallet().getBalance().longValueExact()) throw new RuntimeException("Winner payout is more than the trade wallet's balance");
|
||||||
long loserAmount = tradeAmount.add(buyerSecurityDeposit).add(sellerSecurityDeposit).subtract(BigInteger.valueOf(customWinnerAmount)).longValueExact();
|
long loserAmount = tradeAmount.add(buyerSecurityDeposit).add(sellerSecurityDeposit).subtract(BigInteger.valueOf(customWinnerAmount)).longValueExact();
|
||||||
if (loserAmount < 0) throw new RuntimeException("Loser payout cannot be negative");
|
if (loserAmount < 0) throw new RuntimeException("Loser payout cannot be negative");
|
||||||
|
@ -172,10 +172,12 @@ public class CoreOffersService {
|
|||||||
double marketPriceMargin,
|
double marketPriceMargin,
|
||||||
long amountAsLong,
|
long amountAsLong,
|
||||||
long minAmountAsLong,
|
long minAmountAsLong,
|
||||||
double securityDeposit,
|
double securityDepositPct,
|
||||||
String triggerPriceAsString,
|
String triggerPriceAsString,
|
||||||
boolean reserveExactAmount,
|
boolean reserveExactAmount,
|
||||||
String paymentAccountId,
|
String paymentAccountId,
|
||||||
|
boolean isPrivateOffer,
|
||||||
|
boolean buyerAsTakerWithoutDeposit,
|
||||||
Consumer<Offer> resultHandler,
|
Consumer<Offer> resultHandler,
|
||||||
ErrorMessageHandler errorMessageHandler) {
|
ErrorMessageHandler errorMessageHandler) {
|
||||||
coreWalletsService.verifyWalletsAreAvailable();
|
coreWalletsService.verifyWalletsAreAvailable();
|
||||||
@ -199,8 +201,10 @@ public class CoreOffersService {
|
|||||||
price,
|
price,
|
||||||
useMarketBasedPrice,
|
useMarketBasedPrice,
|
||||||
exactMultiply(marketPriceMargin, 0.01),
|
exactMultiply(marketPriceMargin, 0.01),
|
||||||
securityDeposit,
|
securityDepositPct,
|
||||||
paymentAccount);
|
paymentAccount,
|
||||||
|
isPrivateOffer,
|
||||||
|
buyerAsTakerWithoutDeposit);
|
||||||
|
|
||||||
verifyPaymentAccountIsValidForNewOffer(offer, paymentAccount);
|
verifyPaymentAccountIsValidForNewOffer(offer, paymentAccount);
|
||||||
|
|
||||||
@ -223,8 +227,10 @@ public class CoreOffersService {
|
|||||||
double marketPriceMargin,
|
double marketPriceMargin,
|
||||||
BigInteger amount,
|
BigInteger amount,
|
||||||
BigInteger minAmount,
|
BigInteger minAmount,
|
||||||
double buyerSecurityDeposit,
|
double securityDepositPct,
|
||||||
PaymentAccount paymentAccount) {
|
PaymentAccount paymentAccount,
|
||||||
|
boolean isPrivateOffer,
|
||||||
|
boolean buyerAsTakerWithoutDeposit) {
|
||||||
return createOfferService.createAndGetOffer(offerId,
|
return createOfferService.createAndGetOffer(offerId,
|
||||||
direction,
|
direction,
|
||||||
currencyCode.toUpperCase(),
|
currencyCode.toUpperCase(),
|
||||||
@ -233,8 +239,10 @@ public class CoreOffersService {
|
|||||||
price,
|
price,
|
||||||
useMarketBasedPrice,
|
useMarketBasedPrice,
|
||||||
exactMultiply(marketPriceMargin, 0.01),
|
exactMultiply(marketPriceMargin, 0.01),
|
||||||
buyerSecurityDeposit,
|
securityDepositPct,
|
||||||
paymentAccount);
|
paymentAccount,
|
||||||
|
isPrivateOffer,
|
||||||
|
buyerAsTakerWithoutDeposit);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cancelOffer(String id, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
void cancelOffer(String id, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||||
|
@ -132,7 +132,7 @@ class CoreTradesService {
|
|||||||
// adjust amount for fixed-price offer (based on TakeOfferViewModel)
|
// adjust amount for fixed-price offer (based on TakeOfferViewModel)
|
||||||
String currencyCode = offer.getCurrencyCode();
|
String currencyCode = offer.getCurrencyCode();
|
||||||
OfferDirection direction = offer.getOfferPayload().getDirection();
|
OfferDirection direction = offer.getOfferPayload().getDirection();
|
||||||
long maxTradeLimit = offerUtil.getMaxTradeLimit(paymentAccount, currencyCode, direction);
|
long maxTradeLimit = offerUtil.getMaxTradeLimit(paymentAccount, currencyCode, direction, offer.hasBuyerAsTakerWithoutDeposit());
|
||||||
if (offer.getPrice() != null) {
|
if (offer.getPrice() != null) {
|
||||||
if (PaymentMethod.isRoundedForAtmCash(paymentAccount.getPaymentMethod().getId())) {
|
if (PaymentMethod.isRoundedForAtmCash(paymentAccount.getPaymentMethod().getId())) {
|
||||||
amount = CoinUtil.getRoundedAtmCashAmount(amount, offer.getPrice(), maxTradeLimit);
|
amount = CoinUtil.getRoundedAtmCashAmount(amount, offer.getPrice(), maxTradeLimit);
|
||||||
|
@ -78,6 +78,8 @@ public class OfferInfo implements Payload {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private final String splitOutputTxHash;
|
private final String splitOutputTxHash;
|
||||||
private final long splitOutputTxFee;
|
private final long splitOutputTxFee;
|
||||||
|
private final boolean isPrivateOffer;
|
||||||
|
private final String challenge;
|
||||||
|
|
||||||
public OfferInfo(OfferInfoBuilder builder) {
|
public OfferInfo(OfferInfoBuilder builder) {
|
||||||
this.id = builder.getId();
|
this.id = builder.getId();
|
||||||
@ -111,6 +113,8 @@ public class OfferInfo implements Payload {
|
|||||||
this.arbitratorSigner = builder.getArbitratorSigner();
|
this.arbitratorSigner = builder.getArbitratorSigner();
|
||||||
this.splitOutputTxHash = builder.getSplitOutputTxHash();
|
this.splitOutputTxHash = builder.getSplitOutputTxHash();
|
||||||
this.splitOutputTxFee = builder.getSplitOutputTxFee();
|
this.splitOutputTxFee = builder.getSplitOutputTxFee();
|
||||||
|
this.isPrivateOffer = builder.isPrivateOffer();
|
||||||
|
this.challenge = builder.getChallenge();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OfferInfo toOfferInfo(Offer offer) {
|
public static OfferInfo toOfferInfo(Offer offer) {
|
||||||
@ -137,6 +141,7 @@ public class OfferInfo implements Payload {
|
|||||||
.withIsActivated(isActivated)
|
.withIsActivated(isActivated)
|
||||||
.withSplitOutputTxHash(openOffer.getSplitOutputTxHash())
|
.withSplitOutputTxHash(openOffer.getSplitOutputTxHash())
|
||||||
.withSplitOutputTxFee(openOffer.getSplitOutputTxFee())
|
.withSplitOutputTxFee(openOffer.getSplitOutputTxFee())
|
||||||
|
.withChallenge(openOffer.getChallenge())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,7 +182,9 @@ public class OfferInfo implements Payload {
|
|||||||
.withPubKeyRing(offer.getOfferPayload().getPubKeyRing().toString())
|
.withPubKeyRing(offer.getOfferPayload().getPubKeyRing().toString())
|
||||||
.withVersionNumber(offer.getOfferPayload().getVersionNr())
|
.withVersionNumber(offer.getOfferPayload().getVersionNr())
|
||||||
.withProtocolVersion(offer.getOfferPayload().getProtocolVersion())
|
.withProtocolVersion(offer.getOfferPayload().getProtocolVersion())
|
||||||
.withArbitratorSigner(offer.getOfferPayload().getArbitratorSigner() == null ? null : offer.getOfferPayload().getArbitratorSigner().getFullAddress());
|
.withArbitratorSigner(offer.getOfferPayload().getArbitratorSigner() == null ? null : offer.getOfferPayload().getArbitratorSigner().getFullAddress())
|
||||||
|
.withIsPrivateOffer(offer.isPrivateOffer())
|
||||||
|
.withChallenge(offer.getChallenge());
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -215,9 +222,11 @@ public class OfferInfo implements Payload {
|
|||||||
.setPubKeyRing(pubKeyRing)
|
.setPubKeyRing(pubKeyRing)
|
||||||
.setVersionNr(versionNumber)
|
.setVersionNr(versionNumber)
|
||||||
.setProtocolVersion(protocolVersion)
|
.setProtocolVersion(protocolVersion)
|
||||||
.setSplitOutputTxFee(splitOutputTxFee);
|
.setSplitOutputTxFee(splitOutputTxFee)
|
||||||
|
.setIsPrivateOffer(isPrivateOffer);
|
||||||
Optional.ofNullable(arbitratorSigner).ifPresent(builder::setArbitratorSigner);
|
Optional.ofNullable(arbitratorSigner).ifPresent(builder::setArbitratorSigner);
|
||||||
Optional.ofNullable(splitOutputTxHash).ifPresent(builder::setSplitOutputTxHash);
|
Optional.ofNullable(splitOutputTxHash).ifPresent(builder::setSplitOutputTxHash);
|
||||||
|
Optional.ofNullable(challenge).ifPresent(builder::setChallenge);
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,6 +264,8 @@ public class OfferInfo implements Payload {
|
|||||||
.withArbitratorSigner(proto.getArbitratorSigner())
|
.withArbitratorSigner(proto.getArbitratorSigner())
|
||||||
.withSplitOutputTxHash(proto.getSplitOutputTxHash())
|
.withSplitOutputTxHash(proto.getSplitOutputTxHash())
|
||||||
.withSplitOutputTxFee(proto.getSplitOutputTxFee())
|
.withSplitOutputTxFee(proto.getSplitOutputTxFee())
|
||||||
|
.withIsPrivateOffer(proto.getIsPrivateOffer())
|
||||||
|
.withChallenge(proto.getChallenge())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,14 +172,14 @@ public class TradeInfo implements Payload {
|
|||||||
.withAmount(trade.getAmount().longValueExact())
|
.withAmount(trade.getAmount().longValueExact())
|
||||||
.withMakerFee(trade.getMakerFee().longValueExact())
|
.withMakerFee(trade.getMakerFee().longValueExact())
|
||||||
.withTakerFee(trade.getTakerFee().longValueExact())
|
.withTakerFee(trade.getTakerFee().longValueExact())
|
||||||
.withBuyerSecurityDeposit(trade.getBuyer().getSecurityDeposit() == null ? -1 : trade.getBuyer().getSecurityDeposit().longValueExact())
|
.withBuyerSecurityDeposit(trade.getBuyer().getSecurityDeposit().longValueExact())
|
||||||
.withSellerSecurityDeposit(trade.getSeller().getSecurityDeposit() == null ? -1 : trade.getSeller().getSecurityDeposit().longValueExact())
|
.withSellerSecurityDeposit(trade.getSeller().getSecurityDeposit().longValueExact())
|
||||||
.withBuyerDepositTxFee(trade.getBuyer().getDepositTxFee() == null ? -1 : trade.getBuyer().getDepositTxFee().longValueExact())
|
.withBuyerDepositTxFee(trade.getBuyer().getDepositTxFee().longValueExact())
|
||||||
.withSellerDepositTxFee(trade.getSeller().getDepositTxFee() == null ? -1 : trade.getSeller().getDepositTxFee().longValueExact())
|
.withSellerDepositTxFee(trade.getSeller().getDepositTxFee().longValueExact())
|
||||||
.withBuyerPayoutTxFee(trade.getBuyer().getPayoutTxFee() == null ? -1 : trade.getBuyer().getPayoutTxFee().longValueExact())
|
.withBuyerPayoutTxFee(trade.getBuyer().getPayoutTxFee().longValueExact())
|
||||||
.withSellerPayoutTxFee(trade.getSeller().getPayoutTxFee() == null ? -1 : trade.getSeller().getPayoutTxFee().longValueExact())
|
.withSellerPayoutTxFee(trade.getSeller().getPayoutTxFee().longValueExact())
|
||||||
.withBuyerPayoutAmount(trade.getBuyer().getPayoutAmount() == null ? -1 : trade.getBuyer().getPayoutAmount().longValueExact())
|
.withBuyerPayoutAmount(trade.getBuyer().getPayoutAmount().longValueExact())
|
||||||
.withSellerPayoutAmount(trade.getSeller().getPayoutAmount() == null ? -1 : trade.getSeller().getPayoutAmount().longValueExact())
|
.withSellerPayoutAmount(trade.getSeller().getPayoutAmount().longValueExact())
|
||||||
.withTotalTxFee(trade.getTotalTxFee().longValueExact())
|
.withTotalTxFee(trade.getTotalTxFee().longValueExact())
|
||||||
.withPrice(toPreciseTradePrice.apply(trade))
|
.withPrice(toPreciseTradePrice.apply(trade))
|
||||||
.withVolume(toRoundedVolume.apply(trade))
|
.withVolume(toRoundedVolume.apply(trade))
|
||||||
|
@ -63,6 +63,8 @@ public final class OfferInfoBuilder {
|
|||||||
private String arbitratorSigner;
|
private String arbitratorSigner;
|
||||||
private String splitOutputTxHash;
|
private String splitOutputTxHash;
|
||||||
private long splitOutputTxFee;
|
private long splitOutputTxFee;
|
||||||
|
private boolean isPrivateOffer;
|
||||||
|
private String challenge;
|
||||||
|
|
||||||
public OfferInfoBuilder withId(String id) {
|
public OfferInfoBuilder withId(String id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@ -234,6 +236,16 @@ public final class OfferInfoBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public OfferInfoBuilder withIsPrivateOffer(boolean isPrivateOffer) {
|
||||||
|
this.isPrivateOffer = isPrivateOffer;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OfferInfoBuilder withChallenge(String challenge) {
|
||||||
|
this.challenge = challenge;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public OfferInfo build() {
|
public OfferInfo build() {
|
||||||
return new OfferInfo(this);
|
return new OfferInfo(this);
|
||||||
}
|
}
|
||||||
|
@ -33,10 +33,8 @@ import haveno.core.provider.price.PriceFeedService;
|
|||||||
import haveno.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
import haveno.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||||
import haveno.core.trade.HavenoUtils;
|
import haveno.core.trade.HavenoUtils;
|
||||||
import haveno.core.trade.statistics.TradeStatisticsManager;
|
import haveno.core.trade.statistics.TradeStatisticsManager;
|
||||||
import haveno.core.user.Preferences;
|
|
||||||
import haveno.core.user.User;
|
import haveno.core.user.User;
|
||||||
import haveno.core.util.coin.CoinUtil;
|
import haveno.core.util.coin.CoinUtil;
|
||||||
import haveno.core.xmr.wallet.Restrictions;
|
|
||||||
import haveno.core.xmr.wallet.XmrWalletService;
|
import haveno.core.xmr.wallet.XmrWalletService;
|
||||||
import haveno.network.p2p.NodeAddress;
|
import haveno.network.p2p.NodeAddress;
|
||||||
import haveno.network.p2p.P2PService;
|
import haveno.network.p2p.P2PService;
|
||||||
@ -102,9 +100,10 @@ public class CreateOfferService {
|
|||||||
Price fixedPrice,
|
Price fixedPrice,
|
||||||
boolean useMarketBasedPrice,
|
boolean useMarketBasedPrice,
|
||||||
double marketPriceMargin,
|
double marketPriceMargin,
|
||||||
double securityDepositAsDouble,
|
double securityDepositPct,
|
||||||
PaymentAccount paymentAccount) {
|
PaymentAccount paymentAccount,
|
||||||
|
boolean isPrivateOffer,
|
||||||
|
boolean buyerAsTakerWithoutDeposit) {
|
||||||
log.info("create and get offer with offerId={}, " +
|
log.info("create and get offer with offerId={}, " +
|
||||||
"currencyCode={}, " +
|
"currencyCode={}, " +
|
||||||
"direction={}, " +
|
"direction={}, " +
|
||||||
@ -113,7 +112,9 @@ public class CreateOfferService {
|
|||||||
"marketPriceMargin={}, " +
|
"marketPriceMargin={}, " +
|
||||||
"amount={}, " +
|
"amount={}, " +
|
||||||
"minAmount={}, " +
|
"minAmount={}, " +
|
||||||
"securityDeposit={}",
|
"securityDepositPct={}, " +
|
||||||
|
"isPrivateOffer={}, " +
|
||||||
|
"buyerAsTakerWithoutDeposit={}",
|
||||||
offerId,
|
offerId,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
direction,
|
direction,
|
||||||
@ -122,7 +123,16 @@ public class CreateOfferService {
|
|||||||
marketPriceMargin,
|
marketPriceMargin,
|
||||||
amount,
|
amount,
|
||||||
minAmount,
|
minAmount,
|
||||||
securityDepositAsDouble);
|
securityDepositPct,
|
||||||
|
isPrivateOffer,
|
||||||
|
buyerAsTakerWithoutDeposit);
|
||||||
|
|
||||||
|
|
||||||
|
// verify buyer as taker security deposit
|
||||||
|
boolean isBuyerMaker = offerUtil.isBuyOffer(direction);
|
||||||
|
if (!isBuyerMaker && !isPrivateOffer && buyerAsTakerWithoutDeposit) {
|
||||||
|
throw new IllegalArgumentException("Buyer as taker deposit is required for public offers");
|
||||||
|
}
|
||||||
|
|
||||||
// verify fixed price xor market price with margin
|
// verify fixed price xor market price with margin
|
||||||
if (fixedPrice != null) {
|
if (fixedPrice != null) {
|
||||||
@ -143,10 +153,17 @@ public class CreateOfferService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// adjust amount and min amount for fixed-price offer
|
// adjust amount and min amount for fixed-price offer
|
||||||
long maxTradeLimit = offerUtil.getMaxTradeLimit(paymentAccount, currencyCode, direction);
|
|
||||||
if (fixedPrice != null) {
|
if (fixedPrice != null) {
|
||||||
amount = CoinUtil.getRoundedAmount(amount, fixedPrice, maxTradeLimit, currencyCode, paymentAccount.getPaymentMethod().getId());
|
amount = CoinUtil.getRoundedAmount(amount, fixedPrice, null, currencyCode, paymentAccount.getPaymentMethod().getId());
|
||||||
minAmount = CoinUtil.getRoundedAmount(minAmount, fixedPrice, maxTradeLimit, currencyCode, paymentAccount.getPaymentMethod().getId());
|
minAmount = CoinUtil.getRoundedAmount(minAmount, fixedPrice, null, currencyCode, paymentAccount.getPaymentMethod().getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate one-time challenge for private offer
|
||||||
|
String challenge = null;
|
||||||
|
String challengeHash = null;
|
||||||
|
if (isPrivateOffer) {
|
||||||
|
challenge = HavenoUtils.generateChallenge();
|
||||||
|
challengeHash = HavenoUtils.getChallengeHash(challenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
long priceAsLong = fixedPrice != null ? fixedPrice.getValue() : 0L;
|
long priceAsLong = fixedPrice != null ? fixedPrice.getValue() : 0L;
|
||||||
@ -161,21 +178,16 @@ public class CreateOfferService {
|
|||||||
String bankId = PaymentAccountUtil.getBankId(paymentAccount);
|
String bankId = PaymentAccountUtil.getBankId(paymentAccount);
|
||||||
List<String> acceptedBanks = PaymentAccountUtil.getAcceptedBanks(paymentAccount);
|
List<String> acceptedBanks = PaymentAccountUtil.getAcceptedBanks(paymentAccount);
|
||||||
long maxTradePeriod = paymentAccount.getMaxTradePeriod();
|
long maxTradePeriod = paymentAccount.getMaxTradePeriod();
|
||||||
|
boolean hasBuyerAsTakerWithoutDeposit = !isBuyerMaker && isPrivateOffer && buyerAsTakerWithoutDeposit;
|
||||||
// reserved for future use cases
|
long maxTradeLimit = offerUtil.getMaxTradeLimit(paymentAccount, currencyCode, direction, hasBuyerAsTakerWithoutDeposit);
|
||||||
// Use null values if not set
|
|
||||||
boolean isPrivateOffer = false;
|
|
||||||
boolean useAutoClose = false;
|
boolean useAutoClose = false;
|
||||||
boolean useReOpenAfterAutoClose = false;
|
boolean useReOpenAfterAutoClose = false;
|
||||||
long lowerClosePrice = 0;
|
long lowerClosePrice = 0;
|
||||||
long upperClosePrice = 0;
|
long upperClosePrice = 0;
|
||||||
String hashOfChallenge = null;
|
Map<String, String> extraDataMap = offerUtil.getExtraDataMap(paymentAccount, currencyCode, direction);
|
||||||
Map<String, String> extraDataMap = offerUtil.getExtraDataMap(paymentAccount,
|
|
||||||
currencyCode,
|
|
||||||
direction);
|
|
||||||
|
|
||||||
offerUtil.validateOfferData(
|
offerUtil.validateOfferData(
|
||||||
securityDepositAsDouble,
|
securityDepositPct,
|
||||||
paymentAccount,
|
paymentAccount,
|
||||||
currencyCode);
|
currencyCode);
|
||||||
|
|
||||||
@ -189,11 +201,11 @@ public class CreateOfferService {
|
|||||||
useMarketBasedPriceValue,
|
useMarketBasedPriceValue,
|
||||||
amountAsLong,
|
amountAsLong,
|
||||||
minAmountAsLong,
|
minAmountAsLong,
|
||||||
HavenoUtils.MAKER_FEE_PCT,
|
hasBuyerAsTakerWithoutDeposit ? HavenoUtils.MAKER_FEE_FOR_TAKER_WITHOUT_DEPOSIT_PCT : HavenoUtils.MAKER_FEE_PCT,
|
||||||
HavenoUtils.TAKER_FEE_PCT,
|
hasBuyerAsTakerWithoutDeposit ? 0d : HavenoUtils.TAKER_FEE_PCT,
|
||||||
HavenoUtils.PENALTY_FEE_PCT,
|
HavenoUtils.PENALTY_FEE_PCT,
|
||||||
securityDepositAsDouble,
|
hasBuyerAsTakerWithoutDeposit ? 0d : securityDepositPct, // buyer as taker security deposit is optional for private offers
|
||||||
securityDepositAsDouble,
|
securityDepositPct,
|
||||||
baseCurrencyCode,
|
baseCurrencyCode,
|
||||||
counterCurrencyCode,
|
counterCurrencyCode,
|
||||||
paymentAccount.getPaymentMethod().getId(),
|
paymentAccount.getPaymentMethod().getId(),
|
||||||
@ -211,7 +223,7 @@ public class CreateOfferService {
|
|||||||
upperClosePrice,
|
upperClosePrice,
|
||||||
lowerClosePrice,
|
lowerClosePrice,
|
||||||
isPrivateOffer,
|
isPrivateOffer,
|
||||||
hashOfChallenge,
|
challengeHash,
|
||||||
extraDataMap,
|
extraDataMap,
|
||||||
Version.TRADE_PROTOCOL_VERSION,
|
Version.TRADE_PROTOCOL_VERSION,
|
||||||
null,
|
null,
|
||||||
@ -219,38 +231,10 @@ public class CreateOfferService {
|
|||||||
null);
|
null);
|
||||||
Offer offer = new Offer(offerPayload);
|
Offer offer = new Offer(offerPayload);
|
||||||
offer.setPriceFeedService(priceFeedService);
|
offer.setPriceFeedService(priceFeedService);
|
||||||
|
offer.setChallenge(challenge);
|
||||||
return offer;
|
return offer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigInteger getReservedFundsForOffer(OfferDirection direction,
|
|
||||||
BigInteger amount,
|
|
||||||
double buyerSecurityDeposit,
|
|
||||||
double sellerSecurityDeposit) {
|
|
||||||
|
|
||||||
BigInteger reservedFundsForOffer = getSecurityDeposit(direction,
|
|
||||||
amount,
|
|
||||||
buyerSecurityDeposit,
|
|
||||||
sellerSecurityDeposit);
|
|
||||||
if (!offerUtil.isBuyOffer(direction))
|
|
||||||
reservedFundsForOffer = reservedFundsForOffer.add(amount);
|
|
||||||
|
|
||||||
return reservedFundsForOffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigInteger getSecurityDeposit(OfferDirection direction,
|
|
||||||
BigInteger amount,
|
|
||||||
double buyerSecurityDeposit,
|
|
||||||
double sellerSecurityDeposit) {
|
|
||||||
return offerUtil.isBuyOffer(direction) ?
|
|
||||||
getBuyerSecurityDeposit(amount, buyerSecurityDeposit) :
|
|
||||||
getSellerSecurityDeposit(amount, sellerSecurityDeposit);
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getSellerSecurityDepositAsDouble(double buyerSecurityDeposit) {
|
|
||||||
return Preferences.USE_SYMMETRIC_SECURITY_DEPOSIT ? buyerSecurityDeposit :
|
|
||||||
Restrictions.getSellerSecurityDepositAsPercent();
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Private
|
// Private
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -259,26 +243,4 @@ public class CreateOfferService {
|
|||||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||||
return marketPrice != null && marketPrice.isExternallyProvidedPrice();
|
return marketPrice != null && marketPrice.isExternallyProvidedPrice();
|
||||||
}
|
}
|
||||||
|
|
||||||
private BigInteger getBuyerSecurityDeposit(BigInteger amount, double buyerSecurityDeposit) {
|
|
||||||
BigInteger percentOfAmount = CoinUtil.getPercentOfAmount(buyerSecurityDeposit, amount);
|
|
||||||
return getBoundedBuyerSecurityDeposit(percentOfAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigInteger getSellerSecurityDeposit(BigInteger amount, double sellerSecurityDeposit) {
|
|
||||||
BigInteger percentOfAmount = CoinUtil.getPercentOfAmount(sellerSecurityDeposit, amount);
|
|
||||||
return getBoundedSellerSecurityDeposit(percentOfAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigInteger getBoundedBuyerSecurityDeposit(BigInteger value) {
|
|
||||||
// We need to ensure that for small amount values we don't get a too low BTC amount. We limit it with using the
|
|
||||||
// MinBuyerSecurityDeposit from Restrictions.
|
|
||||||
return Restrictions.getMinBuyerSecurityDeposit().max(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigInteger getBoundedSellerSecurityDeposit(BigInteger value) {
|
|
||||||
// We need to ensure that for small amount values we don't get a too low BTC amount. We limit it with using the
|
|
||||||
// MinSellerSecurityDeposit from Restrictions.
|
|
||||||
return Restrictions.getMinSellerSecurityDeposit().max(value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -115,6 +115,12 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
|||||||
@Setter
|
@Setter
|
||||||
transient private boolean isReservedFundsSpent;
|
transient private boolean isReservedFundsSpent;
|
||||||
|
|
||||||
|
@JsonExclude
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Nullable
|
||||||
|
transient private String challenge;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Constructor
|
// Constructor
|
||||||
@ -337,6 +343,18 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
|||||||
return offerPayload.getSellerSecurityDepositPct();
|
return offerPayload.getSellerSecurityDepositPct();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPrivateOffer() {
|
||||||
|
return offerPayload.isPrivateOffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getChallengeHash() {
|
||||||
|
return offerPayload.getChallengeHash();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasBuyerAsTakerWithoutDeposit() {
|
||||||
|
return getDirection() == OfferDirection.SELL && getBuyerSecurityDepositPct() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
public BigInteger getMaxTradeLimit() {
|
public BigInteger getMaxTradeLimit() {
|
||||||
return BigInteger.valueOf(offerPayload.getMaxTradeLimit());
|
return BigInteger.valueOf(offerPayload.getMaxTradeLimit());
|
||||||
}
|
}
|
||||||
|
@ -201,7 +201,7 @@ public class OfferFilterService {
|
|||||||
accountAgeWitnessService);
|
accountAgeWitnessService);
|
||||||
long myTradeLimit = accountOptional
|
long myTradeLimit = accountOptional
|
||||||
.map(paymentAccount -> accountAgeWitnessService.getMyTradeLimit(paymentAccount,
|
.map(paymentAccount -> accountAgeWitnessService.getMyTradeLimit(paymentAccount,
|
||||||
offer.getCurrencyCode(), offer.getMirroredDirection()))
|
offer.getCurrencyCode(), offer.getMirroredDirection(), offer.hasBuyerAsTakerWithoutDeposit()))
|
||||||
.orElse(0L);
|
.orElse(0L);
|
||||||
long offerMinAmount = offer.getMinAmount().longValueExact();
|
long offerMinAmount = offer.getMinAmount().longValueExact();
|
||||||
log.debug("isInsufficientTradeLimit accountOptional={}, myTradeLimit={}, offerMinAmount={}, ",
|
log.debug("isInsufficientTradeLimit accountOptional={}, myTradeLimit={}, offerMinAmount={}, ",
|
||||||
|
@ -156,7 +156,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
// Reserved for possible future use to support private trades where the taker needs to have an accessKey
|
// Reserved for possible future use to support private trades where the taker needs to have an accessKey
|
||||||
private final boolean isPrivateOffer;
|
private final boolean isPrivateOffer;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final String hashOfChallenge;
|
private final String challengeHash;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -195,7 +195,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
long lowerClosePrice,
|
long lowerClosePrice,
|
||||||
long upperClosePrice,
|
long upperClosePrice,
|
||||||
boolean isPrivateOffer,
|
boolean isPrivateOffer,
|
||||||
@Nullable String hashOfChallenge,
|
@Nullable String challengeHash,
|
||||||
@Nullable Map<String, String> extraDataMap,
|
@Nullable Map<String, String> extraDataMap,
|
||||||
int protocolVersion,
|
int protocolVersion,
|
||||||
@Nullable NodeAddress arbitratorSigner,
|
@Nullable NodeAddress arbitratorSigner,
|
||||||
@ -238,7 +238,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
this.lowerClosePrice = lowerClosePrice;
|
this.lowerClosePrice = lowerClosePrice;
|
||||||
this.upperClosePrice = upperClosePrice;
|
this.upperClosePrice = upperClosePrice;
|
||||||
this.isPrivateOffer = isPrivateOffer;
|
this.isPrivateOffer = isPrivateOffer;
|
||||||
this.hashOfChallenge = hashOfChallenge;
|
this.challengeHash = challengeHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getHash() {
|
public byte[] getHash() {
|
||||||
@ -284,7 +284,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
lowerClosePrice,
|
lowerClosePrice,
|
||||||
upperClosePrice,
|
upperClosePrice,
|
||||||
isPrivateOffer,
|
isPrivateOffer,
|
||||||
hashOfChallenge,
|
challengeHash,
|
||||||
extraDataMap,
|
extraDataMap,
|
||||||
protocolVersion,
|
protocolVersion,
|
||||||
arbitratorSigner,
|
arbitratorSigner,
|
||||||
@ -328,12 +328,17 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
|
|
||||||
public BigInteger getBuyerSecurityDepositForTradeAmount(BigInteger tradeAmount) {
|
public BigInteger getBuyerSecurityDepositForTradeAmount(BigInteger tradeAmount) {
|
||||||
BigInteger securityDepositUnadjusted = HavenoUtils.multiply(tradeAmount, getBuyerSecurityDepositPct());
|
BigInteger securityDepositUnadjusted = HavenoUtils.multiply(tradeAmount, getBuyerSecurityDepositPct());
|
||||||
return Restrictions.getMinBuyerSecurityDeposit().max(securityDepositUnadjusted);
|
boolean isBuyerTaker = getDirection() == OfferDirection.SELL;
|
||||||
|
if (isPrivateOffer() && isBuyerTaker) {
|
||||||
|
return securityDepositUnadjusted;
|
||||||
|
} else {
|
||||||
|
return Restrictions.getMinSecurityDeposit().max(securityDepositUnadjusted);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigInteger getSellerSecurityDepositForTradeAmount(BigInteger tradeAmount) {
|
public BigInteger getSellerSecurityDepositForTradeAmount(BigInteger tradeAmount) {
|
||||||
BigInteger securityDepositUnadjusted = HavenoUtils.multiply(tradeAmount, getSellerSecurityDepositPct());
|
BigInteger securityDepositUnadjusted = HavenoUtils.multiply(tradeAmount, getSellerSecurityDepositPct());
|
||||||
return Restrictions.getMinSellerSecurityDeposit().max(securityDepositUnadjusted);
|
return Restrictions.getMinSecurityDeposit().max(securityDepositUnadjusted);
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -376,7 +381,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
Optional.ofNullable(bankId).ifPresent(builder::setBankId);
|
Optional.ofNullable(bankId).ifPresent(builder::setBankId);
|
||||||
Optional.ofNullable(acceptedBankIds).ifPresent(builder::addAllAcceptedBankIds);
|
Optional.ofNullable(acceptedBankIds).ifPresent(builder::addAllAcceptedBankIds);
|
||||||
Optional.ofNullable(acceptedCountryCodes).ifPresent(builder::addAllAcceptedCountryCodes);
|
Optional.ofNullable(acceptedCountryCodes).ifPresent(builder::addAllAcceptedCountryCodes);
|
||||||
Optional.ofNullable(hashOfChallenge).ifPresent(builder::setHashOfChallenge);
|
Optional.ofNullable(challengeHash).ifPresent(builder::setChallengeHash);
|
||||||
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
|
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
|
||||||
Optional.ofNullable(arbitratorSigner).ifPresent(e -> builder.setArbitratorSigner(arbitratorSigner.toProtoMessage()));
|
Optional.ofNullable(arbitratorSigner).ifPresent(e -> builder.setArbitratorSigner(arbitratorSigner.toProtoMessage()));
|
||||||
Optional.ofNullable(arbitratorSignature).ifPresent(e -> builder.setArbitratorSignature(ByteString.copyFrom(e)));
|
Optional.ofNullable(arbitratorSignature).ifPresent(e -> builder.setArbitratorSignature(ByteString.copyFrom(e)));
|
||||||
@ -392,7 +397,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
null : new ArrayList<>(proto.getAcceptedCountryCodesList());
|
null : new ArrayList<>(proto.getAcceptedCountryCodesList());
|
||||||
List<String> reserveTxKeyImages = proto.getReserveTxKeyImagesList().isEmpty() ?
|
List<String> reserveTxKeyImages = proto.getReserveTxKeyImagesList().isEmpty() ?
|
||||||
null : new ArrayList<>(proto.getReserveTxKeyImagesList());
|
null : new ArrayList<>(proto.getReserveTxKeyImagesList());
|
||||||
String hashOfChallenge = ProtoUtil.stringOrNullFromProto(proto.getHashOfChallenge());
|
String challengeHash = ProtoUtil.stringOrNullFromProto(proto.getChallengeHash());
|
||||||
Map<String, String> extraDataMapMap = CollectionUtils.isEmpty(proto.getExtraDataMap()) ?
|
Map<String, String> extraDataMapMap = CollectionUtils.isEmpty(proto.getExtraDataMap()) ?
|
||||||
null : proto.getExtraDataMap();
|
null : proto.getExtraDataMap();
|
||||||
|
|
||||||
@ -428,7 +433,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
proto.getLowerClosePrice(),
|
proto.getLowerClosePrice(),
|
||||||
proto.getUpperClosePrice(),
|
proto.getUpperClosePrice(),
|
||||||
proto.getIsPrivateOffer(),
|
proto.getIsPrivateOffer(),
|
||||||
hashOfChallenge,
|
challengeHash,
|
||||||
extraDataMapMap,
|
extraDataMapMap,
|
||||||
proto.getProtocolVersion(),
|
proto.getProtocolVersion(),
|
||||||
proto.hasArbitratorSigner() ? NodeAddress.fromProto(proto.getArbitratorSigner()) : null,
|
proto.hasArbitratorSigner() ? NodeAddress.fromProto(proto.getArbitratorSigner()) : null,
|
||||||
@ -475,7 +480,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
",\r\n lowerClosePrice=" + lowerClosePrice +
|
",\r\n lowerClosePrice=" + lowerClosePrice +
|
||||||
",\r\n upperClosePrice=" + upperClosePrice +
|
",\r\n upperClosePrice=" + upperClosePrice +
|
||||||
",\r\n isPrivateOffer=" + isPrivateOffer +
|
",\r\n isPrivateOffer=" + isPrivateOffer +
|
||||||
",\r\n hashOfChallenge='" + hashOfChallenge + '\'' +
|
",\r\n challengeHash='" + challengeHash + '\'' +
|
||||||
",\r\n arbitratorSigner=" + arbitratorSigner +
|
",\r\n arbitratorSigner=" + arbitratorSigner +
|
||||||
",\r\n arbitratorSignature=" + Utilities.bytesAsHexString(arbitratorSignature) +
|
",\r\n arbitratorSignature=" + Utilities.bytesAsHexString(arbitratorSignature) +
|
||||||
"\r\n} ";
|
"\r\n} ";
|
||||||
|
@ -58,8 +58,8 @@ import haveno.core.trade.statistics.ReferralIdService;
|
|||||||
import haveno.core.user.AutoConfirmSettings;
|
import haveno.core.user.AutoConfirmSettings;
|
||||||
import haveno.core.user.Preferences;
|
import haveno.core.user.Preferences;
|
||||||
import haveno.core.util.coin.CoinFormatter;
|
import haveno.core.util.coin.CoinFormatter;
|
||||||
import static haveno.core.xmr.wallet.Restrictions.getMaxBuyerSecurityDepositAsPercent;
|
import static haveno.core.xmr.wallet.Restrictions.getMaxSecurityDepositAsPercent;
|
||||||
import static haveno.core.xmr.wallet.Restrictions.getMinBuyerSecurityDepositAsPercent;
|
import static haveno.core.xmr.wallet.Restrictions.getMinSecurityDepositAsPercent;
|
||||||
import haveno.network.p2p.P2PService;
|
import haveno.network.p2p.P2PService;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -120,9 +120,10 @@ public class OfferUtil {
|
|||||||
|
|
||||||
public long getMaxTradeLimit(PaymentAccount paymentAccount,
|
public long getMaxTradeLimit(PaymentAccount paymentAccount,
|
||||||
String currencyCode,
|
String currencyCode,
|
||||||
OfferDirection direction) {
|
OfferDirection direction,
|
||||||
|
boolean buyerAsTakerWithoutDeposit) {
|
||||||
return paymentAccount != null
|
return paymentAccount != null
|
||||||
? accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode, direction)
|
? accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode, direction, buyerAsTakerWithoutDeposit)
|
||||||
: 0;
|
: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,16 +229,16 @@ public class OfferUtil {
|
|||||||
return extraDataMap.isEmpty() ? null : extraDataMap;
|
return extraDataMap.isEmpty() ? null : extraDataMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void validateOfferData(double buyerSecurityDeposit,
|
public void validateOfferData(double securityDeposit,
|
||||||
PaymentAccount paymentAccount,
|
PaymentAccount paymentAccount,
|
||||||
String currencyCode) {
|
String currencyCode) {
|
||||||
checkNotNull(p2PService.getAddress(), "Address must not be null");
|
checkNotNull(p2PService.getAddress(), "Address must not be null");
|
||||||
checkArgument(buyerSecurityDeposit <= getMaxBuyerSecurityDepositAsPercent(),
|
checkArgument(securityDeposit <= getMaxSecurityDepositAsPercent(),
|
||||||
"securityDeposit must not exceed " +
|
"securityDeposit must not exceed " +
|
||||||
getMaxBuyerSecurityDepositAsPercent());
|
getMaxSecurityDepositAsPercent());
|
||||||
checkArgument(buyerSecurityDeposit >= getMinBuyerSecurityDepositAsPercent(),
|
checkArgument(securityDeposit >= getMinSecurityDepositAsPercent(),
|
||||||
"securityDeposit must not be less than " +
|
"securityDeposit must not be less than " +
|
||||||
getMinBuyerSecurityDepositAsPercent() + " but was " + buyerSecurityDeposit);
|
getMinSecurityDepositAsPercent() + " but was " + securityDeposit);
|
||||||
checkArgument(!filterManager.isCurrencyBanned(currencyCode),
|
checkArgument(!filterManager.isCurrencyBanned(currencyCode),
|
||||||
Res.get("offerbook.warning.currencyBanned"));
|
Res.get("offerbook.warning.currencyBanned"));
|
||||||
checkArgument(!filterManager.isPaymentMethodBanned(paymentAccount.getPaymentMethod()),
|
checkArgument(!filterManager.isPaymentMethodBanned(paymentAccount.getPaymentMethod()),
|
||||||
|
@ -96,6 +96,9 @@ public final class OpenOffer implements Tradable {
|
|||||||
@Getter
|
@Getter
|
||||||
private String reserveTxKey;
|
private String reserveTxKey;
|
||||||
@Getter
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private String challenge;
|
||||||
|
@Getter
|
||||||
private final long triggerPrice;
|
private final long triggerPrice;
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@ -107,7 +110,6 @@ public final class OpenOffer implements Tradable {
|
|||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
transient int numProcessingAttempts = 0;
|
transient int numProcessingAttempts = 0;
|
||||||
|
|
||||||
public OpenOffer(Offer offer) {
|
public OpenOffer(Offer offer) {
|
||||||
this(offer, 0, false);
|
this(offer, 0, false);
|
||||||
}
|
}
|
||||||
@ -120,6 +122,7 @@ public final class OpenOffer implements Tradable {
|
|||||||
this.offer = offer;
|
this.offer = offer;
|
||||||
this.triggerPrice = triggerPrice;
|
this.triggerPrice = triggerPrice;
|
||||||
this.reserveExactAmount = reserveExactAmount;
|
this.reserveExactAmount = reserveExactAmount;
|
||||||
|
this.challenge = offer.getChallenge();
|
||||||
state = State.PENDING;
|
state = State.PENDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,6 +140,7 @@ public final class OpenOffer implements Tradable {
|
|||||||
this.reserveTxHash = openOffer.reserveTxHash;
|
this.reserveTxHash = openOffer.reserveTxHash;
|
||||||
this.reserveTxHex = openOffer.reserveTxHex;
|
this.reserveTxHex = openOffer.reserveTxHex;
|
||||||
this.reserveTxKey = openOffer.reserveTxKey;
|
this.reserveTxKey = openOffer.reserveTxKey;
|
||||||
|
this.challenge = openOffer.challenge;
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -153,7 +157,8 @@ public final class OpenOffer implements Tradable {
|
|||||||
long splitOutputTxFee,
|
long splitOutputTxFee,
|
||||||
@Nullable String reserveTxHash,
|
@Nullable String reserveTxHash,
|
||||||
@Nullable String reserveTxHex,
|
@Nullable String reserveTxHex,
|
||||||
@Nullable String reserveTxKey) {
|
@Nullable String reserveTxKey,
|
||||||
|
@Nullable String challenge) {
|
||||||
this.offer = offer;
|
this.offer = offer;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.triggerPrice = triggerPrice;
|
this.triggerPrice = triggerPrice;
|
||||||
@ -164,6 +169,7 @@ public final class OpenOffer implements Tradable {
|
|||||||
this.reserveTxHash = reserveTxHash;
|
this.reserveTxHash = reserveTxHash;
|
||||||
this.reserveTxHex = reserveTxHex;
|
this.reserveTxHex = reserveTxHex;
|
||||||
this.reserveTxKey = reserveTxKey;
|
this.reserveTxKey = reserveTxKey;
|
||||||
|
this.challenge = challenge;
|
||||||
|
|
||||||
// reset reserved state to available
|
// reset reserved state to available
|
||||||
if (this.state == State.RESERVED) setState(State.AVAILABLE);
|
if (this.state == State.RESERVED) setState(State.AVAILABLE);
|
||||||
@ -184,6 +190,7 @@ public final class OpenOffer implements Tradable {
|
|||||||
Optional.ofNullable(reserveTxHash).ifPresent(e -> builder.setReserveTxHash(reserveTxHash));
|
Optional.ofNullable(reserveTxHash).ifPresent(e -> builder.setReserveTxHash(reserveTxHash));
|
||||||
Optional.ofNullable(reserveTxHex).ifPresent(e -> builder.setReserveTxHex(reserveTxHex));
|
Optional.ofNullable(reserveTxHex).ifPresent(e -> builder.setReserveTxHex(reserveTxHex));
|
||||||
Optional.ofNullable(reserveTxKey).ifPresent(e -> builder.setReserveTxKey(reserveTxKey));
|
Optional.ofNullable(reserveTxKey).ifPresent(e -> builder.setReserveTxKey(reserveTxKey));
|
||||||
|
Optional.ofNullable(challenge).ifPresent(e -> builder.setChallenge(challenge));
|
||||||
|
|
||||||
return protobuf.Tradable.newBuilder().setOpenOffer(builder).build();
|
return protobuf.Tradable.newBuilder().setOpenOffer(builder).build();
|
||||||
}
|
}
|
||||||
@ -199,7 +206,8 @@ public final class OpenOffer implements Tradable {
|
|||||||
proto.getSplitOutputTxFee(),
|
proto.getSplitOutputTxFee(),
|
||||||
ProtoUtil.stringOrNullFromProto(proto.getReserveTxHash()),
|
ProtoUtil.stringOrNullFromProto(proto.getReserveTxHash()),
|
||||||
ProtoUtil.stringOrNullFromProto(proto.getReserveTxHex()),
|
ProtoUtil.stringOrNullFromProto(proto.getReserveTxHex()),
|
||||||
ProtoUtil.stringOrNullFromProto(proto.getReserveTxKey()));
|
ProtoUtil.stringOrNullFromProto(proto.getReserveTxKey()),
|
||||||
|
ProtoUtil.stringOrNullFromProto(proto.getChallenge()));
|
||||||
return openOffer;
|
return openOffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +79,7 @@ import haveno.core.util.JsonUtil;
|
|||||||
import haveno.core.util.Validator;
|
import haveno.core.util.Validator;
|
||||||
import haveno.core.xmr.model.XmrAddressEntry;
|
import haveno.core.xmr.model.XmrAddressEntry;
|
||||||
import haveno.core.xmr.wallet.BtcWalletService;
|
import haveno.core.xmr.wallet.BtcWalletService;
|
||||||
|
import haveno.core.xmr.wallet.Restrictions;
|
||||||
import haveno.core.xmr.wallet.XmrKeyImageListener;
|
import haveno.core.xmr.wallet.XmrKeyImageListener;
|
||||||
import haveno.core.xmr.wallet.XmrKeyImagePoller;
|
import haveno.core.xmr.wallet.XmrKeyImagePoller;
|
||||||
import haveno.core.xmr.wallet.TradeWalletService;
|
import haveno.core.xmr.wallet.TradeWalletService;
|
||||||
@ -1307,7 +1308,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
NodeAddress thisAddress = p2PService.getNetworkNode().getNodeAddress();
|
NodeAddress thisAddress = p2PService.getNetworkNode().getNodeAddress();
|
||||||
if (thisArbitrator == null || !thisArbitrator.getNodeAddress().equals(thisAddress)) {
|
if (thisArbitrator == null || !thisArbitrator.getNodeAddress().equals(thisAddress)) {
|
||||||
errorMessage = "Cannot sign offer because we are not a registered arbitrator";
|
errorMessage = "Cannot sign offer because we are not a registered arbitrator";
|
||||||
log.info(errorMessage);
|
log.warn(errorMessage);
|
||||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1315,47 +1316,109 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
// verify arbitrator is signer of offer payload
|
// verify arbitrator is signer of offer payload
|
||||||
if (!thisAddress.equals(request.getOfferPayload().getArbitratorSigner())) {
|
if (!thisAddress.equals(request.getOfferPayload().getArbitratorSigner())) {
|
||||||
errorMessage = "Cannot sign offer because offer payload is for a different arbitrator";
|
errorMessage = "Cannot sign offer because offer payload is for a different arbitrator";
|
||||||
log.info(errorMessage);
|
log.warn(errorMessage);
|
||||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify maker's trade fee
|
// private offers must have challenge hash
|
||||||
Offer offer = new Offer(request.getOfferPayload());
|
Offer offer = new Offer(request.getOfferPayload());
|
||||||
|
if (offer.isPrivateOffer() && (offer.getChallengeHash() == null || offer.getChallengeHash().length() == 0)) {
|
||||||
|
errorMessage = "Private offer must have challenge hash for offer " + request.offerId;
|
||||||
|
log.warn(errorMessage);
|
||||||
|
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify maker and taker fees
|
||||||
|
boolean hasBuyerAsTakerWithoutDeposit = offer.getDirection() == OfferDirection.SELL && offer.isPrivateOffer() && offer.getChallengeHash() != null && offer.getChallengeHash().length() > 0 && offer.getTakerFeePct() == 0;
|
||||||
|
if (hasBuyerAsTakerWithoutDeposit) {
|
||||||
|
|
||||||
|
// verify maker's trade fee
|
||||||
|
if (offer.getMakerFeePct() != HavenoUtils.MAKER_FEE_FOR_TAKER_WITHOUT_DEPOSIT_PCT) {
|
||||||
|
errorMessage = "Wrong maker fee for offer " + request.offerId;
|
||||||
|
log.warn(errorMessage);
|
||||||
|
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify taker's trade fee
|
||||||
|
if (offer.getTakerFeePct() != 0) {
|
||||||
|
errorMessage = "Wrong taker fee for offer " + request.offerId + ". Expected 0 but got " + offer.getTakerFeePct();
|
||||||
|
log.warn(errorMessage);
|
||||||
|
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify maker security deposit
|
||||||
|
if (offer.getSellerSecurityDepositPct() != Restrictions.MIN_SECURITY_DEPOSIT_PCT) {
|
||||||
|
errorMessage = "Wrong seller security deposit for offer " + request.offerId + ". Expected " + Restrictions.MIN_SECURITY_DEPOSIT_PCT + " but got " + offer.getSellerSecurityDepositPct();
|
||||||
|
log.warn(errorMessage);
|
||||||
|
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify taker's security deposit
|
||||||
|
if (offer.getBuyerSecurityDepositPct() != 0) {
|
||||||
|
errorMessage = "Wrong buyer security deposit for offer " + request.offerId + ". Expected 0 but got " + offer.getBuyerSecurityDepositPct();
|
||||||
|
log.warn(errorMessage);
|
||||||
|
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// verify maker's trade fee
|
||||||
if (offer.getMakerFeePct() != HavenoUtils.MAKER_FEE_PCT) {
|
if (offer.getMakerFeePct() != HavenoUtils.MAKER_FEE_PCT) {
|
||||||
errorMessage = "Wrong maker fee for offer " + request.offerId;
|
errorMessage = "Wrong maker fee for offer " + request.offerId;
|
||||||
log.info(errorMessage);
|
log.warn(errorMessage);
|
||||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify taker's trade fee
|
// verify taker's trade fee
|
||||||
if (offer.getTakerFeePct() != HavenoUtils.TAKER_FEE_PCT) {
|
if (offer.getTakerFeePct() != HavenoUtils.TAKER_FEE_PCT) {
|
||||||
errorMessage = "Wrong taker fee for offer " + request.offerId;
|
errorMessage = "Wrong taker fee for offer " + request.offerId + ". Expected " + HavenoUtils.TAKER_FEE_PCT + " but got " + offer.getTakerFeePct();
|
||||||
log.info(errorMessage);
|
log.warn(errorMessage);
|
||||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// verify seller's security deposit
|
||||||
|
if (offer.getSellerSecurityDepositPct() < Restrictions.MIN_SECURITY_DEPOSIT_PCT) {
|
||||||
|
errorMessage = "Insufficient seller security deposit for offer " + request.offerId + ". Expected at least " + Restrictions.MIN_SECURITY_DEPOSIT_PCT + " but got " + offer.getSellerSecurityDepositPct();
|
||||||
|
log.warn(errorMessage);
|
||||||
|
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify buyer's security deposit
|
||||||
|
if (offer.getBuyerSecurityDepositPct() < Restrictions.MIN_SECURITY_DEPOSIT_PCT) {
|
||||||
|
errorMessage = "Insufficient buyer security deposit for offer " + request.offerId + ". Expected at least " + Restrictions.MIN_SECURITY_DEPOSIT_PCT + " but got " + offer.getBuyerSecurityDepositPct();
|
||||||
|
log.warn(errorMessage);
|
||||||
|
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// security deposits must be equal
|
||||||
|
if (offer.getBuyerSecurityDepositPct() != offer.getSellerSecurityDepositPct()) {
|
||||||
|
errorMessage = "Buyer and seller security deposits are not equal for offer " + request.offerId + ": " + offer.getSellerSecurityDepositPct() + " vs " + offer.getBuyerSecurityDepositPct();
|
||||||
|
log.warn(errorMessage);
|
||||||
|
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// verify penalty fee
|
// verify penalty fee
|
||||||
if (offer.getPenaltyFeePct() != HavenoUtils.PENALTY_FEE_PCT) {
|
if (offer.getPenaltyFeePct() != HavenoUtils.PENALTY_FEE_PCT) {
|
||||||
errorMessage = "Wrong penalty fee for offer " + request.offerId;
|
errorMessage = "Wrong penalty fee for offer " + request.offerId;
|
||||||
log.info(errorMessage);
|
log.warn(errorMessage);
|
||||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify security deposits are equal
|
|
||||||
if (offer.getBuyerSecurityDepositPct() != offer.getSellerSecurityDepositPct()) {
|
|
||||||
errorMessage = "Buyer and seller security deposits are not equal for offer " + request.offerId;
|
|
||||||
log.info(errorMessage);
|
|
||||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify maker's reserve tx (double spend, trade fee, trade amount, mining fee)
|
// verify maker's reserve tx (double spend, trade fee, trade amount, mining fee)
|
||||||
BigInteger penaltyFee = HavenoUtils.multiply(offer.getAmount(), HavenoUtils.PENALTY_FEE_PCT);
|
BigInteger penaltyFee = HavenoUtils.multiply(offer.getAmount(), HavenoUtils.PENALTY_FEE_PCT);
|
||||||
BigInteger maxTradeFee = HavenoUtils.multiply(offer.getAmount(), HavenoUtils.MAKER_FEE_PCT);
|
BigInteger maxTradeFee = HavenoUtils.multiply(offer.getAmount(), hasBuyerAsTakerWithoutDeposit ? HavenoUtils.MAKER_FEE_FOR_TAKER_WITHOUT_DEPOSIT_PCT : HavenoUtils.MAKER_FEE_PCT);
|
||||||
BigInteger sendTradeAmount = offer.getDirection() == OfferDirection.BUY ? BigInteger.ZERO : offer.getAmount();
|
BigInteger sendTradeAmount = offer.getDirection() == OfferDirection.BUY ? BigInteger.ZERO : offer.getAmount();
|
||||||
BigInteger securityDeposit = offer.getDirection() == OfferDirection.BUY ? offer.getMaxBuyerSecurityDeposit() : offer.getMaxSellerSecurityDeposit();
|
BigInteger securityDeposit = offer.getDirection() == OfferDirection.BUY ? offer.getMaxBuyerSecurityDeposit() : offer.getMaxSellerSecurityDeposit();
|
||||||
MoneroTx verifiedTx = xmrWalletService.verifyReserveTx(
|
MoneroTx verifiedTx = xmrWalletService.verifyReserveTx(
|
||||||
@ -1710,7 +1773,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
originalOfferPayload.getLowerClosePrice(),
|
originalOfferPayload.getLowerClosePrice(),
|
||||||
originalOfferPayload.getUpperClosePrice(),
|
originalOfferPayload.getUpperClosePrice(),
|
||||||
originalOfferPayload.isPrivateOffer(),
|
originalOfferPayload.isPrivateOffer(),
|
||||||
originalOfferPayload.getHashOfChallenge(),
|
originalOfferPayload.getChallengeHash(),
|
||||||
updatedExtraDataMap,
|
updatedExtraDataMap,
|
||||||
protocolVersion,
|
protocolVersion,
|
||||||
originalOfferPayload.getArbitratorSigner(),
|
originalOfferPayload.getArbitratorSigner(),
|
||||||
|
@ -88,7 +88,8 @@ public class SendOfferAvailabilityRequest extends Task<OfferAvailabilityModel> {
|
|||||||
null, // reserve tx not sent from taker to maker
|
null, // reserve tx not sent from taker to maker
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
payoutAddress);
|
payoutAddress,
|
||||||
|
null); // challenge is required when offer taken
|
||||||
|
|
||||||
// save trade request to later send to arbitrator
|
// save trade request to later send to arbitrator
|
||||||
model.setTradeRequest(tradeRequest);
|
model.setTradeRequest(tradeRequest);
|
||||||
|
@ -21,6 +21,7 @@ import haveno.common.taskrunner.Task;
|
|||||||
import haveno.common.taskrunner.TaskRunner;
|
import haveno.common.taskrunner.TaskRunner;
|
||||||
import haveno.core.account.witness.AccountAgeWitnessService;
|
import haveno.core.account.witness.AccountAgeWitnessService;
|
||||||
import haveno.core.offer.Offer;
|
import haveno.core.offer.Offer;
|
||||||
|
import haveno.core.offer.OfferDirection;
|
||||||
import haveno.core.offer.placeoffer.PlaceOfferModel;
|
import haveno.core.offer.placeoffer.PlaceOfferModel;
|
||||||
import haveno.core.trade.HavenoUtils;
|
import haveno.core.trade.HavenoUtils;
|
||||||
import haveno.core.trade.messages.TradeMessage;
|
import haveno.core.trade.messages.TradeMessage;
|
||||||
@ -63,8 +64,21 @@ public class ValidateOffer extends Task<PlaceOfferModel> {
|
|||||||
checkBINotNullOrZero(offer.getMaxTradeLimit(), "MaxTradeLimit");
|
checkBINotNullOrZero(offer.getMaxTradeLimit(), "MaxTradeLimit");
|
||||||
if (offer.getMakerFeePct() < 0) throw new IllegalArgumentException("Maker fee must be >= 0% but was " + offer.getMakerFeePct());
|
if (offer.getMakerFeePct() < 0) throw new IllegalArgumentException("Maker fee must be >= 0% but was " + offer.getMakerFeePct());
|
||||||
if (offer.getTakerFeePct() < 0) throw new IllegalArgumentException("Taker fee must be >= 0% but was " + offer.getTakerFeePct());
|
if (offer.getTakerFeePct() < 0) throw new IllegalArgumentException("Taker fee must be >= 0% but was " + offer.getTakerFeePct());
|
||||||
|
offer.isPrivateOffer();
|
||||||
|
if (offer.isPrivateOffer()) {
|
||||||
|
boolean isBuyerMaker = offer.getDirection() == OfferDirection.BUY;
|
||||||
|
if (isBuyerMaker) {
|
||||||
|
if (offer.getBuyerSecurityDepositPct() <= 0) throw new IllegalArgumentException("Buyer security deposit percent must be positive but was " + offer.getBuyerSecurityDepositPct());
|
||||||
|
if (offer.getSellerSecurityDepositPct() < 0) throw new IllegalArgumentException("Seller security deposit percent must be >= 0% but was " + offer.getSellerSecurityDepositPct());
|
||||||
|
} else {
|
||||||
|
if (offer.getBuyerSecurityDepositPct() < 0) throw new IllegalArgumentException("Buyer security deposit percent must be >= 0% but was " + offer.getBuyerSecurityDepositPct());
|
||||||
|
if (offer.getSellerSecurityDepositPct() <= 0) throw new IllegalArgumentException("Seller security deposit percent must be positive but was " + offer.getSellerSecurityDepositPct());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (offer.getBuyerSecurityDepositPct() <= 0) throw new IllegalArgumentException("Buyer security deposit percent must be positive but was " + offer.getBuyerSecurityDepositPct());
|
if (offer.getBuyerSecurityDepositPct() <= 0) throw new IllegalArgumentException("Buyer security deposit percent must be positive but was " + offer.getBuyerSecurityDepositPct());
|
||||||
if (offer.getSellerSecurityDepositPct() <= 0) throw new IllegalArgumentException("Seller security deposit percent must be positive but was " + offer.getSellerSecurityDepositPct());
|
if (offer.getSellerSecurityDepositPct() <= 0) throw new IllegalArgumentException("Seller security deposit percent must be positive but was " + offer.getSellerSecurityDepositPct());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// We remove those checks to be more flexible with future changes.
|
// We remove those checks to be more flexible with future changes.
|
||||||
/*checkArgument(offer.getMakerFee().value >= FeeService.getMinMakerFee(offer.isCurrencyForMakerFeeBtc()).value,
|
/*checkArgument(offer.getMakerFee().value >= FeeService.getMinMakerFee(offer.isCurrencyForMakerFeeBtc()).value,
|
||||||
@ -82,9 +96,9 @@ public class ValidateOffer extends Task<PlaceOfferModel> {
|
|||||||
/*checkArgument(offer.getMinAmount().compareTo(ProposalConsensus.getMinTradeAmount()) >= 0,
|
/*checkArgument(offer.getMinAmount().compareTo(ProposalConsensus.getMinTradeAmount()) >= 0,
|
||||||
"MinAmount is less than " + ProposalConsensus.getMinTradeAmount().toFriendlyString());*/
|
"MinAmount is less than " + ProposalConsensus.getMinTradeAmount().toFriendlyString());*/
|
||||||
|
|
||||||
long maxAmount = accountAgeWitnessService.getMyTradeLimit(user.getPaymentAccount(offer.getMakerPaymentAccountId()), offer.getCurrencyCode(), offer.getDirection());
|
long maxAmount = accountAgeWitnessService.getMyTradeLimit(user.getPaymentAccount(offer.getMakerPaymentAccountId()), offer.getCurrencyCode(), offer.getDirection(), offer.hasBuyerAsTakerWithoutDeposit());
|
||||||
checkArgument(offer.getAmount().longValueExact() <= maxAmount,
|
checkArgument(offer.getAmount().longValueExact() <= maxAmount,
|
||||||
"Amount is larger than " + HavenoUtils.atomicUnitsToXmr(offer.getPaymentMethod().getMaxTradeLimit(offer.getCurrencyCode())) + " XMR");
|
"Amount is larger than " + HavenoUtils.atomicUnitsToXmr(maxAmount) + " XMR");
|
||||||
checkArgument(offer.getAmount().compareTo(offer.getMinAmount()) >= 0, "MinAmount is larger than Amount");
|
checkArgument(offer.getAmount().compareTo(offer.getMinAmount()) >= 0, "MinAmount is larger than Amount");
|
||||||
|
|
||||||
checkNotNull(offer.getPrice(), "Price is null");
|
checkNotNull(offer.getPrice(), "Price is null");
|
||||||
|
@ -148,7 +148,8 @@ public class TakeOfferModel implements Model {
|
|||||||
private long getMaxTradeLimit() {
|
private long getMaxTradeLimit() {
|
||||||
return accountAgeWitnessService.getMyTradeLimit(paymentAccount,
|
return accountAgeWitnessService.getMyTradeLimit(paymentAccount,
|
||||||
offer.getCurrencyCode(),
|
offer.getCurrencyCode(),
|
||||||
offer.getMirroredDirection());
|
offer.getMirroredDirection(),
|
||||||
|
offer.hasBuyerAsTakerWithoutDeposit());
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
@ -124,7 +124,7 @@ public class PaymentAccountUtil {
|
|||||||
AccountAgeWitnessService accountAgeWitnessService) {
|
AccountAgeWitnessService accountAgeWitnessService) {
|
||||||
boolean hasChargebackRisk = hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode());
|
boolean hasChargebackRisk = hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode());
|
||||||
boolean hasValidAccountAgeWitness = accountAgeWitnessService.getMyTradeLimit(paymentAccount,
|
boolean hasValidAccountAgeWitness = accountAgeWitnessService.getMyTradeLimit(paymentAccount,
|
||||||
offer.getCurrencyCode(), offer.getMirroredDirection()) >= offer.getMinAmount().longValueExact();
|
offer.getCurrencyCode(), offer.getMirroredDirection(), offer.hasBuyerAsTakerWithoutDeposit()) >= offer.getMinAmount().longValueExact();
|
||||||
return !hasChargebackRisk || hasValidAccountAgeWitness;
|
return !hasChargebackRisk || hasValidAccountAgeWitness;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,8 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
@Singleton
|
@Singleton
|
||||||
public class TradeLimits {
|
public class TradeLimits {
|
||||||
private static final BigInteger MAX_TRADE_LIMIT = HavenoUtils.xmrToAtomicUnits(528); // max trade limit for lowest risk payment method. Others will get derived from that.
|
private static final BigInteger MAX_TRADE_LIMIT = HavenoUtils.xmrToAtomicUnits(528); // max trade limit for lowest risk payment method. Others will get derived from that.
|
||||||
|
private static final BigInteger MAX_TRADE_LIMIT_WITHOUT_BUYER_AS_TAKER_DEPOSIT = HavenoUtils.xmrToAtomicUnits(1); // max trade limit without deposit from buyer
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Getter
|
@Getter
|
||||||
private static TradeLimits INSTANCE;
|
private static TradeLimits INSTANCE;
|
||||||
@ -57,6 +59,15 @@ public class TradeLimits {
|
|||||||
return MAX_TRADE_LIMIT;
|
return MAX_TRADE_LIMIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum trade limit without a buyer deposit.
|
||||||
|
*
|
||||||
|
* @return the maximum trade limit for a buyer without a deposit
|
||||||
|
*/
|
||||||
|
public BigInteger getMaxTradeLimitBuyerAsTakerWithoutDeposit() {
|
||||||
|
return MAX_TRADE_LIMIT_WITHOUT_BUYER_AS_TAKER_DEPOSIT;
|
||||||
|
}
|
||||||
|
|
||||||
// We possibly rounded value for the first month gets multiplied by 4 to get the trade limit after the account
|
// We possibly rounded value for the first month gets multiplied by 4 to get the trade limit after the account
|
||||||
// age witness is not considered anymore (> 2 months).
|
// age witness is not considered anymore (> 2 months).
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ public class SecurityDepositValidator extends NumberValidator {
|
|||||||
private ValidationResult validateIfNotTooLowPercentageValue(String input) {
|
private ValidationResult validateIfNotTooLowPercentageValue(String input) {
|
||||||
try {
|
try {
|
||||||
double percentage = ParsingUtils.parsePercentStringToDouble(input);
|
double percentage = ParsingUtils.parsePercentStringToDouble(input);
|
||||||
double minPercentage = Restrictions.getMinBuyerSecurityDepositAsPercent();
|
double minPercentage = Restrictions.getMinSecurityDepositAsPercent();
|
||||||
if (percentage < minPercentage)
|
if (percentage < minPercentage)
|
||||||
return new ValidationResult(false,
|
return new ValidationResult(false,
|
||||||
Res.get("validation.inputTooSmall", FormattingUtils.formatToPercentWithSymbol(minPercentage)));
|
Res.get("validation.inputTooSmall", FormattingUtils.formatToPercentWithSymbol(minPercentage)));
|
||||||
@ -73,7 +73,7 @@ public class SecurityDepositValidator extends NumberValidator {
|
|||||||
private ValidationResult validateIfNotTooHighPercentageValue(String input) {
|
private ValidationResult validateIfNotTooHighPercentageValue(String input) {
|
||||||
try {
|
try {
|
||||||
double percentage = ParsingUtils.parsePercentStringToDouble(input);
|
double percentage = ParsingUtils.parsePercentStringToDouble(input);
|
||||||
double maxPercentage = Restrictions.getMaxBuyerSecurityDepositAsPercent();
|
double maxPercentage = Restrictions.getMaxSecurityDepositAsPercent();
|
||||||
if (percentage > maxPercentage)
|
if (percentage > maxPercentage)
|
||||||
return new ValidationResult(false,
|
return new ValidationResult(false,
|
||||||
Res.get("validation.inputTooLarge", FormattingUtils.formatToPercentWithSymbol(maxPercentage)));
|
Res.get("validation.inputTooLarge", FormattingUtils.formatToPercentWithSymbol(maxPercentage)));
|
||||||
|
@ -28,6 +28,8 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trade in the context of an arbitrator.
|
* Trade in the context of an arbitrator.
|
||||||
*/
|
*/
|
||||||
@ -42,8 +44,9 @@ public class ArbitratorTrade extends Trade {
|
|||||||
String uid,
|
String uid,
|
||||||
NodeAddress makerNodeAddress,
|
NodeAddress makerNodeAddress,
|
||||||
NodeAddress takerNodeAddress,
|
NodeAddress takerNodeAddress,
|
||||||
NodeAddress arbitratorNodeAddress) {
|
NodeAddress arbitratorNodeAddress,
|
||||||
super(offer, tradeAmount, tradePrice, xmrWalletService, processModel, uid, makerNodeAddress, takerNodeAddress, arbitratorNodeAddress);
|
@Nullable String challenge) {
|
||||||
|
super(offer, tradeAmount, tradePrice, xmrWalletService, processModel, uid, makerNodeAddress, takerNodeAddress, arbitratorNodeAddress, challenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -81,7 +84,8 @@ public class ArbitratorTrade extends Trade {
|
|||||||
uid,
|
uid,
|
||||||
proto.getProcessModel().getMaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getMaker().getNodeAddress()) : null,
|
proto.getProcessModel().getMaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getMaker().getNodeAddress()) : null,
|
||||||
proto.getProcessModel().getTaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getTaker().getNodeAddress()) : null,
|
proto.getProcessModel().getTaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getTaker().getNodeAddress()) : null,
|
||||||
proto.getProcessModel().getArbitrator().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getArbitrator().getNodeAddress()) : null),
|
proto.getProcessModel().getArbitrator().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getArbitrator().getNodeAddress()) : null,
|
||||||
|
ProtoUtil.stringOrNullFromProto(proto.getChallenge())),
|
||||||
proto,
|
proto,
|
||||||
coreProtoResolver);
|
coreProtoResolver);
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,8 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
|
public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
|
||||||
|
|
||||||
@ -43,7 +45,8 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
|
|||||||
String uid,
|
String uid,
|
||||||
NodeAddress makerNodeAddress,
|
NodeAddress makerNodeAddress,
|
||||||
NodeAddress takerNodeAddress,
|
NodeAddress takerNodeAddress,
|
||||||
NodeAddress arbitratorNodeAddress) {
|
NodeAddress arbitratorNodeAddress,
|
||||||
|
@Nullable String challenge) {
|
||||||
super(offer,
|
super(offer,
|
||||||
tradeAmount,
|
tradeAmount,
|
||||||
tradePrice,
|
tradePrice,
|
||||||
@ -52,7 +55,8 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
|
|||||||
uid,
|
uid,
|
||||||
makerNodeAddress,
|
makerNodeAddress,
|
||||||
takerNodeAddress,
|
takerNodeAddress,
|
||||||
arbitratorNodeAddress);
|
arbitratorNodeAddress,
|
||||||
|
challenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -85,7 +89,8 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
|
|||||||
uid,
|
uid,
|
||||||
proto.getProcessModel().getMaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getMaker().getNodeAddress()) : null,
|
proto.getProcessModel().getMaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getMaker().getNodeAddress()) : null,
|
||||||
proto.getProcessModel().getTaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getTaker().getNodeAddress()) : null,
|
proto.getProcessModel().getTaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getTaker().getNodeAddress()) : null,
|
||||||
proto.getProcessModel().getArbitrator().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getArbitrator().getNodeAddress()) : null);
|
proto.getProcessModel().getArbitrator().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getArbitrator().getNodeAddress()) : null,
|
||||||
|
ProtoUtil.stringOrNullFromProto(proto.getChallenge()));
|
||||||
|
|
||||||
trade.setPrice(proto.getPrice());
|
trade.setPrice(proto.getPrice());
|
||||||
|
|
||||||
|
@ -44,7 +44,8 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
|
|||||||
String uid,
|
String uid,
|
||||||
@Nullable NodeAddress makerNodeAddress,
|
@Nullable NodeAddress makerNodeAddress,
|
||||||
@Nullable NodeAddress takerNodeAddress,
|
@Nullable NodeAddress takerNodeAddress,
|
||||||
@Nullable NodeAddress arbitratorNodeAddress) {
|
@Nullable NodeAddress arbitratorNodeAddress,
|
||||||
|
@Nullable String challenge) {
|
||||||
super(offer,
|
super(offer,
|
||||||
tradeAmount,
|
tradeAmount,
|
||||||
tradePrice,
|
tradePrice,
|
||||||
@ -53,7 +54,8 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
|
|||||||
uid,
|
uid,
|
||||||
makerNodeAddress,
|
makerNodeAddress,
|
||||||
takerNodeAddress,
|
takerNodeAddress,
|
||||||
arbitratorNodeAddress);
|
arbitratorNodeAddress,
|
||||||
|
challenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -87,7 +89,8 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
|
|||||||
uid,
|
uid,
|
||||||
proto.getProcessModel().getMaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getMaker().getNodeAddress()) : null,
|
proto.getProcessModel().getMaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getMaker().getNodeAddress()) : null,
|
||||||
proto.getProcessModel().getTaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getTaker().getNodeAddress()) : null,
|
proto.getProcessModel().getTaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getTaker().getNodeAddress()) : null,
|
||||||
proto.getProcessModel().getArbitrator().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getArbitrator().getNodeAddress()) : null),
|
proto.getProcessModel().getArbitrator().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getArbitrator().getNodeAddress()) : null,
|
||||||
|
ProtoUtil.stringOrNullFromProto(proto.getChallenge())),
|
||||||
proto,
|
proto,
|
||||||
coreProtoResolver);
|
coreProtoResolver);
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,8 @@ public abstract class BuyerTrade extends Trade {
|
|||||||
String uid,
|
String uid,
|
||||||
@Nullable NodeAddress takerNodeAddress,
|
@Nullable NodeAddress takerNodeAddress,
|
||||||
@Nullable NodeAddress makerNodeAddress,
|
@Nullable NodeAddress makerNodeAddress,
|
||||||
@Nullable NodeAddress arbitratorNodeAddress) {
|
@Nullable NodeAddress arbitratorNodeAddress,
|
||||||
|
@Nullable String challenge) {
|
||||||
super(offer,
|
super(offer,
|
||||||
tradeAmount,
|
tradeAmount,
|
||||||
tradePrice,
|
tradePrice,
|
||||||
@ -47,7 +48,8 @@ public abstract class BuyerTrade extends Trade {
|
|||||||
uid,
|
uid,
|
||||||
takerNodeAddress,
|
takerNodeAddress,
|
||||||
makerNodeAddress,
|
makerNodeAddress,
|
||||||
arbitratorNodeAddress);
|
arbitratorNodeAddress,
|
||||||
|
challenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -36,6 +36,7 @@ package haveno.core.trade;
|
|||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import haveno.common.crypto.PubKeyRing;
|
import haveno.common.crypto.PubKeyRing;
|
||||||
|
import haveno.common.proto.ProtoUtil;
|
||||||
import haveno.common.proto.network.NetworkPayload;
|
import haveno.common.proto.network.NetworkPayload;
|
||||||
import haveno.common.util.JsonExclude;
|
import haveno.common.util.JsonExclude;
|
||||||
import haveno.common.util.Utilities;
|
import haveno.common.util.Utilities;
|
||||||
@ -53,6 +54,7 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
@ -79,6 +81,7 @@ public final class Contract implements NetworkPayload {
|
|||||||
private final String makerPayoutAddressString;
|
private final String makerPayoutAddressString;
|
||||||
private final String takerPayoutAddressString;
|
private final String takerPayoutAddressString;
|
||||||
private final String makerDepositTxHash;
|
private final String makerDepositTxHash;
|
||||||
|
@Nullable
|
||||||
private final String takerDepositTxHash;
|
private final String takerDepositTxHash;
|
||||||
|
|
||||||
public Contract(OfferPayload offerPayload,
|
public Contract(OfferPayload offerPayload,
|
||||||
@ -99,7 +102,7 @@ public final class Contract implements NetworkPayload {
|
|||||||
String makerPayoutAddressString,
|
String makerPayoutAddressString,
|
||||||
String takerPayoutAddressString,
|
String takerPayoutAddressString,
|
||||||
String makerDepositTxHash,
|
String makerDepositTxHash,
|
||||||
String takerDepositTxHash) {
|
@Nullable String takerDepositTxHash) {
|
||||||
this.offerPayload = offerPayload;
|
this.offerPayload = offerPayload;
|
||||||
this.tradeAmount = tradeAmount;
|
this.tradeAmount = tradeAmount;
|
||||||
this.tradePrice = tradePrice;
|
this.tradePrice = tradePrice;
|
||||||
@ -134,6 +137,31 @@ public final class Contract implements NetworkPayload {
|
|||||||
// PROTO BUFFER
|
// PROTO BUFFER
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public protobuf.Contract toProtoMessage() {
|
||||||
|
protobuf.Contract.Builder builder = protobuf.Contract.newBuilder()
|
||||||
|
.setOfferPayload(offerPayload.toProtoMessage().getOfferPayload())
|
||||||
|
.setTradeAmount(tradeAmount)
|
||||||
|
.setTradePrice(tradePrice)
|
||||||
|
.setBuyerNodeAddress(buyerNodeAddress.toProtoMessage())
|
||||||
|
.setSellerNodeAddress(sellerNodeAddress.toProtoMessage())
|
||||||
|
.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage())
|
||||||
|
.setIsBuyerMakerAndSellerTaker(isBuyerMakerAndSellerTaker)
|
||||||
|
.setMakerAccountId(makerAccountId)
|
||||||
|
.setTakerAccountId(takerAccountId)
|
||||||
|
.setMakerPaymentMethodId(makerPaymentMethodId)
|
||||||
|
.setTakerPaymentMethodId(takerPaymentMethodId)
|
||||||
|
.setMakerPaymentAccountPayloadHash(ByteString.copyFrom(makerPaymentAccountPayloadHash))
|
||||||
|
.setTakerPaymentAccountPayloadHash(ByteString.copyFrom(takerPaymentAccountPayloadHash))
|
||||||
|
.setMakerPubKeyRing(makerPubKeyRing.toProtoMessage())
|
||||||
|
.setTakerPubKeyRing(takerPubKeyRing.toProtoMessage())
|
||||||
|
.setMakerPayoutAddressString(makerPayoutAddressString)
|
||||||
|
.setTakerPayoutAddressString(takerPayoutAddressString)
|
||||||
|
.setMakerDepositTxHash(makerDepositTxHash);
|
||||||
|
Optional.ofNullable(takerDepositTxHash).ifPresent(builder::setTakerDepositTxHash);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
public static Contract fromProto(protobuf.Contract proto, CoreProtoResolver coreProtoResolver) {
|
public static Contract fromProto(protobuf.Contract proto, CoreProtoResolver coreProtoResolver) {
|
||||||
return new Contract(OfferPayload.fromProto(proto.getOfferPayload()),
|
return new Contract(OfferPayload.fromProto(proto.getOfferPayload()),
|
||||||
proto.getTradeAmount(),
|
proto.getTradeAmount(),
|
||||||
@ -153,32 +181,7 @@ public final class Contract implements NetworkPayload {
|
|||||||
proto.getMakerPayoutAddressString(),
|
proto.getMakerPayoutAddressString(),
|
||||||
proto.getTakerPayoutAddressString(),
|
proto.getTakerPayoutAddressString(),
|
||||||
proto.getMakerDepositTxHash(),
|
proto.getMakerDepositTxHash(),
|
||||||
proto.getTakerDepositTxHash());
|
ProtoUtil.stringOrNullFromProto(proto.getTakerDepositTxHash()));
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public protobuf.Contract toProtoMessage() {
|
|
||||||
return protobuf.Contract.newBuilder()
|
|
||||||
.setOfferPayload(offerPayload.toProtoMessage().getOfferPayload())
|
|
||||||
.setTradeAmount(tradeAmount)
|
|
||||||
.setTradePrice(tradePrice)
|
|
||||||
.setBuyerNodeAddress(buyerNodeAddress.toProtoMessage())
|
|
||||||
.setSellerNodeAddress(sellerNodeAddress.toProtoMessage())
|
|
||||||
.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage())
|
|
||||||
.setIsBuyerMakerAndSellerTaker(isBuyerMakerAndSellerTaker)
|
|
||||||
.setMakerAccountId(makerAccountId)
|
|
||||||
.setTakerAccountId(takerAccountId)
|
|
||||||
.setMakerPaymentMethodId(makerPaymentMethodId)
|
|
||||||
.setTakerPaymentMethodId(takerPaymentMethodId)
|
|
||||||
.setMakerPaymentAccountPayloadHash(ByteString.copyFrom(makerPaymentAccountPayloadHash))
|
|
||||||
.setTakerPaymentAccountPayloadHash(ByteString.copyFrom(takerPaymentAccountPayloadHash))
|
|
||||||
.setMakerPubKeyRing(makerPubKeyRing.toProtoMessage())
|
|
||||||
.setTakerPubKeyRing(takerPubKeyRing.toProtoMessage())
|
|
||||||
.setMakerPayoutAddressString(makerPayoutAddressString)
|
|
||||||
.setTakerPayoutAddressString(takerPayoutAddressString)
|
|
||||||
.setMakerDepositTxHash(makerDepositTxHash)
|
|
||||||
.setTakerDepositTxHash(takerDepositTxHash)
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ import haveno.common.crypto.KeyRing;
|
|||||||
import haveno.common.crypto.PubKeyRing;
|
import haveno.common.crypto.PubKeyRing;
|
||||||
import haveno.common.crypto.Sig;
|
import haveno.common.crypto.Sig;
|
||||||
import haveno.common.file.FileUtil;
|
import haveno.common.file.FileUtil;
|
||||||
|
import haveno.common.util.Base64;
|
||||||
import haveno.common.util.Utilities;
|
import haveno.common.util.Utilities;
|
||||||
import haveno.core.api.CoreNotificationService;
|
import haveno.core.api.CoreNotificationService;
|
||||||
import haveno.core.api.XmrConnectionService;
|
import haveno.core.api.XmrConnectionService;
|
||||||
@ -48,7 +49,10 @@ import java.math.BigDecimal;
|
|||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.text.DecimalFormatSymbols;
|
import java.text.DecimalFormatSymbols;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
@ -87,13 +91,15 @@ public class HavenoUtils {
|
|||||||
|
|
||||||
// configure fees
|
// configure fees
|
||||||
public static final boolean ARBITRATOR_ASSIGNS_TRADE_FEE_ADDRESS = true;
|
public static final boolean ARBITRATOR_ASSIGNS_TRADE_FEE_ADDRESS = true;
|
||||||
|
public static final double PENALTY_FEE_PCT = 0.02; // 2%
|
||||||
public static final double MAKER_FEE_PCT = 0.0015; // 0.15%
|
public static final double MAKER_FEE_PCT = 0.0015; // 0.15%
|
||||||
public static final double TAKER_FEE_PCT = 0.0075; // 0.75%
|
public static final double TAKER_FEE_PCT = 0.0075; // 0.75%
|
||||||
public static final double PENALTY_FEE_PCT = 0.02; // 2%
|
public static final double MAKER_FEE_FOR_TAKER_WITHOUT_DEPOSIT_PCT = MAKER_FEE_PCT + TAKER_FEE_PCT; // customize maker's fee when no deposit or fee from taker
|
||||||
|
|
||||||
// other configuration
|
// other configuration
|
||||||
public static final long LOG_POLL_ERROR_PERIOD_MS = 1000 * 60 * 4; // log poll errors up to once every 4 minutes
|
public static final long LOG_POLL_ERROR_PERIOD_MS = 1000 * 60 * 4; // log poll errors up to once every 4 minutes
|
||||||
public static final long LOG_DAEMON_NOT_SYNCED_WARN_PERIOD_MS = 1000 * 30; // log warnings when daemon not synced once every 30s
|
public static final long LOG_DAEMON_NOT_SYNCED_WARN_PERIOD_MS = 1000 * 30; // log warnings when daemon not synced once every 30s
|
||||||
|
public static final int PRIVATE_OFFER_PASSPHRASE_NUM_WORDS = 8; // number of words in a private offer passphrase
|
||||||
|
|
||||||
// synchronize requests to the daemon
|
// synchronize requests to the daemon
|
||||||
private static boolean SYNC_DAEMON_REQUESTS = false; // sync long requests to daemon (e.g. refresh, update pool) // TODO: performance suffers by syncing daemon requests, but otherwise we sometimes get sporadic errors?
|
private static boolean SYNC_DAEMON_REQUESTS = false; // sync long requests to daemon (e.g. refresh, update pool) // TODO: performance suffers by syncing daemon requests, but otherwise we sometimes get sporadic errors?
|
||||||
@ -286,6 +292,41 @@ public class HavenoUtils {
|
|||||||
|
|
||||||
// ------------------------ SIGNING AND VERIFYING -------------------------
|
// ------------------------ SIGNING AND VERIFYING -------------------------
|
||||||
|
|
||||||
|
public static String generateChallenge() {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// load bip39 words
|
||||||
|
String fileName = "bip39_english.txt";
|
||||||
|
File bip39File = new File(havenoSetup.getConfig().appDataDir, fileName);
|
||||||
|
if (!bip39File.exists()) FileUtil.resourceToFile(fileName, bip39File);
|
||||||
|
List<String> bip39Words = Files.readAllLines(bip39File.toPath(), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
// select words randomly
|
||||||
|
List<String> passphraseWords = new ArrayList<String>();
|
||||||
|
SecureRandom secureRandom = new SecureRandom();
|
||||||
|
for (int i = 0; i < PRIVATE_OFFER_PASSPHRASE_NUM_WORDS; i++) {
|
||||||
|
passphraseWords.add(bip39Words.get(secureRandom.nextInt(bip39Words.size())));
|
||||||
|
}
|
||||||
|
return String.join(" ", passphraseWords);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalStateException("Failed to generate challenge", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getChallengeHash(String challenge) {
|
||||||
|
if (challenge == null) return null;
|
||||||
|
|
||||||
|
// tokenize passphrase
|
||||||
|
String[] words = challenge.toLowerCase().split(" ");
|
||||||
|
|
||||||
|
// collect first 4 letters of each word, which are unique in bip39
|
||||||
|
List<String> prefixes = new ArrayList<String>();
|
||||||
|
for (String word : words) prefixes.add(word.substring(0, Math.min(word.length(), 4)));
|
||||||
|
|
||||||
|
// hash the result
|
||||||
|
return Base64.encode(Hash.getSha256Hash(String.join(" ", prefixes).getBytes()));
|
||||||
|
}
|
||||||
|
|
||||||
public static byte[] sign(KeyRing keyRing, String message) {
|
public static byte[] sign(KeyRing keyRing, String message) {
|
||||||
return sign(keyRing.getSignatureKeyPair().getPrivate(), message);
|
return sign(keyRing.getSignatureKeyPair().getPrivate(), message);
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,8 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade
|
|||||||
String uid,
|
String uid,
|
||||||
@Nullable NodeAddress makerNodeAddress,
|
@Nullable NodeAddress makerNodeAddress,
|
||||||
@Nullable NodeAddress takerNodeAddress,
|
@Nullable NodeAddress takerNodeAddress,
|
||||||
@Nullable NodeAddress arbitratorNodeAddress) {
|
@Nullable NodeAddress arbitratorNodeAddress,
|
||||||
|
@Nullable String challenge) {
|
||||||
super(offer,
|
super(offer,
|
||||||
tradeAmount,
|
tradeAmount,
|
||||||
tradePrice,
|
tradePrice,
|
||||||
@ -53,7 +54,8 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade
|
|||||||
uid,
|
uid,
|
||||||
makerNodeAddress,
|
makerNodeAddress,
|
||||||
takerNodeAddress,
|
takerNodeAddress,
|
||||||
arbitratorNodeAddress);
|
arbitratorNodeAddress,
|
||||||
|
challenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -87,7 +89,8 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade
|
|||||||
uid,
|
uid,
|
||||||
proto.getProcessModel().getMaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getMaker().getNodeAddress()) : null,
|
proto.getProcessModel().getMaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getMaker().getNodeAddress()) : null,
|
||||||
proto.getProcessModel().getTaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getTaker().getNodeAddress()) : null,
|
proto.getProcessModel().getTaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getTaker().getNodeAddress()) : null,
|
||||||
proto.getProcessModel().getArbitrator().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getArbitrator().getNodeAddress()) : null);
|
proto.getProcessModel().getArbitrator().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getArbitrator().getNodeAddress()) : null,
|
||||||
|
ProtoUtil.stringOrNullFromProto(proto.getChallenge()));
|
||||||
|
|
||||||
trade.setPrice(proto.getPrice());
|
trade.setPrice(proto.getPrice());
|
||||||
|
|
||||||
|
@ -44,7 +44,8 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
|
|||||||
String uid,
|
String uid,
|
||||||
@Nullable NodeAddress makerNodeAddress,
|
@Nullable NodeAddress makerNodeAddress,
|
||||||
@Nullable NodeAddress takerNodeAddress,
|
@Nullable NodeAddress takerNodeAddress,
|
||||||
@Nullable NodeAddress arbitratorNodeAddress) {
|
@Nullable NodeAddress arbitratorNodeAddress,
|
||||||
|
@Nullable String challenge) {
|
||||||
super(offer,
|
super(offer,
|
||||||
tradeAmount,
|
tradeAmount,
|
||||||
tradePrice,
|
tradePrice,
|
||||||
@ -53,7 +54,8 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
|
|||||||
uid,
|
uid,
|
||||||
makerNodeAddress,
|
makerNodeAddress,
|
||||||
takerNodeAddress,
|
takerNodeAddress,
|
||||||
arbitratorNodeAddress);
|
arbitratorNodeAddress,
|
||||||
|
challenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -87,7 +89,8 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
|
|||||||
uid,
|
uid,
|
||||||
proto.getProcessModel().getMaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getMaker().getNodeAddress()) : null,
|
proto.getProcessModel().getMaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getMaker().getNodeAddress()) : null,
|
||||||
proto.getProcessModel().getTaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getTaker().getNodeAddress()) : null,
|
proto.getProcessModel().getTaker().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getTaker().getNodeAddress()) : null,
|
||||||
proto.getProcessModel().getArbitrator().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getArbitrator().getNodeAddress()) : null),
|
proto.getProcessModel().getArbitrator().hasNodeAddress() ? NodeAddress.fromProto(proto.getProcessModel().getArbitrator().getNodeAddress()) : null,
|
||||||
|
ProtoUtil.stringOrNullFromProto(proto.getChallenge())),
|
||||||
proto,
|
proto,
|
||||||
coreProtoResolver);
|
coreProtoResolver);
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,8 @@ public abstract class SellerTrade extends Trade {
|
|||||||
String uid,
|
String uid,
|
||||||
@Nullable NodeAddress makerNodeAddress,
|
@Nullable NodeAddress makerNodeAddress,
|
||||||
@Nullable NodeAddress takerNodeAddress,
|
@Nullable NodeAddress takerNodeAddress,
|
||||||
@Nullable NodeAddress arbitratorNodeAddress) {
|
@Nullable NodeAddress arbitratorNodeAddress,
|
||||||
|
@Nullable String challenge) {
|
||||||
super(offer,
|
super(offer,
|
||||||
tradeAmount,
|
tradeAmount,
|
||||||
tradePrice,
|
tradePrice,
|
||||||
@ -45,7 +46,8 @@ public abstract class SellerTrade extends Trade {
|
|||||||
uid,
|
uid,
|
||||||
makerNodeAddress,
|
makerNodeAddress,
|
||||||
takerNodeAddress,
|
takerNodeAddress,
|
||||||
arbitratorNodeAddress);
|
arbitratorNodeAddress,
|
||||||
|
challenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -486,6 +486,8 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
private IdlePayoutSyncer idlePayoutSyncer;
|
private IdlePayoutSyncer idlePayoutSyncer;
|
||||||
@Getter
|
@Getter
|
||||||
private boolean isCompleted;
|
private boolean isCompleted;
|
||||||
|
@Getter
|
||||||
|
private final String challenge;
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Constructors
|
// Constructors
|
||||||
@ -500,7 +502,8 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
String uid,
|
String uid,
|
||||||
@Nullable NodeAddress makerNodeAddress,
|
@Nullable NodeAddress makerNodeAddress,
|
||||||
@Nullable NodeAddress takerNodeAddress,
|
@Nullable NodeAddress takerNodeAddress,
|
||||||
@Nullable NodeAddress arbitratorNodeAddress) {
|
@Nullable NodeAddress arbitratorNodeAddress,
|
||||||
|
@Nullable String challenge) {
|
||||||
super();
|
super();
|
||||||
this.offer = offer;
|
this.offer = offer;
|
||||||
this.amount = tradeAmount.longValueExact();
|
this.amount = tradeAmount.longValueExact();
|
||||||
@ -511,6 +514,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
this.uid = uid;
|
this.uid = uid;
|
||||||
this.takeOfferDate = new Date().getTime();
|
this.takeOfferDate = new Date().getTime();
|
||||||
this.tradeListeners = new ArrayList<TradeListener>();
|
this.tradeListeners = new ArrayList<TradeListener>();
|
||||||
|
this.challenge = challenge;
|
||||||
|
|
||||||
getMaker().setNodeAddress(makerNodeAddress);
|
getMaker().setNodeAddress(makerNodeAddress);
|
||||||
getTaker().setNodeAddress(takerNodeAddress);
|
getTaker().setNodeAddress(takerNodeAddress);
|
||||||
@ -534,7 +538,8 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
String uid,
|
String uid,
|
||||||
@Nullable NodeAddress makerNodeAddress,
|
@Nullable NodeAddress makerNodeAddress,
|
||||||
@Nullable NodeAddress takerNodeAddress,
|
@Nullable NodeAddress takerNodeAddress,
|
||||||
@Nullable NodeAddress arbitratorNodeAddress) {
|
@Nullable NodeAddress arbitratorNodeAddress,
|
||||||
|
@Nullable String challenge) {
|
||||||
|
|
||||||
this(offer,
|
this(offer,
|
||||||
tradeAmount,
|
tradeAmount,
|
||||||
@ -544,7 +549,8 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
uid,
|
uid,
|
||||||
makerNodeAddress,
|
makerNodeAddress,
|
||||||
takerNodeAddress,
|
takerNodeAddress,
|
||||||
arbitratorNodeAddress);
|
arbitratorNodeAddress,
|
||||||
|
challenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove these constructors
|
// TODO: remove these constructors
|
||||||
@ -559,7 +565,8 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
NodeAddress arbitratorNodeAddress,
|
NodeAddress arbitratorNodeAddress,
|
||||||
XmrWalletService xmrWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
ProcessModel processModel,
|
ProcessModel processModel,
|
||||||
String uid) {
|
String uid,
|
||||||
|
@Nullable String challenge) {
|
||||||
|
|
||||||
this(offer,
|
this(offer,
|
||||||
tradeAmount,
|
tradeAmount,
|
||||||
@ -569,7 +576,8 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
uid,
|
uid,
|
||||||
makerNodeAddress,
|
makerNodeAddress,
|
||||||
takerNodeAddress,
|
takerNodeAddress,
|
||||||
arbitratorNodeAddress);
|
arbitratorNodeAddress,
|
||||||
|
challenge);
|
||||||
|
|
||||||
setAmount(tradeAmount);
|
setAmount(tradeAmount);
|
||||||
}
|
}
|
||||||
@ -1233,7 +1241,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
Preconditions.checkNotNull(sellerPayoutAddress, "Seller payout address must not be null");
|
Preconditions.checkNotNull(sellerPayoutAddress, "Seller payout address must not be null");
|
||||||
Preconditions.checkNotNull(buyerPayoutAddress, "Buyer payout address must not be null");
|
Preconditions.checkNotNull(buyerPayoutAddress, "Buyer payout address must not be null");
|
||||||
BigInteger sellerDepositAmount = getSeller().getDepositTx().getIncomingAmount();
|
BigInteger sellerDepositAmount = getSeller().getDepositTx().getIncomingAmount();
|
||||||
BigInteger buyerDepositAmount = getBuyer().getDepositTx().getIncomingAmount();
|
BigInteger buyerDepositAmount = hasBuyerAsTakerWithoutDeposit() ? BigInteger.ZERO : getBuyer().getDepositTx().getIncomingAmount();
|
||||||
BigInteger tradeAmount = getAmount();
|
BigInteger tradeAmount = getAmount();
|
||||||
BigInteger buyerPayoutAmount = buyerDepositAmount.add(tradeAmount);
|
BigInteger buyerPayoutAmount = buyerDepositAmount.add(tradeAmount);
|
||||||
BigInteger sellerPayoutAmount = sellerDepositAmount.subtract(tradeAmount);
|
BigInteger sellerPayoutAmount = sellerDepositAmount.subtract(tradeAmount);
|
||||||
@ -1324,7 +1332,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
MoneroWallet wallet = getWallet();
|
MoneroWallet wallet = getWallet();
|
||||||
Contract contract = getContract();
|
Contract contract = getContract();
|
||||||
BigInteger sellerDepositAmount = getSeller().getDepositTx().getIncomingAmount();
|
BigInteger sellerDepositAmount = getSeller().getDepositTx().getIncomingAmount();
|
||||||
BigInteger buyerDepositAmount = getBuyer().getDepositTx().getIncomingAmount();
|
BigInteger buyerDepositAmount = hasBuyerAsTakerWithoutDeposit() ? BigInteger.ZERO : getBuyer().getDepositTx().getIncomingAmount();
|
||||||
BigInteger tradeAmount = getAmount();
|
BigInteger tradeAmount = getAmount();
|
||||||
|
|
||||||
// describe payout tx
|
// describe payout tx
|
||||||
@ -2091,9 +2099,9 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
final long tradeTime = getTakeOfferDate().getTime();
|
final long tradeTime = getTakeOfferDate().getTime();
|
||||||
MoneroDaemon daemonRpc = xmrWalletService.getDaemon();
|
MoneroDaemon daemonRpc = xmrWalletService.getDaemon();
|
||||||
if (daemonRpc == null) throw new RuntimeException("Cannot set start time for trade " + getId() + " because it has no connection to monerod");
|
if (daemonRpc == null) throw new RuntimeException("Cannot set start time for trade " + getId() + " because it has no connection to monerod");
|
||||||
if (getMakerDepositTx() == null || getTakerDepositTx() == null) throw new RuntimeException("Cannot set start time for trade " + getId() + " because its unlocked deposit tx is null. Is client connected to a daemon?");
|
if (getMakerDepositTx() == null || (getTakerDepositTx() == null && !hasBuyerAsTakerWithoutDeposit())) throw new RuntimeException("Cannot set start time for trade " + getId() + " because its unlocked deposit tx is null. Is client connected to a daemon?");
|
||||||
|
|
||||||
long maxHeight = Math.max(getMakerDepositTx().getHeight(), getTakerDepositTx().getHeight());
|
long maxHeight = Math.max(getMakerDepositTx().getHeight(), hasBuyerAsTakerWithoutDeposit() ? 0l : getTakerDepositTx().getHeight());
|
||||||
long blockTime = daemonRpc.getBlockByHeight(maxHeight).getTimestamp();
|
long blockTime = daemonRpc.getBlockByHeight(maxHeight).getTimestamp();
|
||||||
|
|
||||||
// If block date is in future (Date in blocks can be off by +/- 2 hours) we use our current date.
|
// If block date is in future (Date in blocks can be off by +/- 2 hours) we use our current date.
|
||||||
@ -2125,7 +2133,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
|
|
||||||
public boolean isDepositsPublished() {
|
public boolean isDepositsPublished() {
|
||||||
if (isDepositFailed()) return false;
|
if (isDepositFailed()) return false;
|
||||||
return getState().getPhase().ordinal() >= Phase.DEPOSITS_PUBLISHED.ordinal() && getMaker().getDepositTxHash() != null && getTaker().getDepositTxHash() != null;
|
return getState().getPhase().ordinal() >= Phase.DEPOSITS_PUBLISHED.ordinal() && getMaker().getDepositTxHash() != null && (getTaker().getDepositTxHash() != null || hasBuyerAsTakerWithoutDeposit());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFundsLockedIn() {
|
public boolean isFundsLockedIn() {
|
||||||
@ -2277,7 +2285,11 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public BigInteger getTakerFee() {
|
public BigInteger getTakerFee() {
|
||||||
return offer.getTakerFee(getAmount());
|
return hasBuyerAsTakerWithoutDeposit() ? BigInteger.ZERO : offer.getTakerFee(getAmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getSecurityDepositBeforeMiningFee() {
|
||||||
|
return isBuyer() ? getBuyerSecurityDepositBeforeMiningFee() : getSellerSecurityDepositBeforeMiningFee();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigInteger getBuyerSecurityDepositBeforeMiningFee() {
|
public BigInteger getBuyerSecurityDepositBeforeMiningFee() {
|
||||||
@ -2288,6 +2300,14 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
return offer.getOfferPayload().getSellerSecurityDepositForTradeAmount(getAmount());
|
return offer.getOfferPayload().getSellerSecurityDepositForTradeAmount(getAmount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isBuyerAsTakerWithoutDeposit() {
|
||||||
|
return isBuyer() && isTaker() && BigInteger.ZERO.equals(getBuyerSecurityDepositBeforeMiningFee());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasBuyerAsTakerWithoutDeposit() {
|
||||||
|
return getBuyer() == getTaker() && BigInteger.ZERO.equals(getBuyerSecurityDepositBeforeMiningFee());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BigInteger getTotalTxFee() {
|
public BigInteger getTotalTxFee() {
|
||||||
return getSelf().getDepositTxFee().add(getSelf().getPayoutTxFee()); // sum my tx fees
|
return getSelf().getDepositTxFee().add(getSelf().getPayoutTxFee()); // sum my tx fees
|
||||||
@ -2303,7 +2323,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTxChainInvalid() {
|
public boolean isTxChainInvalid() {
|
||||||
return processModel.getMaker().getDepositTxHash() == null || processModel.getTaker().getDepositTxHash() == null;
|
return processModel.getMaker().getDepositTxHash() == null || (processModel.getTaker().getDepositTxHash() == null && !hasBuyerAsTakerWithoutDeposit());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2537,7 +2557,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
if (isPayoutUnlocked()) return;
|
if (isPayoutUnlocked()) return;
|
||||||
|
|
||||||
// skip if deposit txs unknown or not requested
|
// skip if deposit txs unknown or not requested
|
||||||
if (processModel.getMaker().getDepositTxHash() == null || processModel.getTaker().getDepositTxHash() == null || !isDepositRequested()) return;
|
if (!isDepositRequested() || processModel.getMaker().getDepositTxHash() == null || (processModel.getTaker().getDepositTxHash() == null && !hasBuyerAsTakerWithoutDeposit())) return;
|
||||||
|
|
||||||
// skip if daemon not synced
|
// skip if daemon not synced
|
||||||
if (xmrConnectionService.getTargetHeight() == null || !xmrConnectionService.isSyncedWithinTolerance()) return;
|
if (xmrConnectionService.getTargetHeight() == null || !xmrConnectionService.isSyncedWithinTolerance()) return;
|
||||||
@ -2553,7 +2573,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
|
|
||||||
// get txs from trade wallet
|
// get txs from trade wallet
|
||||||
MoneroTxQuery query = new MoneroTxQuery().setIncludeOutputs(true);
|
MoneroTxQuery query = new MoneroTxQuery().setIncludeOutputs(true);
|
||||||
Boolean updatePool = !isDepositsConfirmed() && (getMaker().getDepositTx() == null || getTaker().getDepositTx() == null);
|
Boolean updatePool = !isDepositsConfirmed() && (getMaker().getDepositTx() == null || (getTaker().getDepositTx() == null && hasBuyerAsTakerWithoutDeposit()));
|
||||||
if (!updatePool) query.setInTxPool(false); // avoid updating from pool if possible
|
if (!updatePool) query.setInTxPool(false); // avoid updating from pool if possible
|
||||||
List<MoneroTxWallet> txs;
|
List<MoneroTxWallet> txs;
|
||||||
if (!updatePool) txs = wallet.getTxs(query);
|
if (!updatePool) txs = wallet.getTxs(query);
|
||||||
@ -2565,22 +2585,22 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
setDepositTxs(txs);
|
setDepositTxs(txs);
|
||||||
if (getMaker().getDepositTx() == null || getTaker().getDepositTx() == null) return; // skip if either deposit tx not seen
|
if (getMaker().getDepositTx() == null || (getTaker().getDepositTx() == null && !hasBuyerAsTakerWithoutDeposit())) return; // skip if either deposit tx not seen
|
||||||
setStateDepositsSeen();
|
setStateDepositsSeen();
|
||||||
|
|
||||||
// set actual security deposits
|
// set actual security deposits
|
||||||
if (getBuyer().getSecurityDeposit().longValueExact() == 0) {
|
if (getBuyer().getSecurityDeposit().longValueExact() == 0) {
|
||||||
BigInteger buyerSecurityDeposit = ((MoneroTxWallet) getBuyer().getDepositTx()).getIncomingAmount();
|
BigInteger buyerSecurityDeposit = hasBuyerAsTakerWithoutDeposit() ? BigInteger.ZERO : ((MoneroTxWallet) getBuyer().getDepositTx()).getIncomingAmount();
|
||||||
BigInteger sellerSecurityDeposit = ((MoneroTxWallet) getSeller().getDepositTx()).getIncomingAmount().subtract(getAmount());
|
BigInteger sellerSecurityDeposit = ((MoneroTxWallet) getSeller().getDepositTx()).getIncomingAmount().subtract(getAmount());
|
||||||
getBuyer().setSecurityDeposit(buyerSecurityDeposit);
|
getBuyer().setSecurityDeposit(buyerSecurityDeposit);
|
||||||
getSeller().setSecurityDeposit(sellerSecurityDeposit);
|
getSeller().setSecurityDeposit(sellerSecurityDeposit);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for deposit txs confirmation
|
// check for deposit txs confirmation
|
||||||
if (getMaker().getDepositTx().isConfirmed() && getTaker().getDepositTx().isConfirmed()) setStateDepositsConfirmed();
|
if (getMaker().getDepositTx().isConfirmed() && (hasBuyerAsTakerWithoutDeposit() || getTaker().getDepositTx().isConfirmed())) setStateDepositsConfirmed();
|
||||||
|
|
||||||
// check for deposit txs unlocked
|
// check for deposit txs unlocked
|
||||||
if (getMaker().getDepositTx().getNumConfirmations() >= XmrWalletService.NUM_BLOCKS_UNLOCK && getTaker().getDepositTx().getNumConfirmations() >= XmrWalletService.NUM_BLOCKS_UNLOCK) {
|
if (getMaker().getDepositTx().getNumConfirmations() >= XmrWalletService.NUM_BLOCKS_UNLOCK && (hasBuyerAsTakerWithoutDeposit() || getTaker().getDepositTx().getNumConfirmations() >= XmrWalletService.NUM_BLOCKS_UNLOCK)) {
|
||||||
setStateDepositsUnlocked();
|
setStateDepositsUnlocked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2750,7 +2770,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
log.warn("Missing maker deposit tx for {} {}", getClass().getSimpleName(), getId());
|
log.warn("Missing maker deposit tx for {} {}", getClass().getSimpleName(), getId());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (getTakerDepositTx() == null) {
|
if (getTakerDepositTx() == null && !hasBuyerAsTakerWithoutDeposit()) {
|
||||||
log.warn("Missing taker deposit tx for {} {}", getClass().getSimpleName(), getId());
|
log.warn("Missing taker deposit tx for {} {}", getClass().getSimpleName(), getId());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -2913,6 +2933,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
Optional.ofNullable(payoutTxHex).ifPresent(e -> builder.setPayoutTxHex(payoutTxHex));
|
Optional.ofNullable(payoutTxHex).ifPresent(e -> builder.setPayoutTxHex(payoutTxHex));
|
||||||
Optional.ofNullable(payoutTxKey).ifPresent(e -> builder.setPayoutTxKey(payoutTxKey));
|
Optional.ofNullable(payoutTxKey).ifPresent(e -> builder.setPayoutTxKey(payoutTxKey));
|
||||||
Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData));
|
Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData));
|
||||||
|
Optional.ofNullable(challenge).ifPresent(e -> builder.setChallenge(challenge));
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2982,6 +3003,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||||||
",\n refundResultState=" + refundResultState +
|
",\n refundResultState=" + refundResultState +
|
||||||
",\n refundResultStateProperty=" + refundResultStateProperty +
|
",\n refundResultStateProperty=" + refundResultStateProperty +
|
||||||
",\n isCompleted=" + isCompleted +
|
",\n isCompleted=" + isCompleted +
|
||||||
|
",\n challenge='" + challenge + '\'' +
|
||||||
"\n}";
|
"\n}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -562,6 +562,12 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
if (openOffer.getState() != OpenOffer.State.AVAILABLE) return;
|
if (openOffer.getState() != OpenOffer.State.AVAILABLE) return;
|
||||||
Offer offer = openOffer.getOffer();
|
Offer offer = openOffer.getOffer();
|
||||||
|
|
||||||
|
// validate challenge
|
||||||
|
if (openOffer.getChallenge() != null && !HavenoUtils.getChallengeHash(openOffer.getChallenge()).equals(HavenoUtils.getChallengeHash(request.getChallenge()))) {
|
||||||
|
log.warn("Ignoring InitTradeRequest to maker because challenge is incorrect, tradeId={}, sender={}", request.getOfferId(), sender);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// ensure trade does not already exist
|
// ensure trade does not already exist
|
||||||
Optional<Trade> tradeOptional = getOpenTrade(request.getOfferId());
|
Optional<Trade> tradeOptional = getOpenTrade(request.getOfferId());
|
||||||
if (tradeOptional.isPresent()) {
|
if (tradeOptional.isPresent()) {
|
||||||
@ -583,7 +589,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
UUID.randomUUID().toString(),
|
UUID.randomUUID().toString(),
|
||||||
request.getMakerNodeAddress(),
|
request.getMakerNodeAddress(),
|
||||||
request.getTakerNodeAddress(),
|
request.getTakerNodeAddress(),
|
||||||
request.getArbitratorNodeAddress());
|
request.getArbitratorNodeAddress(),
|
||||||
|
openOffer.getChallenge());
|
||||||
else
|
else
|
||||||
trade = new SellerAsMakerTrade(offer,
|
trade = new SellerAsMakerTrade(offer,
|
||||||
BigInteger.valueOf(request.getTradeAmount()),
|
BigInteger.valueOf(request.getTradeAmount()),
|
||||||
@ -593,7 +600,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
UUID.randomUUID().toString(),
|
UUID.randomUUID().toString(),
|
||||||
request.getMakerNodeAddress(),
|
request.getMakerNodeAddress(),
|
||||||
request.getTakerNodeAddress(),
|
request.getTakerNodeAddress(),
|
||||||
request.getArbitratorNodeAddress());
|
request.getArbitratorNodeAddress(),
|
||||||
|
openOffer.getChallenge());
|
||||||
trade.getMaker().setPaymentAccountId(trade.getOffer().getOfferPayload().getMakerPaymentAccountId());
|
trade.getMaker().setPaymentAccountId(trade.getOffer().getOfferPayload().getMakerPaymentAccountId());
|
||||||
trade.getTaker().setPaymentAccountId(request.getTakerPaymentAccountId());
|
trade.getTaker().setPaymentAccountId(request.getTakerPaymentAccountId());
|
||||||
trade.getMaker().setPubKeyRing(trade.getOffer().getPubKeyRing());
|
trade.getMaker().setPubKeyRing(trade.getOffer().getPubKeyRing());
|
||||||
@ -646,6 +654,12 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validate challenge hash
|
||||||
|
if (offer.getChallengeHash() != null && !offer.getChallengeHash().equals(HavenoUtils.getChallengeHash(request.getChallenge()))) {
|
||||||
|
log.warn("Ignoring InitTradeRequest to arbitrator because challenge hash is incorrect, tradeId={}, sender={}", request.getOfferId(), sender);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// handle trade
|
// handle trade
|
||||||
Trade trade;
|
Trade trade;
|
||||||
Optional<Trade> tradeOptional = getOpenTrade(offer.getId());
|
Optional<Trade> tradeOptional = getOpenTrade(offer.getId());
|
||||||
@ -679,7 +693,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
UUID.randomUUID().toString(),
|
UUID.randomUUID().toString(),
|
||||||
request.getMakerNodeAddress(),
|
request.getMakerNodeAddress(),
|
||||||
request.getTakerNodeAddress(),
|
request.getTakerNodeAddress(),
|
||||||
request.getArbitratorNodeAddress());
|
request.getArbitratorNodeAddress(),
|
||||||
|
request.getChallenge());
|
||||||
|
|
||||||
// set reserve tx hash if available
|
// set reserve tx hash if available
|
||||||
Optional<SignedOffer> signedOfferOptional = openOfferManager.getSignedOfferById(request.getOfferId());
|
Optional<SignedOffer> signedOfferOptional = openOfferManager.getSignedOfferById(request.getOfferId());
|
||||||
@ -873,7 +888,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
UUID.randomUUID().toString(),
|
UUID.randomUUID().toString(),
|
||||||
offer.getMakerNodeAddress(),
|
offer.getMakerNodeAddress(),
|
||||||
P2PService.getMyNodeAddress(),
|
P2PService.getMyNodeAddress(),
|
||||||
null);
|
null,
|
||||||
|
offer.getChallenge());
|
||||||
} else {
|
} else {
|
||||||
trade = new BuyerAsTakerTrade(offer,
|
trade = new BuyerAsTakerTrade(offer,
|
||||||
amount,
|
amount,
|
||||||
@ -883,7 +899,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
UUID.randomUUID().toString(),
|
UUID.randomUUID().toString(),
|
||||||
offer.getMakerNodeAddress(),
|
offer.getMakerNodeAddress(),
|
||||||
P2PService.getMyNodeAddress(),
|
P2PService.getMyNodeAddress(),
|
||||||
null);
|
null,
|
||||||
|
offer.getChallenge());
|
||||||
}
|
}
|
||||||
trade.getProcessModel().setUseSavingsWallet(useSavingsWallet);
|
trade.getProcessModel().setUseSavingsWallet(useSavingsWallet);
|
||||||
trade.getProcessModel().setFundsNeededForTrade(fundsNeededForTrade.longValueExact());
|
trade.getProcessModel().setFundsNeededForTrade(fundsNeededForTrade.longValueExact());
|
||||||
@ -1127,7 +1144,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
log.warn("We found a closed trade with locked up funds. " +
|
log.warn("We found a closed trade with locked up funds. " +
|
||||||
"That should never happen. trade ID={} ID={}, state={}, payoutState={}, disputeState={}", trade.getClass().getSimpleName(), trade.getId(), trade.getState(), trade.getPayoutState(), trade.getDisputeState());
|
"That should never happen. trade ID={} ID={}, state={}, payoutState={}, disputeState={}", trade.getClass().getSimpleName(), trade.getId(), trade.getState(), trade.getPayoutState(), trade.getDisputeState());
|
||||||
}
|
}
|
||||||
} else {
|
} else if (!trade.hasBuyerAsTakerWithoutDeposit()) {
|
||||||
log.warn("Closed trade with locked up funds missing taker deposit tx. {} ID={}, state={}, payoutState={}, disputeState={}", trade.getClass().getSimpleName(), trade.getId(), trade.getState(), trade.getPayoutState(), trade.getDisputeState());
|
log.warn("Closed trade with locked up funds missing taker deposit tx. {} ID={}, state={}, payoutState={}, disputeState={}", trade.getClass().getSimpleName(), trade.getId(), trade.getState(), trade.getPayoutState(), trade.getDisputeState());
|
||||||
tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithNoDepositTx", trade.getShortId())));
|
tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithNoDepositTx", trade.getShortId())));
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,9 @@ import java.util.Optional;
|
|||||||
public final class DepositRequest extends TradeMessage implements DirectMessage {
|
public final class DepositRequest extends TradeMessage implements DirectMessage {
|
||||||
private final long currentDate;
|
private final long currentDate;
|
||||||
private final byte[] contractSignature;
|
private final byte[] contractSignature;
|
||||||
|
@Nullable
|
||||||
private final String depositTxHex;
|
private final String depositTxHex;
|
||||||
|
@Nullable
|
||||||
private final String depositTxKey;
|
private final String depositTxKey;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final byte[] paymentAccountKey;
|
private final byte[] paymentAccountKey;
|
||||||
@ -43,8 +45,8 @@ public final class DepositRequest extends TradeMessage implements DirectMessage
|
|||||||
String messageVersion,
|
String messageVersion,
|
||||||
long currentDate,
|
long currentDate,
|
||||||
byte[] contractSignature,
|
byte[] contractSignature,
|
||||||
String depositTxHex,
|
@Nullable String depositTxHex,
|
||||||
String depositTxKey,
|
@Nullable String depositTxKey,
|
||||||
@Nullable byte[] paymentAccountKey) {
|
@Nullable byte[] paymentAccountKey) {
|
||||||
super(messageVersion, tradeId, uid);
|
super(messageVersion, tradeId, uid);
|
||||||
this.currentDate = currentDate;
|
this.currentDate = currentDate;
|
||||||
@ -63,13 +65,12 @@ public final class DepositRequest extends TradeMessage implements DirectMessage
|
|||||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||||
protobuf.DepositRequest.Builder builder = protobuf.DepositRequest.newBuilder()
|
protobuf.DepositRequest.Builder builder = protobuf.DepositRequest.newBuilder()
|
||||||
.setTradeId(offerId)
|
.setTradeId(offerId)
|
||||||
.setUid(uid)
|
.setUid(uid);
|
||||||
.setDepositTxHex(depositTxHex)
|
|
||||||
.setDepositTxKey(depositTxKey);
|
|
||||||
builder.setCurrentDate(currentDate);
|
builder.setCurrentDate(currentDate);
|
||||||
Optional.ofNullable(paymentAccountKey).ifPresent(e -> builder.setPaymentAccountKey(ByteString.copyFrom(e)));
|
Optional.ofNullable(paymentAccountKey).ifPresent(e -> builder.setPaymentAccountKey(ByteString.copyFrom(e)));
|
||||||
|
Optional.ofNullable(depositTxHex).ifPresent(builder::setDepositTxHex);
|
||||||
|
Optional.ofNullable(depositTxKey).ifPresent(builder::setDepositTxKey);
|
||||||
Optional.ofNullable(contractSignature).ifPresent(e -> builder.setContractSignature(ByteString.copyFrom(e)));
|
Optional.ofNullable(contractSignature).ifPresent(e -> builder.setContractSignature(ByteString.copyFrom(e)));
|
||||||
|
|
||||||
return getNetworkEnvelopeBuilder().setDepositRequest(builder).build();
|
return getNetworkEnvelopeBuilder().setDepositRequest(builder).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,8 +82,8 @@ public final class DepositRequest extends TradeMessage implements DirectMessage
|
|||||||
messageVersion,
|
messageVersion,
|
||||||
proto.getCurrentDate(),
|
proto.getCurrentDate(),
|
||||||
ProtoUtil.byteArrayOrNullFromProto(proto.getContractSignature()),
|
ProtoUtil.byteArrayOrNullFromProto(proto.getContractSignature()),
|
||||||
proto.getDepositTxHex(),
|
ProtoUtil.stringOrNullFromProto(proto.getDepositTxHex()),
|
||||||
proto.getDepositTxKey(),
|
ProtoUtil.stringOrNullFromProto(proto.getDepositTxKey()),
|
||||||
ProtoUtil.byteArrayOrNullFromProto(proto.getPaymentAccountKey()));
|
ProtoUtil.byteArrayOrNullFromProto(proto.getPaymentAccountKey()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +58,8 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag
|
|||||||
private final String reserveTxKey;
|
private final String reserveTxKey;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final String payoutAddress;
|
private final String payoutAddress;
|
||||||
|
@Nullable
|
||||||
|
private final String challenge;
|
||||||
|
|
||||||
public InitTradeRequest(TradeProtocolVersion tradeProtocolVersion,
|
public InitTradeRequest(TradeProtocolVersion tradeProtocolVersion,
|
||||||
String offerId,
|
String offerId,
|
||||||
@ -79,7 +81,8 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag
|
|||||||
@Nullable String reserveTxHash,
|
@Nullable String reserveTxHash,
|
||||||
@Nullable String reserveTxHex,
|
@Nullable String reserveTxHex,
|
||||||
@Nullable String reserveTxKey,
|
@Nullable String reserveTxKey,
|
||||||
@Nullable String payoutAddress) {
|
@Nullable String payoutAddress,
|
||||||
|
@Nullable String challenge) {
|
||||||
super(messageVersion, offerId, uid);
|
super(messageVersion, offerId, uid);
|
||||||
this.tradeProtocolVersion = tradeProtocolVersion;
|
this.tradeProtocolVersion = tradeProtocolVersion;
|
||||||
this.tradeAmount = tradeAmount;
|
this.tradeAmount = tradeAmount;
|
||||||
@ -99,6 +102,7 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag
|
|||||||
this.reserveTxHex = reserveTxHex;
|
this.reserveTxHex = reserveTxHex;
|
||||||
this.reserveTxKey = reserveTxKey;
|
this.reserveTxKey = reserveTxKey;
|
||||||
this.payoutAddress = payoutAddress;
|
this.payoutAddress = payoutAddress;
|
||||||
|
this.challenge = challenge;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -129,6 +133,7 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag
|
|||||||
Optional.ofNullable(reserveTxHex).ifPresent(e -> builder.setReserveTxHex(reserveTxHex));
|
Optional.ofNullable(reserveTxHex).ifPresent(e -> builder.setReserveTxHex(reserveTxHex));
|
||||||
Optional.ofNullable(reserveTxKey).ifPresent(e -> builder.setReserveTxKey(reserveTxKey));
|
Optional.ofNullable(reserveTxKey).ifPresent(e -> builder.setReserveTxKey(reserveTxKey));
|
||||||
Optional.ofNullable(payoutAddress).ifPresent(e -> builder.setPayoutAddress(payoutAddress));
|
Optional.ofNullable(payoutAddress).ifPresent(e -> builder.setPayoutAddress(payoutAddress));
|
||||||
|
Optional.ofNullable(challenge).ifPresent(e -> builder.setChallenge(challenge));
|
||||||
Optional.ofNullable(accountAgeWitnessSignatureOfOfferId).ifPresent(e -> builder.setAccountAgeWitnessSignatureOfOfferId(ByteString.copyFrom(e)));
|
Optional.ofNullable(accountAgeWitnessSignatureOfOfferId).ifPresent(e -> builder.setAccountAgeWitnessSignatureOfOfferId(ByteString.copyFrom(e)));
|
||||||
builder.setCurrentDate(currentDate);
|
builder.setCurrentDate(currentDate);
|
||||||
|
|
||||||
@ -158,7 +163,8 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag
|
|||||||
ProtoUtil.stringOrNullFromProto(proto.getReserveTxHash()),
|
ProtoUtil.stringOrNullFromProto(proto.getReserveTxHash()),
|
||||||
ProtoUtil.stringOrNullFromProto(proto.getReserveTxHex()),
|
ProtoUtil.stringOrNullFromProto(proto.getReserveTxHex()),
|
||||||
ProtoUtil.stringOrNullFromProto(proto.getReserveTxKey()),
|
ProtoUtil.stringOrNullFromProto(proto.getReserveTxKey()),
|
||||||
ProtoUtil.stringOrNullFromProto(proto.getPayoutAddress()));
|
ProtoUtil.stringOrNullFromProto(proto.getPayoutAddress()),
|
||||||
|
ProtoUtil.stringOrNullFromProto(proto.getChallenge()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -183,6 +189,7 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag
|
|||||||
",\n reserveTxHex=" + reserveTxHex +
|
",\n reserveTxHex=" + reserveTxHex +
|
||||||
",\n reserveTxKey=" + reserveTxKey +
|
",\n reserveTxKey=" + reserveTxKey +
|
||||||
",\n payoutAddress=" + payoutAddress +
|
",\n payoutAddress=" + payoutAddress +
|
||||||
|
",\n challenge=" + challenge +
|
||||||
"\n} " + super.toString();
|
"\n} " + super.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,9 @@ public final class SignContractRequest extends TradeMessage implements DirectMes
|
|||||||
private final String accountId;
|
private final String accountId;
|
||||||
private final byte[] paymentAccountPayloadHash;
|
private final byte[] paymentAccountPayloadHash;
|
||||||
private final String payoutAddress;
|
private final String payoutAddress;
|
||||||
|
@Nullable
|
||||||
private final String depositTxHash;
|
private final String depositTxHash;
|
||||||
|
@Nullable
|
||||||
private final byte[] accountAgeWitnessSignatureOfDepositHash;
|
private final byte[] accountAgeWitnessSignatureOfDepositHash;
|
||||||
|
|
||||||
public SignContractRequest(String tradeId,
|
public SignContractRequest(String tradeId,
|
||||||
@ -45,7 +47,7 @@ public final class SignContractRequest extends TradeMessage implements DirectMes
|
|||||||
String accountId,
|
String accountId,
|
||||||
byte[] paymentAccountPayloadHash,
|
byte[] paymentAccountPayloadHash,
|
||||||
String payoutAddress,
|
String payoutAddress,
|
||||||
String depositTxHash,
|
@Nullable String depositTxHash,
|
||||||
@Nullable byte[] accountAgeWitnessSignatureOfDepositHash) {
|
@Nullable byte[] accountAgeWitnessSignatureOfDepositHash) {
|
||||||
super(messageVersion, tradeId, uid);
|
super(messageVersion, tradeId, uid);
|
||||||
this.currentDate = currentDate;
|
this.currentDate = currentDate;
|
||||||
@ -68,10 +70,9 @@ public final class SignContractRequest extends TradeMessage implements DirectMes
|
|||||||
.setUid(uid)
|
.setUid(uid)
|
||||||
.setAccountId(accountId)
|
.setAccountId(accountId)
|
||||||
.setPaymentAccountPayloadHash(ByteString.copyFrom(paymentAccountPayloadHash))
|
.setPaymentAccountPayloadHash(ByteString.copyFrom(paymentAccountPayloadHash))
|
||||||
.setPayoutAddress(payoutAddress)
|
.setPayoutAddress(payoutAddress);
|
||||||
.setDepositTxHash(depositTxHash);
|
|
||||||
|
|
||||||
Optional.ofNullable(accountAgeWitnessSignatureOfDepositHash).ifPresent(e -> builder.setAccountAgeWitnessSignatureOfDepositHash(ByteString.copyFrom(e)));
|
Optional.ofNullable(accountAgeWitnessSignatureOfDepositHash).ifPresent(e -> builder.setAccountAgeWitnessSignatureOfDepositHash(ByteString.copyFrom(e)));
|
||||||
|
Optional.ofNullable(depositTxHash).ifPresent(builder::setDepositTxHash);
|
||||||
builder.setCurrentDate(currentDate);
|
builder.setCurrentDate(currentDate);
|
||||||
|
|
||||||
return getNetworkEnvelopeBuilder().setSignContractRequest(builder).build();
|
return getNetworkEnvelopeBuilder().setSignContractRequest(builder).build();
|
||||||
@ -87,7 +88,7 @@ public final class SignContractRequest extends TradeMessage implements DirectMes
|
|||||||
proto.getAccountId(),
|
proto.getAccountId(),
|
||||||
proto.getPaymentAccountPayloadHash().toByteArray(),
|
proto.getPaymentAccountPayloadHash().toByteArray(),
|
||||||
proto.getPayoutAddress(),
|
proto.getPayoutAddress(),
|
||||||
proto.getDepositTxHash(),
|
ProtoUtil.stringOrNullFromProto(proto.getDepositTxHash()),
|
||||||
ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignatureOfDepositHash()));
|
ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignatureOfDepositHash()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +158,6 @@ public final class TradePeer implements PersistablePayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public BigInteger getSecurityDeposit() {
|
public BigInteger getSecurityDeposit() {
|
||||||
if (depositTxHash == null) return null;
|
|
||||||
return BigInteger.valueOf(securityDeposit);
|
return BigInteger.valueOf(securityDeposit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,8 +36,9 @@ import monero.daemon.model.MoneroSubmitTxResult;
|
|||||||
import monero.daemon.model.MoneroTx;
|
import monero.daemon.model.MoneroTx;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.Arrays;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ -83,28 +84,31 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
|||||||
byte[] signature = request.getContractSignature();
|
byte[] signature = request.getContractSignature();
|
||||||
|
|
||||||
// get trader info
|
// get trader info
|
||||||
TradePeer trader = trade.getTradePeer(processModel.getTempTradePeerNodeAddress());
|
TradePeer sender = trade.getTradePeer(processModel.getTempTradePeerNodeAddress());
|
||||||
if (trader == null) throw new RuntimeException(request.getClass().getSimpleName() + " is not from maker, taker, or arbitrator");
|
if (sender == null) throw new RuntimeException(request.getClass().getSimpleName() + " is not from maker, taker, or arbitrator");
|
||||||
PubKeyRing peerPubKeyRing = trader.getPubKeyRing();
|
PubKeyRing senderPubKeyRing = sender.getPubKeyRing();
|
||||||
|
|
||||||
// verify signature
|
// verify signature
|
||||||
if (!HavenoUtils.isSignatureValid(peerPubKeyRing, contractAsJson, signature)) {
|
if (!HavenoUtils.isSignatureValid(senderPubKeyRing, contractAsJson, signature)) {
|
||||||
throw new RuntimeException("Peer's contract signature is invalid");
|
throw new RuntimeException("Peer's contract signature is invalid");
|
||||||
}
|
}
|
||||||
|
|
||||||
// set peer's signature
|
// set peer's signature
|
||||||
trader.setContractSignature(signature);
|
sender.setContractSignature(signature);
|
||||||
|
|
||||||
// collect expected values
|
// collect expected values
|
||||||
Offer offer = trade.getOffer();
|
Offer offer = trade.getOffer();
|
||||||
boolean isFromTaker = trader == trade.getTaker();
|
boolean isFromTaker = sender == trade.getTaker();
|
||||||
boolean isFromBuyer = trader == trade.getBuyer();
|
boolean isFromBuyer = sender == trade.getBuyer();
|
||||||
BigInteger tradeFee = isFromTaker ? trade.getTakerFee() : trade.getMakerFee();
|
BigInteger tradeFee = isFromTaker ? trade.getTakerFee() : trade.getMakerFee();
|
||||||
BigInteger sendTradeAmount = isFromBuyer ? BigInteger.ZERO : trade.getAmount();
|
BigInteger sendTradeAmount = isFromBuyer ? BigInteger.ZERO : trade.getAmount();
|
||||||
BigInteger securityDeposit = isFromBuyer ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee();
|
BigInteger securityDeposit = isFromBuyer ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee();
|
||||||
String depositAddress = processModel.getMultisigAddress();
|
String depositAddress = processModel.getMultisigAddress();
|
||||||
|
sender.setSecurityDeposit(securityDeposit);
|
||||||
|
|
||||||
// verify deposit tx
|
// verify deposit tx
|
||||||
|
boolean isFromBuyerAsTakerWithoutDeposit = isFromBuyer && isFromTaker && trade.hasBuyerAsTakerWithoutDeposit();
|
||||||
|
if (!isFromBuyerAsTakerWithoutDeposit) {
|
||||||
MoneroTx verifiedTx;
|
MoneroTx verifiedTx;
|
||||||
try {
|
try {
|
||||||
verifiedTx = trade.getXmrWalletService().verifyDepositTx(
|
verifiedTx = trade.getXmrWalletService().verifyDepositTx(
|
||||||
@ -114,41 +118,52 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
|||||||
sendTradeAmount,
|
sendTradeAmount,
|
||||||
securityDeposit,
|
securityDeposit,
|
||||||
depositAddress,
|
depositAddress,
|
||||||
trader.getDepositTxHash(),
|
sender.getDepositTxHash(),
|
||||||
request.getDepositTxHex(),
|
request.getDepositTxHex(),
|
||||||
request.getDepositTxKey(),
|
request.getDepositTxKey(),
|
||||||
null);
|
null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Error processing deposit tx from " + (isFromTaker ? "taker " : "maker ") + trader.getNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage());
|
throw new RuntimeException("Error processing deposit tx from " + (isFromTaker ? "taker " : "maker ") + sender.getNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// update trade state
|
// update trade state
|
||||||
trader.setSecurityDeposit(securityDeposit.subtract(verifiedTx.getFee())); // subtract mining fee from security deposit
|
sender.setSecurityDeposit(sender.getSecurityDeposit().subtract(verifiedTx.getFee())); // subtract mining fee from security deposit
|
||||||
trader.setDepositTxFee(verifiedTx.getFee());
|
sender.setDepositTxFee(verifiedTx.getFee());
|
||||||
trader.setDepositTxHex(request.getDepositTxHex());
|
sender.setDepositTxHex(request.getDepositTxHex());
|
||||||
trader.setDepositTxKey(request.getDepositTxKey());
|
sender.setDepositTxKey(request.getDepositTxKey());
|
||||||
if (request.getPaymentAccountKey() != null) trader.setPaymentAccountKey(request.getPaymentAccountKey());
|
}
|
||||||
|
|
||||||
|
// update trade state
|
||||||
|
if (request.getPaymentAccountKey() != null) sender.setPaymentAccountKey(request.getPaymentAccountKey());
|
||||||
processModel.getTradeManager().requestPersistence();
|
processModel.getTradeManager().requestPersistence();
|
||||||
|
|
||||||
// relay deposit txs when both available
|
// relay deposit txs when both requests received
|
||||||
MoneroDaemon daemon = trade.getXmrWalletService().getDaemon();
|
MoneroDaemon daemon = trade.getXmrWalletService().getDaemon();
|
||||||
if (processModel.getMaker().getDepositTxHex() != null && processModel.getTaker().getDepositTxHex() != null) {
|
if (processModel.getMaker().getContractSignature() != null && processModel.getTaker().getContractSignature() != null) {
|
||||||
|
|
||||||
// check timeout and extend just before relaying
|
// check timeout and extend just before relaying
|
||||||
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out before relaying deposit txs for {} {}" + trade.getClass().getSimpleName() + " " + trade.getShortId());
|
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out before relaying deposit txs for {} {}" + trade.getClass().getSimpleName() + " " + trade.getShortId());
|
||||||
trade.addInitProgressStep();
|
trade.addInitProgressStep();
|
||||||
|
|
||||||
|
// relay deposit txs
|
||||||
boolean depositTxsRelayed = false;
|
boolean depositTxsRelayed = false;
|
||||||
|
List<String> txHashes = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// submit txs to pool but do not relay
|
// submit maker tx to pool but do not relay
|
||||||
MoneroSubmitTxResult makerResult = daemon.submitTxHex(processModel.getMaker().getDepositTxHex(), true);
|
MoneroSubmitTxResult makerResult = daemon.submitTxHex(processModel.getMaker().getDepositTxHex(), true);
|
||||||
MoneroSubmitTxResult takerResult = daemon.submitTxHex(processModel.getTaker().getDepositTxHex(), true);
|
|
||||||
if (!makerResult.isGood()) throw new RuntimeException("Error submitting maker deposit tx: " + JsonUtils.serialize(makerResult));
|
if (!makerResult.isGood()) throw new RuntimeException("Error submitting maker deposit tx: " + JsonUtils.serialize(makerResult));
|
||||||
|
txHashes.add(processModel.getMaker().getDepositTxHash());
|
||||||
|
|
||||||
|
// submit taker tx to pool but do not relay
|
||||||
|
if (!trade.hasBuyerAsTakerWithoutDeposit()) {
|
||||||
|
MoneroSubmitTxResult takerResult = daemon.submitTxHex(processModel.getTaker().getDepositTxHex(), true);
|
||||||
if (!takerResult.isGood()) throw new RuntimeException("Error submitting taker deposit tx: " + JsonUtils.serialize(takerResult));
|
if (!takerResult.isGood()) throw new RuntimeException("Error submitting taker deposit tx: " + JsonUtils.serialize(takerResult));
|
||||||
|
txHashes.add(processModel.getTaker().getDepositTxHash());
|
||||||
|
}
|
||||||
|
|
||||||
// relay txs
|
// relay txs
|
||||||
daemon.relayTxsByHash(Arrays.asList(processModel.getMaker().getDepositTxHash(), processModel.getTaker().getDepositTxHash()));
|
daemon.relayTxsByHash(txHashes);
|
||||||
depositTxsRelayed = true;
|
depositTxsRelayed = true;
|
||||||
|
|
||||||
// update trade state
|
// update trade state
|
||||||
@ -160,7 +175,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
|||||||
|
|
||||||
// flush txs from pool
|
// flush txs from pool
|
||||||
try {
|
try {
|
||||||
daemon.flushTxPool(processModel.getMaker().getDepositTxHash(), processModel.getTaker().getDepositTxHash());
|
daemon.flushTxPool(txHashes);
|
||||||
} catch (Exception e2) {
|
} catch (Exception e2) {
|
||||||
log.warn("Error flushing deposit txs from pool for trade {}: {}\n", trade.getId(), e2.getMessage(), e2);
|
log.warn("Error flushing deposit txs from pool for trade {}: {}\n", trade.getId(), e2.getMessage(), e2);
|
||||||
}
|
}
|
||||||
@ -180,7 +195,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (processModel.getMaker().getDepositTxHex() == null) log.info("Arbitrator waiting for deposit request from maker for trade " + trade.getId());
|
if (processModel.getMaker().getDepositTxHex() == null) log.info("Arbitrator waiting for deposit request from maker for trade " + trade.getId());
|
||||||
if (processModel.getTaker().getDepositTxHex() == null) log.info("Arbitrator waiting for deposit request from taker for trade " + trade.getId());
|
if (processModel.getTaker().getDepositTxHex() == null && !trade.hasBuyerAsTakerWithoutDeposit()) log.info("Arbitrator waiting for deposit request from taker for trade " + trade.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,14 +53,20 @@ public class ArbitratorProcessReserveTx extends TradeTask {
|
|||||||
TradePeer sender = trade.getTradePeer(processModel.getTempTradePeerNodeAddress());
|
TradePeer sender = trade.getTradePeer(processModel.getTempTradePeerNodeAddress());
|
||||||
boolean isFromMaker = sender == trade.getMaker();
|
boolean isFromMaker = sender == trade.getMaker();
|
||||||
boolean isFromBuyer = isFromMaker ? offer.getDirection() == OfferDirection.BUY : offer.getDirection() == OfferDirection.SELL;
|
boolean isFromBuyer = isFromMaker ? offer.getDirection() == OfferDirection.BUY : offer.getDirection() == OfferDirection.SELL;
|
||||||
|
sender = isFromMaker ? processModel.getMaker() : processModel.getTaker();
|
||||||
|
BigInteger securityDeposit = isFromMaker ? isFromBuyer ? offer.getMaxBuyerSecurityDeposit() : offer.getMaxSellerSecurityDeposit() : isFromBuyer ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee();
|
||||||
|
sender.setSecurityDeposit(securityDeposit);
|
||||||
|
|
||||||
// TODO (woodser): if signer online, should never be called by maker?
|
// TODO (woodser): if signer online, should never be called by maker?
|
||||||
|
|
||||||
|
// process reserve tx unless from buyer as taker without deposit
|
||||||
|
boolean isFromBuyerAsTakerWithoutDeposit = isFromBuyer && !isFromMaker && trade.hasBuyerAsTakerWithoutDeposit();
|
||||||
|
if (!isFromBuyerAsTakerWithoutDeposit) {
|
||||||
|
|
||||||
// process reserve tx with expected values
|
// process reserve tx with expected values
|
||||||
BigInteger penaltyFee = HavenoUtils.multiply(isFromMaker ? offer.getAmount() : trade.getAmount(), offer.getPenaltyFeePct());
|
BigInteger penaltyFee = HavenoUtils.multiply(isFromMaker ? offer.getAmount() : trade.getAmount(), offer.getPenaltyFeePct());
|
||||||
BigInteger tradeFee = isFromMaker ? offer.getMaxMakerFee() : trade.getTakerFee();
|
BigInteger tradeFee = isFromMaker ? offer.getMaxMakerFee() : trade.getTakerFee();
|
||||||
BigInteger sendAmount = isFromBuyer ? BigInteger.ZERO : isFromMaker ? offer.getAmount() : trade.getAmount(); // maker reserve tx is for offer amount
|
BigInteger sendAmount = isFromBuyer ? BigInteger.ZERO : isFromMaker ? offer.getAmount() : trade.getAmount(); // maker reserve tx is for offer amount
|
||||||
BigInteger securityDeposit = isFromMaker ? isFromBuyer ? offer.getMaxBuyerSecurityDeposit() : offer.getMaxSellerSecurityDeposit() : isFromBuyer ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee();
|
|
||||||
MoneroTx verifiedTx;
|
MoneroTx verifiedTx;
|
||||||
try {
|
try {
|
||||||
verifiedTx = trade.getXmrWalletService().verifyReserveTx(
|
verifiedTx = trade.getXmrWalletService().verifyReserveTx(
|
||||||
@ -80,11 +86,11 @@ public class ArbitratorProcessReserveTx extends TradeTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// save reserve tx to model
|
// save reserve tx to model
|
||||||
TradePeer trader = isFromMaker ? processModel.getMaker() : processModel.getTaker();
|
sender.setSecurityDeposit(sender.getSecurityDeposit().subtract(verifiedTx.getFee())); // subtract mining fee from security deposit
|
||||||
trader.setSecurityDeposit(securityDeposit.subtract(verifiedTx.getFee())); // subtract mining fee from security deposit
|
sender.setReserveTxHash(request.getReserveTxHash());
|
||||||
trader.setReserveTxHash(request.getReserveTxHash());
|
sender.setReserveTxHex(request.getReserveTxHex());
|
||||||
trader.setReserveTxHex(request.getReserveTxHex());
|
sender.setReserveTxKey(request.getReserveTxKey());
|
||||||
trader.setReserveTxKey(request.getReserveTxKey());
|
}
|
||||||
|
|
||||||
// persist trade
|
// persist trade
|
||||||
processModel.getTradeManager().requestPersistence();
|
processModel.getTradeManager().requestPersistence();
|
||||||
|
@ -78,6 +78,7 @@ public class ArbitratorSendInitTradeOrMultisigRequests extends TradeTask {
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
null);
|
null);
|
||||||
|
|
||||||
// send request to taker
|
// send request to taker
|
||||||
@ -118,7 +119,7 @@ public class ArbitratorSendInitTradeOrMultisigRequests extends TradeTask {
|
|||||||
|
|
||||||
// ensure arbitrator has reserve txs
|
// ensure arbitrator has reserve txs
|
||||||
if (processModel.getMaker().getReserveTxHash() == null) throw new RuntimeException("Arbitrator does not have maker's reserve tx after initializing trade");
|
if (processModel.getMaker().getReserveTxHash() == null) throw new RuntimeException("Arbitrator does not have maker's reserve tx after initializing trade");
|
||||||
if (processModel.getTaker().getReserveTxHash() == null) throw new RuntimeException("Arbitrator does not have taker's reserve tx after initializing trade");
|
if (processModel.getTaker().getReserveTxHash() == null && !trade.hasBuyerAsTakerWithoutDeposit()) throw new RuntimeException("Arbitrator does not have taker's reserve tx after initializing trade");
|
||||||
|
|
||||||
// create wallet for multisig
|
// create wallet for multisig
|
||||||
MoneroWallet multisigWallet = trade.createWallet();
|
MoneroWallet multisigWallet = trade.createWallet();
|
||||||
|
@ -74,7 +74,7 @@ public class BuyerPreparePaymentSentMessage extends TradeTask {
|
|||||||
Preconditions.checkNotNull(trade.getSeller().getPaymentAccountPayload(), "Seller's payment account payload is null");
|
Preconditions.checkNotNull(trade.getSeller().getPaymentAccountPayload(), "Seller's payment account payload is null");
|
||||||
Preconditions.checkNotNull(trade.getAmount(), "trade.getTradeAmount() must not be null");
|
Preconditions.checkNotNull(trade.getAmount(), "trade.getTradeAmount() must not be null");
|
||||||
Preconditions.checkNotNull(trade.getMakerDepositTx(), "trade.getMakerDepositTx() must not be null");
|
Preconditions.checkNotNull(trade.getMakerDepositTx(), "trade.getMakerDepositTx() must not be null");
|
||||||
Preconditions.checkNotNull(trade.getTakerDepositTx(), "trade.getTakerDepositTx() must not be null");
|
if (!trade.hasBuyerAsTakerWithoutDeposit()) Preconditions.checkNotNull(trade.getTakerDepositTx(), "trade.getTakerDepositTx() must not be null");
|
||||||
checkNotNull(trade.getOffer(), "offer must not be null");
|
checkNotNull(trade.getOffer(), "offer must not be null");
|
||||||
|
|
||||||
// create payout tx if we have seller's updated multisig hex
|
// create payout tx if we have seller's updated multisig hex
|
||||||
|
@ -138,7 +138,8 @@ public class MakerSendInitTradeRequestToArbitrator extends TradeTask {
|
|||||||
trade.getSelf().getReserveTxHash(),
|
trade.getSelf().getReserveTxHash(),
|
||||||
trade.getSelf().getReserveTxHex(),
|
trade.getSelf().getReserveTxHex(),
|
||||||
trade.getSelf().getReserveTxKey(),
|
trade.getSelf().getReserveTxKey(),
|
||||||
model.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString());
|
model.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString(),
|
||||||
|
trade.getChallenge());
|
||||||
|
|
||||||
// send request to arbitrator
|
// send request to arbitrator
|
||||||
log.info("Sending {} with offerId {} and uid {} to arbitrator {}", arbitratorRequest.getClass().getSimpleName(), arbitratorRequest.getOfferId(), arbitratorRequest.getUid(), trade.getArbitrator().getNodeAddress());
|
log.info("Sending {} with offerId {} and uid {} to arbitrator {}", arbitratorRequest.getClass().getSimpleName(), arbitratorRequest.getOfferId(), arbitratorRequest.getUid(), trade.getArbitrator().getNodeAddress());
|
||||||
|
@ -83,7 +83,7 @@ public class MaybeSendSignContractRequest extends TradeTask {
|
|||||||
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out while getting lock to create deposit tx, tradeId=" + trade.getShortId());
|
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out while getting lock to create deposit tx, tradeId=" + trade.getShortId());
|
||||||
trade.startProtocolTimeout();
|
trade.startProtocolTimeout();
|
||||||
|
|
||||||
// collect relevant info
|
// collect info
|
||||||
Integer subaddressIndex = null;
|
Integer subaddressIndex = null;
|
||||||
boolean reserveExactAmount = false;
|
boolean reserveExactAmount = false;
|
||||||
if (trade instanceof MakerTrade) {
|
if (trade instanceof MakerTrade) {
|
||||||
@ -97,6 +97,7 @@ public class MaybeSendSignContractRequest extends TradeTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// attempt creating deposit tx
|
// attempt creating deposit tx
|
||||||
|
if (!trade.isBuyerAsTakerWithoutDeposit()) {
|
||||||
try {
|
try {
|
||||||
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||||
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
@ -131,19 +132,25 @@ public class MaybeSendSignContractRequest extends TradeTask {
|
|||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// reset protocol timeout
|
// reset protocol timeout
|
||||||
trade.addInitProgressStep();
|
trade.addInitProgressStep();
|
||||||
|
|
||||||
// update trade state
|
// update trade state
|
||||||
|
trade.getSelf().setPayoutAddressString(trade.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString()); // TODO (woodser): allow custom payout address?
|
||||||
|
trade.getSelf().setPaymentAccountPayload(trade.getProcessModel().getPaymentAccountPayload(trade.getSelf().getPaymentAccountId()));
|
||||||
|
trade.getSelf().setPaymentAccountPayloadHash(trade.getSelf().getPaymentAccountPayload().getHash());
|
||||||
BigInteger securityDeposit = trade instanceof BuyerTrade ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee();
|
BigInteger securityDeposit = trade instanceof BuyerTrade ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee();
|
||||||
|
if (depositTx == null) {
|
||||||
|
trade.getSelf().setSecurityDeposit(securityDeposit);
|
||||||
|
} else {
|
||||||
trade.getSelf().setSecurityDeposit(securityDeposit.subtract(depositTx.getFee()));
|
trade.getSelf().setSecurityDeposit(securityDeposit.subtract(depositTx.getFee()));
|
||||||
trade.getSelf().setDepositTx(depositTx);
|
trade.getSelf().setDepositTx(depositTx);
|
||||||
trade.getSelf().setDepositTxHash(depositTx.getHash());
|
trade.getSelf().setDepositTxHash(depositTx.getHash());
|
||||||
trade.getSelf().setDepositTxFee(depositTx.getFee());
|
trade.getSelf().setDepositTxFee(depositTx.getFee());
|
||||||
trade.getSelf().setReserveTxKeyImages(HavenoUtils.getInputKeyImages(depositTx));
|
trade.getSelf().setReserveTxKeyImages(HavenoUtils.getInputKeyImages(depositTx));
|
||||||
trade.getSelf().setPayoutAddressString(trade.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString()); // TODO (woodser): allow custom payout address?
|
}
|
||||||
trade.getSelf().setPaymentAccountPayload(trade.getProcessModel().getPaymentAccountPayload(trade.getSelf().getPaymentAccountId()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// maker signs deposit hash nonce to avoid challenge protocol
|
// maker signs deposit hash nonce to avoid challenge protocol
|
||||||
@ -161,7 +168,7 @@ public class MaybeSendSignContractRequest extends TradeTask {
|
|||||||
trade.getProcessModel().getAccountId(),
|
trade.getProcessModel().getAccountId(),
|
||||||
trade.getSelf().getPaymentAccountPayload().getHash(),
|
trade.getSelf().getPaymentAccountPayload().getHash(),
|
||||||
trade.getSelf().getPayoutAddressString(),
|
trade.getSelf().getPayoutAddressString(),
|
||||||
depositTx.getHash(),
|
depositTx == null ? null : depositTx.getHash(),
|
||||||
sig);
|
sig);
|
||||||
|
|
||||||
// send request to trading peer
|
// send request to trading peer
|
||||||
|
@ -63,20 +63,20 @@ public class ProcessSignContractRequest extends TradeTask {
|
|||||||
// extract fields from request
|
// extract fields from request
|
||||||
// TODO (woodser): verify request and from maker or taker
|
// TODO (woodser): verify request and from maker or taker
|
||||||
SignContractRequest request = (SignContractRequest) processModel.getTradeMessage();
|
SignContractRequest request = (SignContractRequest) processModel.getTradeMessage();
|
||||||
TradePeer trader = trade.getTradePeer(processModel.getTempTradePeerNodeAddress());
|
TradePeer sender = trade.getTradePeer(processModel.getTempTradePeerNodeAddress());
|
||||||
trader.setDepositTxHash(request.getDepositTxHash());
|
sender.setDepositTxHash(request.getDepositTxHash());
|
||||||
trader.setAccountId(request.getAccountId());
|
sender.setAccountId(request.getAccountId());
|
||||||
trader.setPaymentAccountPayloadHash(request.getPaymentAccountPayloadHash());
|
sender.setPaymentAccountPayloadHash(request.getPaymentAccountPayloadHash());
|
||||||
trader.setPayoutAddressString(request.getPayoutAddress());
|
sender.setPayoutAddressString(request.getPayoutAddress());
|
||||||
|
|
||||||
// maker sends witness signature of deposit tx hash
|
// maker sends witness signature of deposit tx hash
|
||||||
if (trader == trade.getMaker()) {
|
if (sender == trade.getMaker()) {
|
||||||
trader.setAccountAgeWitnessNonce(request.getDepositTxHash().getBytes(Charsets.UTF_8));
|
sender.setAccountAgeWitnessNonce(request.getDepositTxHash().getBytes(Charsets.UTF_8));
|
||||||
trader.setAccountAgeWitnessSignature(request.getAccountAgeWitnessSignatureOfDepositHash());
|
sender.setAccountAgeWitnessSignature(request.getAccountAgeWitnessSignatureOfDepositHash());
|
||||||
}
|
}
|
||||||
|
|
||||||
// sign contract only when both deposit txs hashes known
|
// sign contract only when received from both peers
|
||||||
if (processModel.getMaker().getDepositTxHash() == null || processModel.getTaker().getDepositTxHash() == null) {
|
if (processModel.getMaker().getPaymentAccountPayloadHash() == null || processModel.getTaker().getPaymentAccountPayloadHash() == null) {
|
||||||
complete();
|
complete();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -82,8 +82,8 @@ public class SendDepositRequest extends TradeTask {
|
|||||||
Version.getP2PMessageVersion(),
|
Version.getP2PMessageVersion(),
|
||||||
new Date().getTime(),
|
new Date().getTime(),
|
||||||
trade.getSelf().getContractSignature(),
|
trade.getSelf().getContractSignature(),
|
||||||
trade.getSelf().getDepositTx().getFullHex(),
|
trade.getSelf().getDepositTx() == null ? null : trade.getSelf().getDepositTx().getFullHex(),
|
||||||
trade.getSelf().getDepositTx().getKey(),
|
trade.getSelf().getDepositTx() == null ? null : trade.getSelf().getDepositTx().getKey(),
|
||||||
trade.getSelf().getPaymentAccountKey());
|
trade.getSelf().getPaymentAccountKey());
|
||||||
|
|
||||||
// update trade state
|
// update trade state
|
||||||
|
@ -47,8 +47,9 @@ public class TakerReserveTradeFunds extends TradeTask {
|
|||||||
throw new RuntimeException("Expected taker trade but was " + trade.getClass().getSimpleName() + " " + trade.getShortId() + ". That should never happen.");
|
throw new RuntimeException("Expected taker trade but was " + trade.getClass().getSimpleName() + " " + trade.getShortId() + ". That should never happen.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// create reserve tx
|
// create reserve tx unless deposit not required from buyer as taker
|
||||||
MoneroTxWallet reserveTx = null;
|
MoneroTxWallet reserveTx = null;
|
||||||
|
if (!trade.isBuyerAsTakerWithoutDeposit()) {
|
||||||
synchronized (HavenoUtils.xmrWalletService.getWalletLock()) {
|
synchronized (HavenoUtils.xmrWalletService.getWalletLock()) {
|
||||||
|
|
||||||
// check for timeout
|
// check for timeout
|
||||||
@ -59,7 +60,7 @@ public class TakerReserveTradeFunds extends TradeTask {
|
|||||||
BigInteger penaltyFee = HavenoUtils.multiply(trade.getAmount(), trade.getOffer().getPenaltyFeePct());
|
BigInteger penaltyFee = HavenoUtils.multiply(trade.getAmount(), trade.getOffer().getPenaltyFeePct());
|
||||||
BigInteger takerFee = trade.getTakerFee();
|
BigInteger takerFee = trade.getTakerFee();
|
||||||
BigInteger sendAmount = trade.getOffer().getDirection() == OfferDirection.BUY ? trade.getAmount() : BigInteger.ZERO;
|
BigInteger sendAmount = trade.getOffer().getDirection() == OfferDirection.BUY ? trade.getAmount() : BigInteger.ZERO;
|
||||||
BigInteger securityDeposit = trade.getOffer().getDirection() == OfferDirection.BUY ? trade.getSellerSecurityDepositBeforeMiningFee() : trade.getBuyerSecurityDepositBeforeMiningFee();
|
BigInteger securityDeposit = trade.getSecurityDepositBeforeMiningFee();
|
||||||
String returnAddress = trade.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
String returnAddress = trade.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||||
|
|
||||||
// attempt creating reserve tx
|
// attempt creating reserve tx
|
||||||
@ -94,7 +95,6 @@ public class TakerReserveTradeFunds extends TradeTask {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// reset protocol timeout
|
// reset protocol timeout
|
||||||
trade.startProtocolTimeout();
|
trade.startProtocolTimeout();
|
||||||
|
|
||||||
@ -104,6 +104,7 @@ public class TakerReserveTradeFunds extends TradeTask {
|
|||||||
trade.getTaker().setReserveTxKey(reserveTx.getKey());
|
trade.getTaker().setReserveTxKey(reserveTx.getKey());
|
||||||
trade.getTaker().setReserveTxKeyImages(HavenoUtils.getInputKeyImages(reserveTx));
|
trade.getTaker().setReserveTxKeyImages(HavenoUtils.getInputKeyImages(reserveTx));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// save process state
|
// save process state
|
||||||
processModel.setReserveTx(reserveTx); // TODO: remove this? how is it used?
|
processModel.setReserveTx(reserveTx); // TODO: remove this? how is it used?
|
||||||
|
@ -48,7 +48,9 @@ public class TakerSendInitTradeRequestToArbitrator extends TradeTask {
|
|||||||
InitTradeRequest sourceRequest = (InitTradeRequest) processModel.getTradeMessage(); // arbitrator's InitTradeRequest to taker
|
InitTradeRequest sourceRequest = (InitTradeRequest) processModel.getTradeMessage(); // arbitrator's InitTradeRequest to taker
|
||||||
checkNotNull(sourceRequest);
|
checkNotNull(sourceRequest);
|
||||||
checkTradeId(processModel.getOfferId(), sourceRequest);
|
checkTradeId(processModel.getOfferId(), sourceRequest);
|
||||||
if (trade.getSelf().getReserveTxHash() == null || trade.getSelf().getReserveTxHash().isEmpty()) throw new IllegalStateException("Reserve tx id is not initialized: " + trade.getSelf().getReserveTxHash());
|
if (!trade.isBuyerAsTakerWithoutDeposit() && trade.getSelf().getReserveTxHash() == null) {
|
||||||
|
throw new IllegalStateException("Taker reserve tx id is not initialized: " + trade.getSelf().getReserveTxHash());
|
||||||
|
}
|
||||||
|
|
||||||
// create request to arbitrator
|
// create request to arbitrator
|
||||||
Offer offer = processModel.getOffer();
|
Offer offer = processModel.getOffer();
|
||||||
@ -73,7 +75,8 @@ public class TakerSendInitTradeRequestToArbitrator extends TradeTask {
|
|||||||
trade.getSelf().getReserveTxHash(),
|
trade.getSelf().getReserveTxHash(),
|
||||||
trade.getSelf().getReserveTxHex(),
|
trade.getSelf().getReserveTxHex(),
|
||||||
trade.getSelf().getReserveTxKey(),
|
trade.getSelf().getReserveTxKey(),
|
||||||
model.getXmrWalletService().getAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString());
|
model.getXmrWalletService().getAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString(),
|
||||||
|
trade.getChallenge());
|
||||||
|
|
||||||
// send request to arbitrator
|
// send request to arbitrator
|
||||||
log.info("Sending {} with offerId {} and uid {} to arbitrator {}", arbitratorRequest.getClass().getSimpleName(), arbitratorRequest.getOfferId(), arbitratorRequest.getUid(), trade.getArbitrator().getNodeAddress());
|
log.info("Sending {} with offerId {} and uid {} to arbitrator {}", arbitratorRequest.getClass().getSimpleName(), arbitratorRequest.getOfferId(), arbitratorRequest.getUid(), trade.getArbitrator().getNodeAddress());
|
||||||
|
@ -47,7 +47,9 @@ public class TakerSendInitTradeRequestToMaker extends TradeTask {
|
|||||||
runInterceptHook();
|
runInterceptHook();
|
||||||
|
|
||||||
// verify trade state
|
// verify trade state
|
||||||
if (trade.getSelf().getReserveTxHash() == null || trade.getSelf().getReserveTxHash().isEmpty()) throw new IllegalStateException("Reserve tx id is not initialized: " + trade.getSelf().getReserveTxHash());
|
if (!trade.isBuyerAsTakerWithoutDeposit() && trade.getSelf().getReserveTxHash() == null) {
|
||||||
|
throw new IllegalStateException("Taker reserve tx id is not initialized: " + trade.getSelf().getReserveTxHash());
|
||||||
|
}
|
||||||
|
|
||||||
// collect fields
|
// collect fields
|
||||||
Offer offer = model.getOffer();
|
Offer offer = model.getOffer();
|
||||||
@ -55,6 +57,7 @@ public class TakerSendInitTradeRequestToMaker extends TradeTask {
|
|||||||
P2PService p2PService = processModel.getP2PService();
|
P2PService p2PService = processModel.getP2PService();
|
||||||
XmrWalletService walletService = model.getXmrWalletService();
|
XmrWalletService walletService = model.getXmrWalletService();
|
||||||
String payoutAddress = walletService.getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
String payoutAddress = walletService.getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||||
|
String challenge = model.getChallenge();
|
||||||
|
|
||||||
// taker signs offer using offer id as nonce to avoid challenge protocol
|
// taker signs offer using offer id as nonce to avoid challenge protocol
|
||||||
byte[] sig = HavenoUtils.sign(p2PService.getKeyRing(), offer.getId());
|
byte[] sig = HavenoUtils.sign(p2PService.getKeyRing(), offer.getId());
|
||||||
@ -81,7 +84,8 @@ public class TakerSendInitTradeRequestToMaker extends TradeTask {
|
|||||||
null, // reserve tx not sent from taker to maker
|
null, // reserve tx not sent from taker to maker
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
payoutAddress);
|
payoutAddress,
|
||||||
|
challenge);
|
||||||
|
|
||||||
// send request to maker
|
// send request to maker
|
||||||
log.info("Sending {} with offerId {} and uid {} to maker {}", makerRequest.getClass().getSimpleName(), makerRequest.getOfferId(), makerRequest.getUid(), trade.getMaker().getNodeAddress());
|
log.info("Sending {} with offerId {} and uid {} to maker {}", makerRequest.getClass().getSimpleName(), makerRequest.getOfferId(), makerRequest.getUid(), trade.getMaker().getNodeAddress());
|
||||||
|
@ -616,14 +616,14 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||||||
requestPersistence();
|
requestPersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBuyerSecurityDepositAsPercent(double buyerSecurityDepositAsPercent, PaymentAccount paymentAccount) {
|
public void setSecurityDepositAsPercent(double securityDepositAsPercent, PaymentAccount paymentAccount) {
|
||||||
double max = Restrictions.getMaxBuyerSecurityDepositAsPercent();
|
double max = Restrictions.getMaxSecurityDepositAsPercent();
|
||||||
double min = Restrictions.getMinBuyerSecurityDepositAsPercent();
|
double min = Restrictions.getMinSecurityDepositAsPercent();
|
||||||
|
|
||||||
if (PaymentAccountUtil.isCryptoCurrencyAccount(paymentAccount))
|
if (PaymentAccountUtil.isCryptoCurrencyAccount(paymentAccount))
|
||||||
prefPayload.setBuyerSecurityDepositAsPercentForCrypto(Math.min(max, Math.max(min, buyerSecurityDepositAsPercent)));
|
prefPayload.setSecurityDepositAsPercentForCrypto(Math.min(max, Math.max(min, securityDepositAsPercent)));
|
||||||
else
|
else
|
||||||
prefPayload.setBuyerSecurityDepositAsPercent(Math.min(max, Math.max(min, buyerSecurityDepositAsPercent)));
|
prefPayload.setSecurityDepositAsPercent(Math.min(max, Math.max(min, securityDepositAsPercent)));
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -755,6 +755,11 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||||||
requestPersistence();
|
requestPersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setShowPrivateOffers(boolean value) {
|
||||||
|
prefPayload.setShowPrivateOffers(value);
|
||||||
|
requestPersistence();
|
||||||
|
}
|
||||||
|
|
||||||
public void setDenyApiTaker(boolean value) {
|
public void setDenyApiTaker(boolean value) {
|
||||||
prefPayload.setDenyApiTaker(value);
|
prefPayload.setDenyApiTaker(value);
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
@ -838,16 +843,16 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||||||
return prefPayload.isSplitOfferOutput();
|
return prefPayload.isSplitOfferOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getBuyerSecurityDepositAsPercent(PaymentAccount paymentAccount) {
|
public double getSecurityDepositAsPercent(PaymentAccount paymentAccount) {
|
||||||
double value = PaymentAccountUtil.isCryptoCurrencyAccount(paymentAccount) ?
|
double value = PaymentAccountUtil.isCryptoCurrencyAccount(paymentAccount) ?
|
||||||
prefPayload.getBuyerSecurityDepositAsPercentForCrypto() : prefPayload.getBuyerSecurityDepositAsPercent();
|
prefPayload.getSecurityDepositAsPercentForCrypto() : prefPayload.getSecurityDepositAsPercent();
|
||||||
|
|
||||||
if (value < Restrictions.getMinBuyerSecurityDepositAsPercent()) {
|
if (value < Restrictions.getMinSecurityDepositAsPercent()) {
|
||||||
value = Restrictions.getMinBuyerSecurityDepositAsPercent();
|
value = Restrictions.getMinSecurityDepositAsPercent();
|
||||||
setBuyerSecurityDepositAsPercent(value, paymentAccount);
|
setSecurityDepositAsPercent(value, paymentAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
return value == 0 ? Restrictions.getDefaultBuyerSecurityDepositAsPercent() : value;
|
return value == 0 ? Restrictions.getDefaultSecurityDepositAsPercent() : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -41,7 +41,7 @@ import java.util.Map;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static haveno.core.xmr.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
import static haveno.core.xmr.wallet.Restrictions.getDefaultSecurityDepositAsPercent;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Data
|
@Data
|
||||||
@ -120,10 +120,10 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
|||||||
private String rpcPw;
|
private String rpcPw;
|
||||||
@Nullable
|
@Nullable
|
||||||
private String takeOfferSelectedPaymentAccountId;
|
private String takeOfferSelectedPaymentAccountId;
|
||||||
private double buyerSecurityDepositAsPercent = getDefaultBuyerSecurityDepositAsPercent();
|
private double securityDepositAsPercent = getDefaultSecurityDepositAsPercent();
|
||||||
private int ignoreDustThreshold = 600;
|
private int ignoreDustThreshold = 600;
|
||||||
private int clearDataAfterDays = Preferences.CLEAR_DATA_AFTER_DAYS_INITIAL;
|
private int clearDataAfterDays = Preferences.CLEAR_DATA_AFTER_DAYS_INITIAL;
|
||||||
private double buyerSecurityDepositAsPercentForCrypto = getDefaultBuyerSecurityDepositAsPercent();
|
private double securityDepositAsPercentForCrypto = getDefaultSecurityDepositAsPercent();
|
||||||
private int blockNotifyPort;
|
private int blockNotifyPort;
|
||||||
private boolean tacAcceptedV120;
|
private boolean tacAcceptedV120;
|
||||||
private double bsqAverageTrimThreshold = 0.05;
|
private double bsqAverageTrimThreshold = 0.05;
|
||||||
@ -134,6 +134,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
|||||||
// Added in 1.5.5
|
// Added in 1.5.5
|
||||||
private boolean hideNonAccountPaymentMethods;
|
private boolean hideNonAccountPaymentMethods;
|
||||||
private boolean showOffersMatchingMyAccounts;
|
private boolean showOffersMatchingMyAccounts;
|
||||||
|
private boolean showPrivateOffers;
|
||||||
private boolean denyApiTaker;
|
private boolean denyApiTaker;
|
||||||
private boolean notifyOnPreRelease;
|
private boolean notifyOnPreRelease;
|
||||||
|
|
||||||
@ -193,10 +194,10 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
|||||||
.setUseStandbyMode(useStandbyMode)
|
.setUseStandbyMode(useStandbyMode)
|
||||||
.setUseSoundForNotifications(useSoundForNotifications)
|
.setUseSoundForNotifications(useSoundForNotifications)
|
||||||
.setUseSoundForNotificationsInitialized(useSoundForNotificationsInitialized)
|
.setUseSoundForNotificationsInitialized(useSoundForNotificationsInitialized)
|
||||||
.setBuyerSecurityDepositAsPercent(buyerSecurityDepositAsPercent)
|
.setSecurityDepositAsPercent(securityDepositAsPercent)
|
||||||
.setIgnoreDustThreshold(ignoreDustThreshold)
|
.setIgnoreDustThreshold(ignoreDustThreshold)
|
||||||
.setClearDataAfterDays(clearDataAfterDays)
|
.setClearDataAfterDays(clearDataAfterDays)
|
||||||
.setBuyerSecurityDepositAsPercentForCrypto(buyerSecurityDepositAsPercentForCrypto)
|
.setSecurityDepositAsPercentForCrypto(securityDepositAsPercentForCrypto)
|
||||||
.setBlockNotifyPort(blockNotifyPort)
|
.setBlockNotifyPort(blockNotifyPort)
|
||||||
.setTacAcceptedV120(tacAcceptedV120)
|
.setTacAcceptedV120(tacAcceptedV120)
|
||||||
.setBsqAverageTrimThreshold(bsqAverageTrimThreshold)
|
.setBsqAverageTrimThreshold(bsqAverageTrimThreshold)
|
||||||
@ -205,6 +206,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
|||||||
.collect(Collectors.toList()))
|
.collect(Collectors.toList()))
|
||||||
.setHideNonAccountPaymentMethods(hideNonAccountPaymentMethods)
|
.setHideNonAccountPaymentMethods(hideNonAccountPaymentMethods)
|
||||||
.setShowOffersMatchingMyAccounts(showOffersMatchingMyAccounts)
|
.setShowOffersMatchingMyAccounts(showOffersMatchingMyAccounts)
|
||||||
|
.setShowPrivateOffers(showPrivateOffers)
|
||||||
.setDenyApiTaker(denyApiTaker)
|
.setDenyApiTaker(denyApiTaker)
|
||||||
.setNotifyOnPreRelease(notifyOnPreRelease);
|
.setNotifyOnPreRelease(notifyOnPreRelease);
|
||||||
|
|
||||||
@ -297,10 +299,10 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
|||||||
proto.getRpcUser().isEmpty() ? null : proto.getRpcUser(),
|
proto.getRpcUser().isEmpty() ? null : proto.getRpcUser(),
|
||||||
proto.getRpcPw().isEmpty() ? null : proto.getRpcPw(),
|
proto.getRpcPw().isEmpty() ? null : proto.getRpcPw(),
|
||||||
proto.getTakeOfferSelectedPaymentAccountId().isEmpty() ? null : proto.getTakeOfferSelectedPaymentAccountId(),
|
proto.getTakeOfferSelectedPaymentAccountId().isEmpty() ? null : proto.getTakeOfferSelectedPaymentAccountId(),
|
||||||
proto.getBuyerSecurityDepositAsPercent(),
|
proto.getSecurityDepositAsPercent(),
|
||||||
proto.getIgnoreDustThreshold(),
|
proto.getIgnoreDustThreshold(),
|
||||||
proto.getClearDataAfterDays(),
|
proto.getClearDataAfterDays(),
|
||||||
proto.getBuyerSecurityDepositAsPercentForCrypto(),
|
proto.getSecurityDepositAsPercentForCrypto(),
|
||||||
proto.getBlockNotifyPort(),
|
proto.getBlockNotifyPort(),
|
||||||
proto.getTacAcceptedV120(),
|
proto.getTacAcceptedV120(),
|
||||||
proto.getBsqAverageTrimThreshold(),
|
proto.getBsqAverageTrimThreshold(),
|
||||||
@ -310,6 +312,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
|||||||
.collect(Collectors.toList())),
|
.collect(Collectors.toList())),
|
||||||
proto.getHideNonAccountPaymentMethods(),
|
proto.getHideNonAccountPaymentMethods(),
|
||||||
proto.getShowOffersMatchingMyAccounts(),
|
proto.getShowOffersMatchingMyAccounts(),
|
||||||
|
proto.getShowPrivateOffers(),
|
||||||
proto.getDenyApiTaker(),
|
proto.getDenyApiTaker(),
|
||||||
proto.getNotifyOnPreRelease(),
|
proto.getNotifyOnPreRelease(),
|
||||||
XmrNodeSettings.fromProto(proto.getXmrNodeSettings())
|
XmrNodeSettings.fromProto(proto.getXmrNodeSettings())
|
||||||
|
@ -47,35 +47,35 @@ public class CoinUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param value Btc amount to be converted to percent value. E.g. 0.01 BTC is 1% (of 1 BTC)
|
* @param value Xmr amount to be converted to percent value. E.g. 0.01 XMR is 1% (of 1 XMR)
|
||||||
* @return The percentage value as double (e.g. 1% is 0.01)
|
* @return The percentage value as double (e.g. 1% is 0.01)
|
||||||
*/
|
*/
|
||||||
public static double getAsPercentPerBtc(BigInteger value) {
|
public static double getAsPercentPerXmr(BigInteger value) {
|
||||||
return getAsPercentPerBtc(value, HavenoUtils.xmrToAtomicUnits(1.0));
|
return getAsPercentPerXmr(value, HavenoUtils.xmrToAtomicUnits(1.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param part Btc amount to be converted to percent value, based on total value passed.
|
* @param part Xmr amount to be converted to percent value, based on total value passed.
|
||||||
* E.g. 0.1 BTC is 25% (of 0.4 BTC)
|
* E.g. 0.1 XMR is 25% (of 0.4 XMR)
|
||||||
* @param total Total Btc amount the percentage part is calculated from
|
* @param total Total Xmr amount the percentage part is calculated from
|
||||||
*
|
*
|
||||||
* @return The percentage value as double (e.g. 1% is 0.01)
|
* @return The percentage value as double (e.g. 1% is 0.01)
|
||||||
*/
|
*/
|
||||||
public static double getAsPercentPerBtc(BigInteger part, BigInteger total) {
|
public static double getAsPercentPerXmr(BigInteger part, BigInteger total) {
|
||||||
return MathUtils.roundDouble(HavenoUtils.divide(part == null ? BigInteger.ZERO : part, total == null ? BigInteger.valueOf(1) : total), 4);
|
return MathUtils.roundDouble(HavenoUtils.divide(part == null ? BigInteger.ZERO : part, total == null ? BigInteger.valueOf(1) : total), 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param percent The percentage value as double (e.g. 1% is 0.01)
|
* @param percent The percentage value as double (e.g. 1% is 0.01)
|
||||||
* @param amount The amount as atomic units for the percentage calculation
|
* @param amount The amount as atomic units for the percentage calculation
|
||||||
* @return The percentage as atomic units (e.g. 1% of 1 BTC is 0.01 BTC)
|
* @return The percentage as atomic units (e.g. 1% of 1 XMR is 0.01 XMR)
|
||||||
*/
|
*/
|
||||||
public static BigInteger getPercentOfAmount(double percent, BigInteger amount) {
|
public static BigInteger getPercentOfAmount(double percent, BigInteger amount) {
|
||||||
if (amount == null) amount = BigInteger.ZERO;
|
if (amount == null) amount = BigInteger.ZERO;
|
||||||
return BigDecimal.valueOf(percent).multiply(new BigDecimal(amount)).setScale(8, RoundingMode.DOWN).toBigInteger();
|
return BigDecimal.valueOf(percent).multiply(new BigDecimal(amount)).setScale(8, RoundingMode.DOWN).toBigInteger();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BigInteger getRoundedAmount(BigInteger amount, Price price, long maxTradeLimit, String currencyCode, String paymentMethodId) {
|
public static BigInteger getRoundedAmount(BigInteger amount, Price price, Long maxTradeLimit, String currencyCode, String paymentMethodId) {
|
||||||
if (PaymentMethod.isRoundedForAtmCash(paymentMethodId)) {
|
if (PaymentMethod.isRoundedForAtmCash(paymentMethodId)) {
|
||||||
return getRoundedAtmCashAmount(amount, price, maxTradeLimit);
|
return getRoundedAtmCashAmount(amount, price, maxTradeLimit);
|
||||||
} else if (CurrencyUtil.isVolumeRoundedToNearestUnit(currencyCode)) {
|
} else if (CurrencyUtil.isVolumeRoundedToNearestUnit(currencyCode)) {
|
||||||
@ -86,7 +86,7 @@ public class CoinUtil {
|
|||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BigInteger getRoundedAtmCashAmount(BigInteger amount, Price price, long maxTradeLimit) {
|
public static BigInteger getRoundedAtmCashAmount(BigInteger amount, Price price, Long maxTradeLimit) {
|
||||||
return getAdjustedAmount(amount, price, maxTradeLimit, 10);
|
return getAdjustedAmount(amount, price, maxTradeLimit, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,11 +99,11 @@ public class CoinUtil {
|
|||||||
* @param maxTradeLimit The max. trade limit of the users account, in atomic units.
|
* @param maxTradeLimit The max. trade limit of the users account, in atomic units.
|
||||||
* @return The adjusted amount
|
* @return The adjusted amount
|
||||||
*/
|
*/
|
||||||
public static BigInteger getRoundedAmountUnit(BigInteger amount, Price price, long maxTradeLimit) {
|
public static BigInteger getRoundedAmountUnit(BigInteger amount, Price price, Long maxTradeLimit) {
|
||||||
return getAdjustedAmount(amount, price, maxTradeLimit, 1);
|
return getAdjustedAmount(amount, price, maxTradeLimit, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BigInteger getRoundedAmount4Decimals(BigInteger amount, Price price, long maxTradeLimit) {
|
public static BigInteger getRoundedAmount4Decimals(BigInteger amount, Price price, Long maxTradeLimit) {
|
||||||
DecimalFormat decimalFormat = new DecimalFormat("#.####");
|
DecimalFormat decimalFormat = new DecimalFormat("#.####");
|
||||||
double roundedXmrAmount = Double.parseDouble(decimalFormat.format(HavenoUtils.atomicUnitsToXmr(amount)));
|
double roundedXmrAmount = Double.parseDouble(decimalFormat.format(HavenoUtils.atomicUnitsToXmr(amount)));
|
||||||
return HavenoUtils.xmrToAtomicUnits(roundedXmrAmount);
|
return HavenoUtils.xmrToAtomicUnits(roundedXmrAmount);
|
||||||
@ -121,7 +121,7 @@ public class CoinUtil {
|
|||||||
* @return The adjusted amount
|
* @return The adjusted amount
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static BigInteger getAdjustedAmount(BigInteger amount, Price price, long maxTradeLimit, int factor) {
|
static BigInteger getAdjustedAmount(BigInteger amount, Price price, Long maxTradeLimit, int factor) {
|
||||||
checkArgument(
|
checkArgument(
|
||||||
amount.longValueExact() >= Restrictions.getMinTradeAmount().longValueExact(),
|
amount.longValueExact() >= Restrictions.getMinTradeAmount().longValueExact(),
|
||||||
"amount needs to be above minimum of " + HavenoUtils.atomicUnitsToXmr(Restrictions.getMinTradeAmount()) + " xmr"
|
"amount needs to be above minimum of " + HavenoUtils.atomicUnitsToXmr(Restrictions.getMinTradeAmount()) + " xmr"
|
||||||
@ -163,11 +163,13 @@ public class CoinUtil {
|
|||||||
|
|
||||||
// If we are above our trade limit we reduce the amount by the smallestUnitForAmount
|
// If we are above our trade limit we reduce the amount by the smallestUnitForAmount
|
||||||
BigInteger smallestUnitForAmountUnadjusted = price.getAmountByVolume(smallestUnitForVolume);
|
BigInteger smallestUnitForAmountUnadjusted = price.getAmountByVolume(smallestUnitForVolume);
|
||||||
|
if (maxTradeLimit != null) {
|
||||||
while (adjustedAmount > maxTradeLimit) {
|
while (adjustedAmount > maxTradeLimit) {
|
||||||
adjustedAmount -= smallestUnitForAmountUnadjusted.longValueExact();
|
adjustedAmount -= smallestUnitForAmountUnadjusted.longValueExact();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
adjustedAmount = Math.max(minTradeAmount, adjustedAmount);
|
adjustedAmount = Math.max(minTradeAmount, adjustedAmount);
|
||||||
adjustedAmount = Math.min(maxTradeLimit, adjustedAmount);
|
if (maxTradeLimit != null) adjustedAmount = Math.min(maxTradeLimit, adjustedAmount);
|
||||||
return BigInteger.valueOf(adjustedAmount);
|
return BigInteger.valueOf(adjustedAmount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,11 +24,13 @@ import org.bitcoinj.core.Coin;
|
|||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
|
||||||
public class Restrictions {
|
public class Restrictions {
|
||||||
|
|
||||||
|
// configure restrictions
|
||||||
|
public static final double MIN_SECURITY_DEPOSIT_PCT = 0.15;
|
||||||
|
public static final double MAX_SECURITY_DEPOSIT_PCT = 0.5;
|
||||||
public static BigInteger MIN_TRADE_AMOUNT = HavenoUtils.xmrToAtomicUnits(0.1);
|
public static BigInteger MIN_TRADE_AMOUNT = HavenoUtils.xmrToAtomicUnits(0.1);
|
||||||
public static BigInteger MIN_BUYER_SECURITY_DEPOSIT = HavenoUtils.xmrToAtomicUnits(0.1);
|
public static BigInteger MIN_SECURITY_DEPOSIT = HavenoUtils.xmrToAtomicUnits(0.1);
|
||||||
// For the seller we use a fixed one as there is no way the seller can cancel the trade
|
|
||||||
// To make it editable would just increase complexity.
|
|
||||||
public static BigInteger MIN_SELLER_SECURITY_DEPOSIT = MIN_BUYER_SECURITY_DEPOSIT;
|
|
||||||
// At mediation we require a min. payout to the losing party to keep incentive for the trader to accept the
|
// At mediation we require a min. payout to the losing party to keep incentive for the trader to accept the
|
||||||
// mediated payout. For Refund agent cases we do not have that restriction.
|
// mediated payout. For Refund agent cases we do not have that restriction.
|
||||||
private static BigInteger MIN_REFUND_AT_MEDIATED_DISPUTE;
|
private static BigInteger MIN_REFUND_AT_MEDIATED_DISPUTE;
|
||||||
@ -53,31 +55,20 @@ public class Restrictions {
|
|||||||
return MIN_TRADE_AMOUNT;
|
return MIN_TRADE_AMOUNT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static double getDefaultBuyerSecurityDepositAsPercent() {
|
public static double getDefaultSecurityDepositAsPercent() {
|
||||||
return 0.15; // 15% of trade amount.
|
return MIN_SECURITY_DEPOSIT_PCT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static double getMinBuyerSecurityDepositAsPercent() {
|
public static double getMinSecurityDepositAsPercent() {
|
||||||
return 0.15; // 15% of trade amount.
|
return MIN_SECURITY_DEPOSIT_PCT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static double getMaxBuyerSecurityDepositAsPercent() {
|
public static double getMaxSecurityDepositAsPercent() {
|
||||||
return 0.5; // 50% of trade amount. For a 1 BTC trade it is about 3500 USD @ 7000 USD/BTC
|
return MAX_SECURITY_DEPOSIT_PCT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We use MIN_BUYER_SECURITY_DEPOSIT as well as lower bound in case of small trade amounts.
|
public static BigInteger getMinSecurityDeposit() {
|
||||||
// So 0.0005 BTC is the min. buyer security deposit even with amount of 0.0001 BTC and 0.05% percentage value.
|
return MIN_SECURITY_DEPOSIT;
|
||||||
public static BigInteger getMinBuyerSecurityDeposit() {
|
|
||||||
return MIN_BUYER_SECURITY_DEPOSIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static double getSellerSecurityDepositAsPercent() {
|
|
||||||
return 0.15; // 15% of trade amount.
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BigInteger getMinSellerSecurityDeposit() {
|
|
||||||
return MIN_SELLER_SECURITY_DEPOSIT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This value must be lower than MIN_BUYER_SECURITY_DEPOSIT and SELLER_SECURITY_DEPOSIT
|
// This value must be lower than MIN_BUYER_SECURITY_DEPOSIT and SELLER_SECURITY_DEPOSIT
|
||||||
|
2048
core/src/main/resources/bip39_english.txt
Normal file
2048
core/src/main/resources/bip39_english.txt
Normal file
File diff suppressed because it is too large
Load Diff
@ -43,6 +43,8 @@ shared.buyMonero=Buy Monero
|
|||||||
shared.sellMonero=Sell Monero
|
shared.sellMonero=Sell Monero
|
||||||
shared.buyCurrency=Buy {0}
|
shared.buyCurrency=Buy {0}
|
||||||
shared.sellCurrency=Sell {0}
|
shared.sellCurrency=Sell {0}
|
||||||
|
shared.buyCurrencyLocked=Buy {0} 🔒
|
||||||
|
shared.sellCurrencyLocked=Sell {0} 🔒
|
||||||
shared.buyingXMRWith=buying XMR with {0}
|
shared.buyingXMRWith=buying XMR with {0}
|
||||||
shared.sellingXMRFor=selling XMR for {0}
|
shared.sellingXMRFor=selling XMR for {0}
|
||||||
shared.buyingCurrency=buying {0} (selling XMR)
|
shared.buyingCurrency=buying {0} (selling XMR)
|
||||||
@ -349,6 +351,7 @@ market.trades.showVolumeInUSD=Show volume in USD
|
|||||||
offerbook.createOffer=Create offer
|
offerbook.createOffer=Create offer
|
||||||
offerbook.takeOffer=Take offer
|
offerbook.takeOffer=Take offer
|
||||||
offerbook.takeOffer.createAccount=Create account and take offer
|
offerbook.takeOffer.createAccount=Create account and take offer
|
||||||
|
offerbook.takeOffer.enterChallenge=Enter the offer passphrase
|
||||||
offerbook.trader=Trader
|
offerbook.trader=Trader
|
||||||
offerbook.offerersBankId=Maker''s bank ID (BIC/SWIFT): {0}
|
offerbook.offerersBankId=Maker''s bank ID (BIC/SWIFT): {0}
|
||||||
offerbook.offerersBankName=Maker''s bank name: {0}
|
offerbook.offerersBankName=Maker''s bank name: {0}
|
||||||
@ -360,6 +363,8 @@ offerbook.availableOffersToSell=Sell {0} for {1}
|
|||||||
offerbook.filterByCurrency=Choose currency
|
offerbook.filterByCurrency=Choose currency
|
||||||
offerbook.filterByPaymentMethod=Choose payment method
|
offerbook.filterByPaymentMethod=Choose payment method
|
||||||
offerbook.matchingOffers=Offers matching my accounts
|
offerbook.matchingOffers=Offers matching my accounts
|
||||||
|
offerbook.filterNoDeposit=No deposit
|
||||||
|
offerbook.noDepositOffers=Offers with no deposit (passphrase required)
|
||||||
offerbook.timeSinceSigning=Account info
|
offerbook.timeSinceSigning=Account info
|
||||||
offerbook.timeSinceSigning.info.arbitrator=signed by an arbitrator and can sign peer accounts
|
offerbook.timeSinceSigning.info.arbitrator=signed by an arbitrator and can sign peer accounts
|
||||||
offerbook.timeSinceSigning.info.peer=signed by a peer, waiting %d days for limits to be lifted
|
offerbook.timeSinceSigning.info.peer=signed by a peer, waiting %d days for limits to be lifted
|
||||||
@ -527,7 +532,10 @@ createOffer.setDepositAsBuyer=Set my security deposit as buyer (%)
|
|||||||
createOffer.setDepositForBothTraders=Set both traders' security deposit (%)
|
createOffer.setDepositForBothTraders=Set both traders' security deposit (%)
|
||||||
createOffer.securityDepositInfo=Your buyer''s security deposit will be {0}
|
createOffer.securityDepositInfo=Your buyer''s security deposit will be {0}
|
||||||
createOffer.securityDepositInfoAsBuyer=Your security deposit as buyer will be {0}
|
createOffer.securityDepositInfoAsBuyer=Your security deposit as buyer will be {0}
|
||||||
createOffer.minSecurityDepositUsed=Min. buyer security deposit is used
|
createOffer.minSecurityDepositUsed=Minimum security deposit is used
|
||||||
|
createOffer.buyerAsTakerWithoutDeposit=No deposit required from buyer (passphrase protected)
|
||||||
|
createOffer.myDeposit=My security deposit (%)
|
||||||
|
createOffer.myDepositInfo=Your security deposit will be {0}
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -553,6 +561,8 @@ takeOffer.fundsBox.networkFee=Total mining fees
|
|||||||
takeOffer.fundsBox.takeOfferSpinnerInfo=Taking offer: {0}
|
takeOffer.fundsBox.takeOfferSpinnerInfo=Taking offer: {0}
|
||||||
takeOffer.fundsBox.paymentLabel=Haveno trade with ID {0}
|
takeOffer.fundsBox.paymentLabel=Haveno trade with ID {0}
|
||||||
takeOffer.fundsBox.fundsStructure=({0} security deposit, {1} trade fee)
|
takeOffer.fundsBox.fundsStructure=({0} security deposit, {1} trade fee)
|
||||||
|
takeOffer.fundsBox.noFundingRequiredTitle=No funding required
|
||||||
|
takeOffer.fundsBox.noFundingRequiredDescription=Get the offer passphrase from the seller outside Haveno to take this offer.
|
||||||
takeOffer.success.headline=You have successfully taken an offer.
|
takeOffer.success.headline=You have successfully taken an offer.
|
||||||
takeOffer.success.info=You can see the status of your trade at \"Portfolio/Open trades\".
|
takeOffer.success.info=You can see the status of your trade at \"Portfolio/Open trades\".
|
||||||
takeOffer.error.message=An error occurred when taking the offer.\n\n{0}
|
takeOffer.error.message=An error occurred when taking the offer.\n\n{0}
|
||||||
@ -1968,6 +1978,7 @@ offerDetailsWindow.confirm.taker=Confirm: Take offer to {0} monero
|
|||||||
offerDetailsWindow.confirm.takerCrypto=Confirm: Take offer to {0} {1}
|
offerDetailsWindow.confirm.takerCrypto=Confirm: Take offer to {0} {1}
|
||||||
offerDetailsWindow.creationDate=Creation date
|
offerDetailsWindow.creationDate=Creation date
|
||||||
offerDetailsWindow.makersOnion=Maker's onion address
|
offerDetailsWindow.makersOnion=Maker's onion address
|
||||||
|
offerDetailsWindow.challenge=Offer passphrase
|
||||||
|
|
||||||
qRCodeWindow.headline=QR Code
|
qRCodeWindow.headline=QR Code
|
||||||
qRCodeWindow.msg=Please use this QR code for funding your Haveno wallet from your external wallet.
|
qRCodeWindow.msg=Please use this QR code for funding your Haveno wallet from your external wallet.
|
||||||
@ -2269,6 +2280,12 @@ popup.accountSigning.unsignedPubKeys.signed=Pubkeys were signed
|
|||||||
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
||||||
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign
|
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign
|
||||||
|
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit.headline=No deposit required from buyer
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit=\
|
||||||
|
Your offer will not require a security deposit or fee from the XMR buyer.\n\n\
|
||||||
|
To accept your offer, you must share a passphrase with your trade partner outside Haveno.\n\n\
|
||||||
|
The passphrase is automatically generated and shown in the offer details after creation.\
|
||||||
|
|
||||||
popup.info.torMigration.msg=Your Haveno node is probably using a deprecated Tor v2 address. \
|
popup.info.torMigration.msg=Your Haveno node is probably using a deprecated Tor v2 address. \
|
||||||
Please switch your Haveno node to a Tor v3 address. \
|
Please switch your Haveno node to a Tor v3 address. \
|
||||||
Make sure to back up your data directory beforehand.
|
Make sure to back up your data directory beforehand.
|
||||||
@ -2408,6 +2425,7 @@ navigation.support=\"Support\"
|
|||||||
|
|
||||||
formatter.formatVolumeLabel={0} amount{1}
|
formatter.formatVolumeLabel={0} amount{1}
|
||||||
formatter.makerTaker=Maker as {0} {1} / Taker as {2} {3}
|
formatter.makerTaker=Maker as {0} {1} / Taker as {2} {3}
|
||||||
|
formatter.makerTakerLocked=Maker as {0} {1} / Taker as {2} {3} 🔒
|
||||||
formatter.youAreAsMaker=You are: {1} {0} (maker) / Taker is: {3} {2}
|
formatter.youAreAsMaker=You are: {1} {0} (maker) / Taker is: {3} {2}
|
||||||
formatter.youAreAsTaker=You are: {1} {0} (taker) / Maker is: {3} {2}
|
formatter.youAreAsTaker=You are: {1} {0} (taker) / Maker is: {3} {2}
|
||||||
formatter.youAre=You are {0} {1} ({2} {3})
|
formatter.youAre=You are {0} {1} ({2} {3})
|
||||||
|
@ -40,6 +40,8 @@ shared.buyMonero=Koupit monero
|
|||||||
shared.sellMonero=Prodat monero
|
shared.sellMonero=Prodat monero
|
||||||
shared.buyCurrency=Koupit {0}
|
shared.buyCurrency=Koupit {0}
|
||||||
shared.sellCurrency=Prodat {0}
|
shared.sellCurrency=Prodat {0}
|
||||||
|
shared.buyCurrencyLocked=Koupit {0} 🔒
|
||||||
|
shared.sellCurrencyLocked=Prodat {0} 🔒
|
||||||
shared.buyingXMRWith=nakoupit XMR za {0}
|
shared.buyingXMRWith=nakoupit XMR za {0}
|
||||||
shared.sellingXMRFor=prodat XMR za {0}
|
shared.sellingXMRFor=prodat XMR za {0}
|
||||||
shared.buyingCurrency=nakoupit {0} (prodat XMR)
|
shared.buyingCurrency=nakoupit {0} (prodat XMR)
|
||||||
@ -330,6 +332,7 @@ offerbook.createOffer=Vytvořit nabídku
|
|||||||
offerbook.takeOffer=Přijmout nabídku
|
offerbook.takeOffer=Přijmout nabídku
|
||||||
offerbook.takeOfferToBuy=Přijmout nabídku na nákup {0}
|
offerbook.takeOfferToBuy=Přijmout nabídku na nákup {0}
|
||||||
offerbook.takeOfferToSell=Přijmout nabídku k prodeji {0}
|
offerbook.takeOfferToSell=Přijmout nabídku k prodeji {0}
|
||||||
|
offerbook.takeOffer.enterChallenge=Zadejte heslo nabídky
|
||||||
offerbook.trader=Obchodník
|
offerbook.trader=Obchodník
|
||||||
offerbook.offerersBankId=ID banky tvůrce (BIC/SWIFT): {0}
|
offerbook.offerersBankId=ID banky tvůrce (BIC/SWIFT): {0}
|
||||||
offerbook.offerersBankName=Jméno banky tvůrce: {0}
|
offerbook.offerersBankName=Jméno banky tvůrce: {0}
|
||||||
@ -340,6 +343,8 @@ offerbook.availableOffers=Dostupné nabídky
|
|||||||
offerbook.filterByCurrency=Filtrovat podle měny
|
offerbook.filterByCurrency=Filtrovat podle měny
|
||||||
offerbook.filterByPaymentMethod=Filtrovat podle platební metody
|
offerbook.filterByPaymentMethod=Filtrovat podle platební metody
|
||||||
offerbook.matchingOffers=Nabídky odpovídající mým účtům
|
offerbook.matchingOffers=Nabídky odpovídající mým účtům
|
||||||
|
offerbook.filterNoDeposit=Žádný vklad
|
||||||
|
offerbook.noDepositOffers=Nabídky bez zálohy (vyžaduje se heslo)
|
||||||
offerbook.timeSinceSigning=Informace o účtu
|
offerbook.timeSinceSigning=Informace o účtu
|
||||||
offerbook.timeSinceSigning.info=Tento účet byl ověřen a {0}
|
offerbook.timeSinceSigning.info=Tento účet byl ověřen a {0}
|
||||||
offerbook.timeSinceSigning.info.arbitrator=podepsán rozhodcem a může podepisovat účty partnerů
|
offerbook.timeSinceSigning.info.arbitrator=podepsán rozhodcem a může podepisovat účty partnerů
|
||||||
@ -485,7 +490,10 @@ createOffer.setDepositAsBuyer=Nastavit mou kauci jako kupujícího (%)
|
|||||||
createOffer.setDepositForBothTraders=Nastavit kauci obou obchodníků (%)
|
createOffer.setDepositForBothTraders=Nastavit kauci obou obchodníků (%)
|
||||||
createOffer.securityDepositInfo=Kauce vašeho kupujícího bude {0}
|
createOffer.securityDepositInfo=Kauce vašeho kupujícího bude {0}
|
||||||
createOffer.securityDepositInfoAsBuyer=Vaše kauce jako kupující bude {0}
|
createOffer.securityDepositInfoAsBuyer=Vaše kauce jako kupující bude {0}
|
||||||
createOffer.minSecurityDepositUsed=Je použita min. záloha kupujícího
|
createOffer.minSecurityDepositUsed=Minimální bezpečnostní záloha je použita
|
||||||
|
createOffer.buyerAsTakerWithoutDeposit=Žádný vklad od kupujícího (chráněno heslem)
|
||||||
|
createOffer.myDeposit=Můj bezpečnostní vklad (%)
|
||||||
|
createOffer.myDepositInfo=Vaše záloha na bezpečnost bude {0}
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -509,6 +517,8 @@ takeOffer.fundsBox.networkFee=Celkové poplatky za těžbu
|
|||||||
takeOffer.fundsBox.takeOfferSpinnerInfo=Přijímám nabídku: {0}
|
takeOffer.fundsBox.takeOfferSpinnerInfo=Přijímám nabídku: {0}
|
||||||
takeOffer.fundsBox.paymentLabel=Haveno obchod s ID {0}
|
takeOffer.fundsBox.paymentLabel=Haveno obchod s ID {0}
|
||||||
takeOffer.fundsBox.fundsStructure=(kauce {0}, obchodní poplatek {1}, poplatek za těžbu {2})
|
takeOffer.fundsBox.fundsStructure=(kauce {0}, obchodní poplatek {1}, poplatek za těžbu {2})
|
||||||
|
takeOffer.fundsBox.noFundingRequiredTitle=Žádné financování požadováno
|
||||||
|
takeOffer.fundsBox.noFundingRequiredDescription=Získejte passphrase nabídky od prodávajícího mimo Haveno, abyste tuto nabídku přijali.
|
||||||
takeOffer.success.headline=Úspěšně jste přijali nabídku.
|
takeOffer.success.headline=Úspěšně jste přijali nabídku.
|
||||||
takeOffer.success.info=Stav vašeho obchodu můžete vidět v \"Portfolio/Otevřené obchody\".
|
takeOffer.success.info=Stav vašeho obchodu můžete vidět v \"Portfolio/Otevřené obchody\".
|
||||||
takeOffer.error.message=Při převzetí nabídky došlo k chybě.\n\n{0}
|
takeOffer.error.message=Při převzetí nabídky došlo k chybě.\n\n{0}
|
||||||
@ -1464,6 +1474,7 @@ offerDetailsWindow.confirm.maker=Potvrďte: Umístit nabídku {0} monero
|
|||||||
offerDetailsWindow.confirm.taker=Potvrďte: Využít nabídku {0} monero
|
offerDetailsWindow.confirm.taker=Potvrďte: Využít nabídku {0} monero
|
||||||
offerDetailsWindow.creationDate=Datum vzniku
|
offerDetailsWindow.creationDate=Datum vzniku
|
||||||
offerDetailsWindow.makersOnion=Onion adresa tvůrce
|
offerDetailsWindow.makersOnion=Onion adresa tvůrce
|
||||||
|
offerDetailsWindow.challenge=Passphrase nabídky
|
||||||
|
|
||||||
qRCodeWindow.headline=QR Kód
|
qRCodeWindow.headline=QR Kód
|
||||||
qRCodeWindow.msg=Použijte tento QR kód k financování vaší peněženky Haveno z vaší externí peněženky.
|
qRCodeWindow.msg=Použijte tento QR kód k financování vaší peněženky Haveno z vaší externí peněženky.
|
||||||
@ -1689,6 +1700,9 @@ popup.accountSigning.unsignedPubKeys.signed=Pubkeys byly podepsány
|
|||||||
popup.accountSigning.unsignedPubKeys.result.signed=Podepsané pubkeys
|
popup.accountSigning.unsignedPubKeys.result.signed=Podepsané pubkeys
|
||||||
popup.accountSigning.unsignedPubKeys.result.failed=Nepodařilo se podepsat
|
popup.accountSigning.unsignedPubKeys.result.failed=Nepodařilo se podepsat
|
||||||
|
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit.headline=Žádný vklad není od kupujícího požadován
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit=Vaše nabídka nebude vyžadovat bezpečnostní zálohu ani poplatek od kupujícího XMR.\n\nPro přijetí vaší nabídky musíte sdílet heslo se svým obchodním partnerem mimo Haveno.\n\nHeslo je automaticky vygenerováno a zobrazeno v detailech nabídky po jejím vytvoření.
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
# Notifications
|
# Notifications
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -1814,6 +1828,7 @@ navigation.support=\"Podpora\"
|
|||||||
|
|
||||||
formatter.formatVolumeLabel={0} částka{1}
|
formatter.formatVolumeLabel={0} částka{1}
|
||||||
formatter.makerTaker=Tvůrce jako {0} {1} / Příjemce jako {2} {3}
|
formatter.makerTaker=Tvůrce jako {0} {1} / Příjemce jako {2} {3}
|
||||||
|
formatter.makerTakerLocked=Tvůrce jako {0} {1} / Příjemce jako {2} {3} 🔒
|
||||||
formatter.youAreAsMaker=Jste {1} {0} (jako tvůrce) / Příjemce je {3} {2}
|
formatter.youAreAsMaker=Jste {1} {0} (jako tvůrce) / Příjemce je {3} {2}
|
||||||
formatter.youAreAsTaker=Jste {1} {0} (jako příjemce) / Tvůrce je {3} {2}
|
formatter.youAreAsTaker=Jste {1} {0} (jako příjemce) / Tvůrce je {3} {2}
|
||||||
formatter.youAre={0}te {1} ({2}te {3})
|
formatter.youAre={0}te {1} ({2}te {3})
|
||||||
|
@ -40,6 +40,8 @@ shared.buyMonero=Monero kaufen
|
|||||||
shared.sellMonero=Monero verkaufen
|
shared.sellMonero=Monero verkaufen
|
||||||
shared.buyCurrency={0} kaufen
|
shared.buyCurrency={0} kaufen
|
||||||
shared.sellCurrency={0} verkaufen
|
shared.sellCurrency={0} verkaufen
|
||||||
|
shared.buyCurrencyLocked={0} kaufen 🔒
|
||||||
|
shared.sellCurrencyLocked={0} verkaufen 🔒
|
||||||
shared.buyingXMRWith=kaufe XMR mit {0}
|
shared.buyingXMRWith=kaufe XMR mit {0}
|
||||||
shared.sellingXMRFor=verkaufe XMR für {0}
|
shared.sellingXMRFor=verkaufe XMR für {0}
|
||||||
shared.buyingCurrency=kaufe {0} (verkaufe XMR)
|
shared.buyingCurrency=kaufe {0} (verkaufe XMR)
|
||||||
@ -330,6 +332,7 @@ offerbook.createOffer=Angebot erstellen
|
|||||||
offerbook.takeOffer=Angebot annehmen
|
offerbook.takeOffer=Angebot annehmen
|
||||||
offerbook.takeOfferToBuy=Angebot annehmen {0} zu kaufen
|
offerbook.takeOfferToBuy=Angebot annehmen {0} zu kaufen
|
||||||
offerbook.takeOfferToSell=Angebot annehmen {0} zu verkaufen
|
offerbook.takeOfferToSell=Angebot annehmen {0} zu verkaufen
|
||||||
|
offerbook.takeOffer.enterChallenge=Geben Sie das Angebots-Passphrase ein
|
||||||
offerbook.trader=Händler
|
offerbook.trader=Händler
|
||||||
offerbook.offerersBankId=Bankkennung des Erstellers (BIC/SWIFT): {0}
|
offerbook.offerersBankId=Bankkennung des Erstellers (BIC/SWIFT): {0}
|
||||||
offerbook.offerersBankName=Bankname des Erstellers: {0}
|
offerbook.offerersBankName=Bankname des Erstellers: {0}
|
||||||
@ -340,6 +343,8 @@ offerbook.availableOffers=Verfügbare Angebote
|
|||||||
offerbook.filterByCurrency=Nach Währung filtern
|
offerbook.filterByCurrency=Nach Währung filtern
|
||||||
offerbook.filterByPaymentMethod=Nach Zahlungsmethode filtern
|
offerbook.filterByPaymentMethod=Nach Zahlungsmethode filtern
|
||||||
offerbook.matchingOffers=Angebote die meinen Zahlungskonten entsprechen
|
offerbook.matchingOffers=Angebote die meinen Zahlungskonten entsprechen
|
||||||
|
offerbook.filterNoDeposit=Kein Deposit
|
||||||
|
offerbook.noDepositOffers=Angebote ohne Einzahlung (Passphrase erforderlich)
|
||||||
offerbook.timeSinceSigning=Informationen zum Zahlungskonto
|
offerbook.timeSinceSigning=Informationen zum Zahlungskonto
|
||||||
offerbook.timeSinceSigning.info=Dieses Konto wurde verifiziert und {0}
|
offerbook.timeSinceSigning.info=Dieses Konto wurde verifiziert und {0}
|
||||||
offerbook.timeSinceSigning.info.arbitrator=von einem Vermittler unterzeichnet und kann Partner-Konten unterzeichnen
|
offerbook.timeSinceSigning.info.arbitrator=von einem Vermittler unterzeichnet und kann Partner-Konten unterzeichnen
|
||||||
@ -485,7 +490,10 @@ createOffer.setDepositAsBuyer=Meine Kaution als Käufer festlegen (%)
|
|||||||
createOffer.setDepositForBothTraders=Legen Sie die Kaution für beide Handelspartner fest (%)
|
createOffer.setDepositForBothTraders=Legen Sie die Kaution für beide Handelspartner 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=Der Mindest-Sicherheitsbetrag wird verwendet.
|
||||||
|
createOffer.buyerAsTakerWithoutDeposit=Kein Deposit erforderlich vom Käufer (Passphrase geschützt)
|
||||||
|
createOffer.myDeposit=Meine Sicherheitsleistung (%)
|
||||||
|
createOffer.myDepositInfo=Ihre Sicherheitsleistung beträgt {0}
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -509,6 +517,8 @@ takeOffer.fundsBox.networkFee=Gesamte Mining-Gebühr
|
|||||||
takeOffer.fundsBox.takeOfferSpinnerInfo=Angebot annehmen: {0}
|
takeOffer.fundsBox.takeOfferSpinnerInfo=Angebot annehmen: {0}
|
||||||
takeOffer.fundsBox.paymentLabel=Haveno-Handel mit der ID {0}
|
takeOffer.fundsBox.paymentLabel=Haveno-Handel mit der ID {0}
|
||||||
takeOffer.fundsBox.fundsStructure=({0} Kaution, {1} Handelsgebühr, {2} Mining-Gebühr)
|
takeOffer.fundsBox.fundsStructure=({0} Kaution, {1} Handelsgebühr, {2} Mining-Gebühr)
|
||||||
|
takeOffer.fundsBox.noFundingRequiredTitle=Keine Finanzierung erforderlich
|
||||||
|
takeOffer.fundsBox.noFundingRequiredDescription=Holen Sie sich das Angebots-Passwort vom Verkäufer außerhalb von Haveno, um dieses Angebot anzunehmen.
|
||||||
takeOffer.success.headline=Sie haben erfolgreich ein Angebot angenommen.
|
takeOffer.success.headline=Sie haben erfolgreich ein Angebot angenommen.
|
||||||
takeOffer.success.info=Sie können den Status Ihres Trades unter \"Portfolio/Offene Trades\" einsehen.
|
takeOffer.success.info=Sie können den Status Ihres Trades unter \"Portfolio/Offene Trades\" einsehen.
|
||||||
takeOffer.error.message=Bei der Angebotsannahme trat ein Fehler auf.\n\n{0}
|
takeOffer.error.message=Bei der Angebotsannahme trat ein Fehler auf.\n\n{0}
|
||||||
@ -1464,6 +1474,7 @@ offerDetailsWindow.confirm.maker=Bestätigen: Anbieten monero zu {0}
|
|||||||
offerDetailsWindow.confirm.taker=Bestätigen: Angebot annehmen monero zu {0}
|
offerDetailsWindow.confirm.taker=Bestätigen: Angebot annehmen monero zu {0}
|
||||||
offerDetailsWindow.creationDate=Erstellungsdatum
|
offerDetailsWindow.creationDate=Erstellungsdatum
|
||||||
offerDetailsWindow.makersOnion=Onion-Adresse des Erstellers
|
offerDetailsWindow.makersOnion=Onion-Adresse des Erstellers
|
||||||
|
offerDetailsWindow.challenge=Angebots-Passphrase
|
||||||
|
|
||||||
qRCodeWindow.headline=QR Code
|
qRCodeWindow.headline=QR Code
|
||||||
qRCodeWindow.msg=Bitte nutzen Sie diesen QR Code um Ihr Haveno Wallet von Ihrem externen Wallet aufzuladen.
|
qRCodeWindow.msg=Bitte nutzen Sie diesen QR Code um Ihr Haveno Wallet von Ihrem externen Wallet aufzuladen.
|
||||||
@ -1690,6 +1701,9 @@ popup.accountSigning.unsignedPubKeys.signed=Pubkeys wurden unterzeichnet
|
|||||||
popup.accountSigning.unsignedPubKeys.result.signed=Unterzeichnete Pubkeys
|
popup.accountSigning.unsignedPubKeys.result.signed=Unterzeichnete Pubkeys
|
||||||
popup.accountSigning.unsignedPubKeys.result.failed=Unterzeichnung fehlgeschlagen
|
popup.accountSigning.unsignedPubKeys.result.failed=Unterzeichnung fehlgeschlagen
|
||||||
|
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit.headline=Kein Depositum vom Käufer erforderlich
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit=Ihr Angebot erfordert keine Sicherheitsleistung oder Gebühr vom XMR-Käufer.\n\nUm Ihr Angebot anzunehmen, müssen Sie ein Passwort mit Ihrem Handelspartner außerhalb von Haveno teilen.\n\nDas Passwort wird automatisch generiert und nach der Erstellung in den Angebotsdetails angezeigt.
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
# Notifications
|
# Notifications
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -1815,6 +1829,7 @@ navigation.support=\"Support\"
|
|||||||
|
|
||||||
formatter.formatVolumeLabel={0} Betrag{1}
|
formatter.formatVolumeLabel={0} Betrag{1}
|
||||||
formatter.makerTaker=Ersteller als {0} {1} / Abnehmer als {2} {3}
|
formatter.makerTaker=Ersteller als {0} {1} / Abnehmer als {2} {3}
|
||||||
|
formatter.makerTakerLocked=Ersteller als {0} {1} / Abnehmer als {2} {3} 🔒
|
||||||
formatter.youAreAsMaker=Sie sind: {1} {0} (Ersteller) / Abnehmer ist: {3} {2}
|
formatter.youAreAsMaker=Sie sind: {1} {0} (Ersteller) / Abnehmer ist: {3} {2}
|
||||||
formatter.youAreAsTaker=Sie sind: {1} {0} (Abnehmer) / Ersteller ist: {3} {2}
|
formatter.youAreAsTaker=Sie sind: {1} {0} (Abnehmer) / Ersteller ist: {3} {2}
|
||||||
formatter.youAre=Sie {0} {1} ({2} {3})
|
formatter.youAre=Sie {0} {1} ({2} {3})
|
||||||
|
@ -40,6 +40,8 @@ shared.buyMonero=Comprar monero
|
|||||||
shared.sellMonero=Vender monero
|
shared.sellMonero=Vender monero
|
||||||
shared.buyCurrency=Comprar {0}
|
shared.buyCurrency=Comprar {0}
|
||||||
shared.sellCurrency=Vender {0}
|
shared.sellCurrency=Vender {0}
|
||||||
|
shared.buyCurrencyLocked=Comprar {0} 🔒
|
||||||
|
shared.sellCurrencyLocked=Vender {0} 🔒
|
||||||
shared.buyingXMRWith=Comprando XMR con {0}
|
shared.buyingXMRWith=Comprando XMR con {0}
|
||||||
shared.sellingXMRFor=Vendiendo XMR por {0}
|
shared.sellingXMRFor=Vendiendo XMR por {0}
|
||||||
shared.buyingCurrency=comprando {0} (Vendiendo XMR)
|
shared.buyingCurrency=comprando {0} (Vendiendo XMR)
|
||||||
@ -330,6 +332,7 @@ offerbook.createOffer=Crear oferta
|
|||||||
offerbook.takeOffer=Tomar oferta
|
offerbook.takeOffer=Tomar oferta
|
||||||
offerbook.takeOfferToBuy=Tomar oferta de compra de {0}
|
offerbook.takeOfferToBuy=Tomar oferta de compra de {0}
|
||||||
offerbook.takeOfferToSell=Tomar oferta de venta de {0}
|
offerbook.takeOfferToSell=Tomar oferta de venta de {0}
|
||||||
|
offerbook.takeOffer.enterChallenge=Introduzca la frase secreta de la oferta
|
||||||
offerbook.trader=Trader
|
offerbook.trader=Trader
|
||||||
offerbook.offerersBankId=ID del banco del creador (BIC/SWIFT): {0}
|
offerbook.offerersBankId=ID del banco del creador (BIC/SWIFT): {0}
|
||||||
offerbook.offerersBankName=Nombre del banco del creador: {0}
|
offerbook.offerersBankName=Nombre del banco del creador: {0}
|
||||||
@ -340,6 +343,8 @@ offerbook.availableOffers=Ofertas disponibles
|
|||||||
offerbook.filterByCurrency=Filtrar por moneda
|
offerbook.filterByCurrency=Filtrar por moneda
|
||||||
offerbook.filterByPaymentMethod=Filtrar por método de pago
|
offerbook.filterByPaymentMethod=Filtrar por método de pago
|
||||||
offerbook.matchingOffers=Ofertas que concuerden con mis cuentas
|
offerbook.matchingOffers=Ofertas que concuerden con mis cuentas
|
||||||
|
offerbook.filterNoDeposit=Sin depósito
|
||||||
|
offerbook.noDepositOffers=Ofertas sin depósito (se requiere frase de paso)
|
||||||
offerbook.timeSinceSigning=Información de la cuenta
|
offerbook.timeSinceSigning=Información de la cuenta
|
||||||
offerbook.timeSinceSigning.info=Esta cuenta fue verificada y {0}
|
offerbook.timeSinceSigning.info=Esta cuenta fue verificada y {0}
|
||||||
offerbook.timeSinceSigning.info.arbitrator=firmada por un árbitro y puede firmar cuentas de pares
|
offerbook.timeSinceSigning.info.arbitrator=firmada por un árbitro y puede firmar cuentas de pares
|
||||||
@ -485,7 +490,10 @@ createOffer.setDepositAsBuyer=Establecer mi depósito de seguridad como comprado
|
|||||||
createOffer.setDepositForBothTraders=Establecer el depósito de seguridad para los comerciantes (%)
|
createOffer.setDepositForBothTraders=Establecer el depósito de seguridad para los comerciantes (%)
|
||||||
createOffer.securityDepositInfo=Su depósito de seguridad como comprador será {0}
|
createOffer.securityDepositInfo=Su depósito de seguridad como comprador será {0}
|
||||||
createOffer.securityDepositInfoAsBuyer=Su depósito de seguridad como comprador será {0}
|
createOffer.securityDepositInfoAsBuyer=Su depósito de seguridad como comprador será {0}
|
||||||
createOffer.minSecurityDepositUsed=En uso el depósito de seguridad mínimo
|
createOffer.minSecurityDepositUsed=Se utiliza un depósito de seguridad mínimo
|
||||||
|
createOffer.buyerAsTakerWithoutDeposit=No se requiere depósito del comprador (protegido por passphrase)
|
||||||
|
createOffer.myDeposit=Mi depósito de seguridad (%)
|
||||||
|
createOffer.myDepositInfo=Tu depósito de seguridad será {0}
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -509,6 +517,8 @@ takeOffer.fundsBox.networkFee=Comisiones de minado totales
|
|||||||
takeOffer.fundsBox.takeOfferSpinnerInfo=Aceptando oferta: {0}
|
takeOffer.fundsBox.takeOfferSpinnerInfo=Aceptando oferta: {0}
|
||||||
takeOffer.fundsBox.paymentLabel=Intercambio Haveno con ID {0}
|
takeOffer.fundsBox.paymentLabel=Intercambio Haveno con ID {0}
|
||||||
takeOffer.fundsBox.fundsStructure=({0} depósito de seguridad {1} tasa de intercambio, {2} tarifa de minado)
|
takeOffer.fundsBox.fundsStructure=({0} depósito de seguridad {1} tasa de intercambio, {2} tarifa de minado)
|
||||||
|
takeOffer.fundsBox.noFundingRequiredTitle=No se requiere financiamiento
|
||||||
|
takeOffer.fundsBox.noFundingRequiredDescription=Obtén la frase de acceso de la oferta del vendedor fuera de Haveno para aceptar esta oferta.
|
||||||
takeOffer.success.headline=Ha aceptado la oferta con éxito.
|
takeOffer.success.headline=Ha aceptado la oferta con éxito.
|
||||||
takeOffer.success.info=Puede ver el estado de su intercambio en \"Portafolio/Intercambios abiertos\".
|
takeOffer.success.info=Puede ver el estado de su intercambio en \"Portafolio/Intercambios abiertos\".
|
||||||
takeOffer.error.message=Un error ocurrió al tomar la oferta.\n\n{0}
|
takeOffer.error.message=Un error ocurrió al tomar la oferta.\n\n{0}
|
||||||
@ -1465,6 +1475,7 @@ offerDetailsWindow.confirm.maker=Confirmar: Poner oferta para {0} monero
|
|||||||
offerDetailsWindow.confirm.taker=Confirmar: Tomar oferta {0} monero
|
offerDetailsWindow.confirm.taker=Confirmar: Tomar oferta {0} monero
|
||||||
offerDetailsWindow.creationDate=Fecha de creación
|
offerDetailsWindow.creationDate=Fecha de creación
|
||||||
offerDetailsWindow.makersOnion=Dirección onion del creador
|
offerDetailsWindow.makersOnion=Dirección onion del creador
|
||||||
|
offerDetailsWindow.challenge=Frase de contraseña de la oferta
|
||||||
|
|
||||||
qRCodeWindow.headline=Código QR
|
qRCodeWindow.headline=Código QR
|
||||||
qRCodeWindow.msg=Por favor, utilice este código QR para fondear su billetera Haveno desde su billetera externa.
|
qRCodeWindow.msg=Por favor, utilice este código QR para fondear su billetera Haveno desde su billetera externa.
|
||||||
@ -1691,6 +1702,9 @@ popup.accountSigning.unsignedPubKeys.signed=Las claves públicas se firmaron
|
|||||||
popup.accountSigning.unsignedPubKeys.result.signed=Claves públicas firmadas
|
popup.accountSigning.unsignedPubKeys.result.signed=Claves públicas firmadas
|
||||||
popup.accountSigning.unsignedPubKeys.result.failed=Error al firmar
|
popup.accountSigning.unsignedPubKeys.result.failed=Error al firmar
|
||||||
|
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit.headline=No se requiere depósito del comprador
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit=Tu oferta no requerirá un depósito de seguridad ni una tarifa del comprador de XMR.\n\nPara aceptar tu oferta, debes compartir una frase de acceso con tu compañero de comercio fuera de Haveno.\n\nLa frase de acceso se genera automáticamente y se muestra en los detalles de la oferta después de la creación.
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
# Notifications
|
# Notifications
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -1816,6 +1830,7 @@ navigation.support=\"Soporte\"
|
|||||||
|
|
||||||
formatter.formatVolumeLabel={0} cantidad{1}
|
formatter.formatVolumeLabel={0} cantidad{1}
|
||||||
formatter.makerTaker=Creador como {0} {1} / Tomador como {2} {3}
|
formatter.makerTaker=Creador como {0} {1} / Tomador como {2} {3}
|
||||||
|
formatter.makerTakerLocked=Creador como {0} {1} / Tomador como {2} {3} 🔒
|
||||||
formatter.youAreAsMaker=Usted es: {1} {0} (creador) / El tomador es: {3} {2}
|
formatter.youAreAsMaker=Usted es: {1} {0} (creador) / El tomador es: {3} {2}
|
||||||
formatter.youAreAsTaker=Usted es: {1} {0} (tomador) / Creador es: {3} {2}
|
formatter.youAreAsTaker=Usted es: {1} {0} (tomador) / Creador es: {3} {2}
|
||||||
formatter.youAre=Usted es {0} {1} ({2} {3})
|
formatter.youAre=Usted es {0} {1} ({2} {3})
|
||||||
|
@ -40,6 +40,8 @@ shared.buyMonero=خرید بیتکوین
|
|||||||
shared.sellMonero=بیتکوین بفروشید
|
shared.sellMonero=بیتکوین بفروشید
|
||||||
shared.buyCurrency=خرید {0}
|
shared.buyCurrency=خرید {0}
|
||||||
shared.sellCurrency=فروش {0}
|
shared.sellCurrency=فروش {0}
|
||||||
|
shared.buyCurrencyLocked=بخر {0} 🔒
|
||||||
|
shared.sellCurrencyLocked=فروش {0} 🔒
|
||||||
shared.buyingXMRWith=خرید بیتکوین با {0}
|
shared.buyingXMRWith=خرید بیتکوین با {0}
|
||||||
shared.sellingXMRFor=فروش بیتکوین با {0}
|
shared.sellingXMRFor=فروش بیتکوین با {0}
|
||||||
shared.buyingCurrency=خرید {0} ( فروش بیتکوین)
|
shared.buyingCurrency=خرید {0} ( فروش بیتکوین)
|
||||||
@ -330,6 +332,7 @@ offerbook.createOffer=ایجاد پیشنهاد
|
|||||||
offerbook.takeOffer=برداشتن پیشنهاد
|
offerbook.takeOffer=برداشتن پیشنهاد
|
||||||
offerbook.takeOfferToBuy=پیشنهاد خرید {0} را بردار
|
offerbook.takeOfferToBuy=پیشنهاد خرید {0} را بردار
|
||||||
offerbook.takeOfferToSell=پیشنهاد فروش {0} را بردار
|
offerbook.takeOfferToSell=پیشنهاد فروش {0} را بردار
|
||||||
|
offerbook.takeOffer.enterChallenge=عبارت عبور پیشنهاد را وارد کنید
|
||||||
offerbook.trader=معاملهگر
|
offerbook.trader=معاملهگر
|
||||||
offerbook.offerersBankId=شناسه بانک سفارشگذار (BIC/SWIFT): {0}
|
offerbook.offerersBankId=شناسه بانک سفارشگذار (BIC/SWIFT): {0}
|
||||||
offerbook.offerersBankName= نام بانک سفارشگذار : {0}
|
offerbook.offerersBankName= نام بانک سفارشگذار : {0}
|
||||||
@ -339,7 +342,9 @@ offerbook.offerersAcceptedBankSeats=بانکهای کشورهای پذیرف
|
|||||||
offerbook.availableOffers=پیشنهادهای موجود
|
offerbook.availableOffers=پیشنهادهای موجود
|
||||||
offerbook.filterByCurrency=فیلتر بر اساس ارز
|
offerbook.filterByCurrency=فیلتر بر اساس ارز
|
||||||
offerbook.filterByPaymentMethod=فیلتر بر اساس روش پرداخت
|
offerbook.filterByPaymentMethod=فیلتر بر اساس روش پرداخت
|
||||||
offerbook.matchingOffers=Offers matching my accounts
|
offerbook.matchingOffers=پیشنهادات متناسب با حسابهای من
|
||||||
|
offerbook.filterNoDeposit=هیچ سپردهای
|
||||||
|
offerbook.noDepositOffers=پیشنهادهایی بدون ودیعه (نیاز به عبارت عبور)
|
||||||
offerbook.timeSinceSigning=Account info
|
offerbook.timeSinceSigning=Account info
|
||||||
offerbook.timeSinceSigning.info=This account was verified and {0}
|
offerbook.timeSinceSigning.info=This account was verified and {0}
|
||||||
offerbook.timeSinceSigning.info.arbitrator=signed by an arbitrator and can sign peer accounts
|
offerbook.timeSinceSigning.info.arbitrator=signed by an arbitrator and can sign peer accounts
|
||||||
@ -484,7 +489,10 @@ createOffer.setDepositAsBuyer=تنظیم سپردهی اطمینان من ب
|
|||||||
createOffer.setDepositForBothTraders=Set both traders' security deposit (%)
|
createOffer.setDepositForBothTraders=Set both traders' security deposit (%)
|
||||||
createOffer.securityDepositInfo=سپردهی اطمینان خریدار شما {0} خواهد بود
|
createOffer.securityDepositInfo=سپردهی اطمینان خریدار شما {0} خواهد بود
|
||||||
createOffer.securityDepositInfoAsBuyer=سپردهی اطمینان شما به عنوان خریدار {0} خواهد بود
|
createOffer.securityDepositInfoAsBuyer=سپردهی اطمینان شما به عنوان خریدار {0} خواهد بود
|
||||||
createOffer.minSecurityDepositUsed=Min. buyer security deposit is used
|
createOffer.minSecurityDepositUsed=حداقل سپرده امنیتی استفاده میشود
|
||||||
|
createOffer.buyerAsTakerWithoutDeposit=هیچ سپردهای از خریدار مورد نیاز نیست (محافظت شده با پسعبارت)
|
||||||
|
createOffer.myDeposit=سپرده امنیتی من (%)
|
||||||
|
createOffer.myDepositInfo=ودیعه امنیتی شما {0} خواهد بود
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -508,6 +516,8 @@ takeOffer.fundsBox.networkFee=کل کارمزد استخراج
|
|||||||
takeOffer.fundsBox.takeOfferSpinnerInfo=پذیرفتن پیشنهاد: {0}
|
takeOffer.fundsBox.takeOfferSpinnerInfo=پذیرفتن پیشنهاد: {0}
|
||||||
takeOffer.fundsBox.paymentLabel=معامله Haveno با شناسهی {0}
|
takeOffer.fundsBox.paymentLabel=معامله Haveno با شناسهی {0}
|
||||||
takeOffer.fundsBox.fundsStructure=({0} سپردهی اطمینان، {1} هزینهی معامله، {2} هزینه تراکنش شبکه)
|
takeOffer.fundsBox.fundsStructure=({0} سپردهی اطمینان، {1} هزینهی معامله، {2} هزینه تراکنش شبکه)
|
||||||
|
takeOffer.fundsBox.noFundingRequiredTitle=نیاز به تأمین مالی نیست
|
||||||
|
takeOffer.fundsBox.noFundingRequiredDescription=برای پذیرش این پیشنهاد، رمزعبور آن را از فروشنده خارج از هاونئو دریافت کنید.
|
||||||
takeOffer.success.headline=با موفقیت یک پیشنهاد را قبول کردهاید.
|
takeOffer.success.headline=با موفقیت یک پیشنهاد را قبول کردهاید.
|
||||||
takeOffer.success.info=شما میتوانید وضعیت معاملهی خود را در \"سبد سهام /معاملات باز\" ببینید.
|
takeOffer.success.info=شما میتوانید وضعیت معاملهی خود را در \"سبد سهام /معاملات باز\" ببینید.
|
||||||
takeOffer.error.message=هنگام قبول کردن پیشنهاد، اتفاقی رخ داده است.\n\n{0}
|
takeOffer.error.message=هنگام قبول کردن پیشنهاد، اتفاقی رخ داده است.\n\n{0}
|
||||||
@ -1460,6 +1470,7 @@ offerDetailsWindow.confirm.maker=تأیید: پیشنهاد را به {0} بگذ
|
|||||||
offerDetailsWindow.confirm.taker=تأیید: پیشنهاد را به {0} بپذیرید
|
offerDetailsWindow.confirm.taker=تأیید: پیشنهاد را به {0} بپذیرید
|
||||||
offerDetailsWindow.creationDate=تاریخ ایجاد
|
offerDetailsWindow.creationDate=تاریخ ایجاد
|
||||||
offerDetailsWindow.makersOnion=آدرس Onion سفارش گذار
|
offerDetailsWindow.makersOnion=آدرس Onion سفارش گذار
|
||||||
|
offerDetailsWindow.challenge=Passphrase de l'offre
|
||||||
|
|
||||||
qRCodeWindow.headline=QR Code
|
qRCodeWindow.headline=QR Code
|
||||||
qRCodeWindow.msg=Please use this QR code for funding your Haveno wallet from your external wallet.
|
qRCodeWindow.msg=Please use this QR code for funding your Haveno wallet from your external wallet.
|
||||||
@ -1683,6 +1694,9 @@ popup.accountSigning.unsignedPubKeys.signed=Pubkeys were signed
|
|||||||
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
||||||
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign
|
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign
|
||||||
|
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit.headline=هیچ پیشپرداختی از خریدار مورد نیاز نیست
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit=پیشنهاد شما نیاز به ودیعه امنیتی یا هزینه از خریدار XMR ندارد.\n\nبرای پذیرفتن پیشنهاد شما، باید یک پسعبارت را با شریک تجاری خود خارج از Haveno به اشتراک بگذارید.\n\nپسعبارت بهطور خودکار تولید میشود و پس از ایجاد در جزئیات پیشنهاد نمایش داده میشود.
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
# Notifications
|
# Notifications
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -1808,6 +1822,7 @@ navigation.support=\"پشتیبانی\"
|
|||||||
|
|
||||||
formatter.formatVolumeLabel={0} مبلغ {1}
|
formatter.formatVolumeLabel={0} مبلغ {1}
|
||||||
formatter.makerTaker=سفارش گذار به عنوان {0} {1} / پذیرنده به عنوان {2} {3}
|
formatter.makerTaker=سفارش گذار به عنوان {0} {1} / پذیرنده به عنوان {2} {3}
|
||||||
|
formatter.makerTakerLocked=سفارش گذار به عنوان {0} {1} / پذیرنده به عنوان {2} {3} 🔒
|
||||||
formatter.youAreAsMaker=You are: {1} {0} (maker) / Taker is: {3} {2}
|
formatter.youAreAsMaker=You are: {1} {0} (maker) / Taker is: {3} {2}
|
||||||
formatter.youAreAsTaker=You are: {1} {0} (taker) / Maker is: {3} {2}
|
formatter.youAreAsTaker=You are: {1} {0} (taker) / Maker is: {3} {2}
|
||||||
formatter.youAre=شما {0} {1} ({2} {3}) هستید
|
formatter.youAre=شما {0} {1} ({2} {3}) هستید
|
||||||
|
@ -40,6 +40,8 @@ shared.buyMonero=Achat Monero
|
|||||||
shared.sellMonero=Vendre des Moneros
|
shared.sellMonero=Vendre des Moneros
|
||||||
shared.buyCurrency=Achat {0}
|
shared.buyCurrency=Achat {0}
|
||||||
shared.sellCurrency=Vendre {0}
|
shared.sellCurrency=Vendre {0}
|
||||||
|
shared.buyCurrencyLocked=Achat {0} 🔒
|
||||||
|
shared.sellCurrencyLocked=Vendre {0} 🔒
|
||||||
shared.buyingXMRWith=achat XMR avec {0}
|
shared.buyingXMRWith=achat XMR avec {0}
|
||||||
shared.sellingXMRFor=vendre XMR pour {0}
|
shared.sellingXMRFor=vendre XMR pour {0}
|
||||||
shared.buyingCurrency=achat {0} (vente XMR)
|
shared.buyingCurrency=achat {0} (vente XMR)
|
||||||
@ -330,6 +332,7 @@ offerbook.createOffer=Créer un ordre
|
|||||||
offerbook.takeOffer=Accepter un ordre
|
offerbook.takeOffer=Accepter un ordre
|
||||||
offerbook.takeOfferToBuy=Accepter l''ordre d''achat {0}
|
offerbook.takeOfferToBuy=Accepter l''ordre d''achat {0}
|
||||||
offerbook.takeOfferToSell=Accepter l''ordre de vente {0}
|
offerbook.takeOfferToSell=Accepter l''ordre de vente {0}
|
||||||
|
offerbook.takeOffer.enterChallenge=Entrez la phrase secrète de l'offre
|
||||||
offerbook.trader=Échanger
|
offerbook.trader=Échanger
|
||||||
offerbook.offerersBankId=ID de la banque du maker (BIC/SWIFT): {0}
|
offerbook.offerersBankId=ID de la banque du maker (BIC/SWIFT): {0}
|
||||||
offerbook.offerersBankName=Nom de la banque du maker: {0}
|
offerbook.offerersBankName=Nom de la banque du maker: {0}
|
||||||
@ -340,6 +343,8 @@ offerbook.availableOffers=Ordres disponibles
|
|||||||
offerbook.filterByCurrency=Filtrer par devise
|
offerbook.filterByCurrency=Filtrer par devise
|
||||||
offerbook.filterByPaymentMethod=Filtrer par mode de paiement
|
offerbook.filterByPaymentMethod=Filtrer par mode de paiement
|
||||||
offerbook.matchingOffers=Offres correspondants à mes comptes
|
offerbook.matchingOffers=Offres correspondants à mes comptes
|
||||||
|
offerbook.filterNoDeposit=Aucun dépôt
|
||||||
|
offerbook.noDepositOffers=Offres sans dépôt (passphrase requise)
|
||||||
offerbook.timeSinceSigning=Informations du compte
|
offerbook.timeSinceSigning=Informations du compte
|
||||||
offerbook.timeSinceSigning.info=Ce compte a été vérifié et {0}
|
offerbook.timeSinceSigning.info=Ce compte a été vérifié et {0}
|
||||||
offerbook.timeSinceSigning.info.arbitrator=signé par un arbitre et pouvant signer des comptes pairs
|
offerbook.timeSinceSigning.info.arbitrator=signé par un arbitre et pouvant signer des comptes pairs
|
||||||
@ -485,7 +490,10 @@ createOffer.setDepositAsBuyer=Définir mon dépôt de garantie en tant qu'achete
|
|||||||
createOffer.setDepositForBothTraders=Établissez le dépôt de sécurité des deux traders (%)
|
createOffer.setDepositForBothTraders=Établissez le dépôt de sécurité des deux traders (%)
|
||||||
createOffer.securityDepositInfo=Le dépôt de garantie de votre acheteur sera de {0}
|
createOffer.securityDepositInfo=Le dépôt de garantie de votre acheteur sera de {0}
|
||||||
createOffer.securityDepositInfoAsBuyer=Votre dépôt de garantie en tant qu''acheteur sera de {0}
|
createOffer.securityDepositInfoAsBuyer=Votre dépôt de garantie en tant qu''acheteur sera de {0}
|
||||||
createOffer.minSecurityDepositUsed=Le minimum de dépôt de garantie de l'acheteur est utilisé
|
createOffer.minSecurityDepositUsed=Le dépôt de sécurité minimum est utilisé
|
||||||
|
createOffer.buyerAsTakerWithoutDeposit=Aucun dépôt requis de la part de l'acheteur (protégé par un mot de passe)
|
||||||
|
createOffer.myDeposit=Mon dépôt de garantie (%)
|
||||||
|
createOffer.myDepositInfo=Votre dépôt de garantie sera de {0}
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -509,6 +517,8 @@ takeOffer.fundsBox.networkFee=Total des frais de minage
|
|||||||
takeOffer.fundsBox.takeOfferSpinnerInfo=Acceptation de l'offre : {0}
|
takeOffer.fundsBox.takeOfferSpinnerInfo=Acceptation de l'offre : {0}
|
||||||
takeOffer.fundsBox.paymentLabel=Transaction Haveno avec l''ID {0}
|
takeOffer.fundsBox.paymentLabel=Transaction Haveno avec l''ID {0}
|
||||||
takeOffer.fundsBox.fundsStructure=({0} dépôt de garantie, {1} frais de transaction, {2} frais de minage)
|
takeOffer.fundsBox.fundsStructure=({0} dépôt de garantie, {1} frais de transaction, {2} frais de minage)
|
||||||
|
takeOffer.fundsBox.noFundingRequiredTitle=Aucun financement requis
|
||||||
|
takeOffer.fundsBox.noFundingRequiredDescription=Obtenez la phrase secrète de l'offre auprès du vendeur en dehors de Haveno pour accepter cette offre.
|
||||||
takeOffer.success.headline=Vous avez accepté un ordre avec succès.
|
takeOffer.success.headline=Vous avez accepté un ordre avec succès.
|
||||||
takeOffer.success.info=Vous pouvez voir vos transactions dans \"Portfolio/Échanges en cours\".
|
takeOffer.success.info=Vous pouvez voir vos transactions dans \"Portfolio/Échanges en cours\".
|
||||||
takeOffer.error.message=Une erreur s''est produite pendant l’'acceptation de l''ordre.\n\n{0}
|
takeOffer.error.message=Une erreur s''est produite pendant l’'acceptation de l''ordre.\n\n{0}
|
||||||
@ -1466,6 +1476,7 @@ offerDetailsWindow.confirm.maker=Confirmer: Placer un ordre de {0} monero
|
|||||||
offerDetailsWindow.confirm.taker=Confirmer: Acceptez l''ordre de {0} monero
|
offerDetailsWindow.confirm.taker=Confirmer: Acceptez l''ordre de {0} monero
|
||||||
offerDetailsWindow.creationDate=Date de création
|
offerDetailsWindow.creationDate=Date de création
|
||||||
offerDetailsWindow.makersOnion=Adresse onion du maker
|
offerDetailsWindow.makersOnion=Adresse onion du maker
|
||||||
|
offerDetailsWindow.challenge=Phrase secrète de l'offre
|
||||||
|
|
||||||
qRCodeWindow.headline=QR Code
|
qRCodeWindow.headline=QR Code
|
||||||
qRCodeWindow.msg=Veuillez utiliser le code QR pour recharger du portefeuille externe au portefeuille Haveno.
|
qRCodeWindow.msg=Veuillez utiliser le code QR pour recharger du portefeuille externe au portefeuille Haveno.
|
||||||
@ -1692,6 +1703,9 @@ popup.accountSigning.unsignedPubKeys.signed=Les clés publiques ont été signé
|
|||||||
popup.accountSigning.unsignedPubKeys.result.signed=Clés publiques signées
|
popup.accountSigning.unsignedPubKeys.result.signed=Clés publiques signées
|
||||||
popup.accountSigning.unsignedPubKeys.result.failed=Échec de la signature
|
popup.accountSigning.unsignedPubKeys.result.failed=Échec de la signature
|
||||||
|
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit.headline=Aucun dépôt requis de la part de l'acheteur
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit=Votre offre ne nécessitera pas de dépôt de sécurité ni de frais de la part de l'acheteur XMR.\n\nPour accepter votre offre, vous devez partager un mot de passe avec votre partenaire commercial en dehors de Haveno.\n\nLe mot de passe est généré automatiquement et affiché dans les détails de l'offre après sa création.
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
# Notifications
|
# Notifications
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -1817,6 +1831,7 @@ navigation.support=\"Assistance\"
|
|||||||
|
|
||||||
formatter.formatVolumeLabel={0} montant{1}
|
formatter.formatVolumeLabel={0} montant{1}
|
||||||
formatter.makerTaker=Maker comme {0} {1} / Taker comme {2} {3}
|
formatter.makerTaker=Maker comme {0} {1} / Taker comme {2} {3}
|
||||||
|
formatter.makerTakerLocked=Maker comme {0} {1} / Taker comme {2} {3} 🔒
|
||||||
formatter.youAreAsMaker=Vous êtes {1} {0} (maker) / Le preneur est: {3} {2}
|
formatter.youAreAsMaker=Vous êtes {1} {0} (maker) / Le preneur est: {3} {2}
|
||||||
formatter.youAreAsTaker=Vous êtes: {1} {0} (preneur) / Le maker est: {3} {2}
|
formatter.youAreAsTaker=Vous êtes: {1} {0} (preneur) / Le maker est: {3} {2}
|
||||||
formatter.youAre=Vous êtes {0} {1} ({2} {3})
|
formatter.youAre=Vous êtes {0} {1} ({2} {3})
|
||||||
|
@ -40,6 +40,8 @@ shared.buyMonero=Acquista monero
|
|||||||
shared.sellMonero=Vendi monero
|
shared.sellMonero=Vendi monero
|
||||||
shared.buyCurrency=Acquista {0}
|
shared.buyCurrency=Acquista {0}
|
||||||
shared.sellCurrency=Vendi {0}
|
shared.sellCurrency=Vendi {0}
|
||||||
|
shared.buyCurrencyLocked=Acquista {0} 🔒
|
||||||
|
shared.sellCurrencyLocked=Vendi {0} 🔒
|
||||||
shared.buyingXMRWith=acquistando XMR con {0}
|
shared.buyingXMRWith=acquistando XMR con {0}
|
||||||
shared.sellingXMRFor=vendendo XMR per {0}
|
shared.sellingXMRFor=vendendo XMR per {0}
|
||||||
shared.buyingCurrency=comprando {0} (vendendo XMR)
|
shared.buyingCurrency=comprando {0} (vendendo XMR)
|
||||||
@ -330,6 +332,7 @@ offerbook.createOffer=Crea offerta
|
|||||||
offerbook.takeOffer=Accetta offerta
|
offerbook.takeOffer=Accetta offerta
|
||||||
offerbook.takeOfferToBuy=Accetta l'offerta per acquistare {0}
|
offerbook.takeOfferToBuy=Accetta l'offerta per acquistare {0}
|
||||||
offerbook.takeOfferToSell=Accetta l'offerta per vendere {0}
|
offerbook.takeOfferToSell=Accetta l'offerta per vendere {0}
|
||||||
|
offerbook.takeOffer.enterChallenge=Inserisci la passphrase dell'offerta
|
||||||
offerbook.trader=Trader
|
offerbook.trader=Trader
|
||||||
offerbook.offerersBankId=ID banca del Maker (BIC/SWIFT): {0}
|
offerbook.offerersBankId=ID banca del Maker (BIC/SWIFT): {0}
|
||||||
offerbook.offerersBankName=Nome della banca del Maker: {0}
|
offerbook.offerersBankName=Nome della banca del Maker: {0}
|
||||||
@ -339,7 +342,9 @@ offerbook.offerersAcceptedBankSeats=Sede accettata dei paesi bancari (acquirente
|
|||||||
offerbook.availableOffers=Offerte disponibili
|
offerbook.availableOffers=Offerte disponibili
|
||||||
offerbook.filterByCurrency=Filtra per valuta
|
offerbook.filterByCurrency=Filtra per valuta
|
||||||
offerbook.filterByPaymentMethod=Filtra per metodo di pagamento
|
offerbook.filterByPaymentMethod=Filtra per metodo di pagamento
|
||||||
offerbook.matchingOffers=Offers matching my accounts
|
offerbook.matchingOffers=Offerte che corrispondono ai miei account
|
||||||
|
offerbook.filterNoDeposit=Nessun deposito
|
||||||
|
offerbook.noDepositOffers=Offerte senza deposito (passphrase richiesta)
|
||||||
offerbook.timeSinceSigning=Account info
|
offerbook.timeSinceSigning=Account info
|
||||||
offerbook.timeSinceSigning.info=Questo account è stato verificato e {0}
|
offerbook.timeSinceSigning.info=Questo account è stato verificato e {0}
|
||||||
offerbook.timeSinceSigning.info.arbitrator=firmato da un arbitro e può firmare account peer
|
offerbook.timeSinceSigning.info.arbitrator=firmato da un arbitro e può firmare account peer
|
||||||
@ -484,7 +489,10 @@ createOffer.setDepositAsBuyer=Imposta il mio deposito cauzionale come acquirente
|
|||||||
createOffer.setDepositForBothTraders=Set both traders' security deposit (%)
|
createOffer.setDepositForBothTraders=Set both traders' security deposit (%)
|
||||||
createOffer.securityDepositInfo=Il deposito cauzionale dell'acquirente sarà {0}
|
createOffer.securityDepositInfo=Il deposito cauzionale dell'acquirente sarà {0}
|
||||||
createOffer.securityDepositInfoAsBuyer=Il tuo deposito cauzionale come acquirente sarà {0}
|
createOffer.securityDepositInfoAsBuyer=Il tuo deposito cauzionale come acquirente sarà {0}
|
||||||
createOffer.minSecurityDepositUsed=Viene utilizzato il minimo deposito cauzionale dell'acquirente
|
createOffer.minSecurityDepositUsed=Il deposito di sicurezza minimo è utilizzato
|
||||||
|
createOffer.buyerAsTakerWithoutDeposit=Nessun deposito richiesto dal compratore (protetto da passphrase)
|
||||||
|
createOffer.myDeposit=Il mio deposito di sicurezza (%)
|
||||||
|
createOffer.myDepositInfo=Il tuo deposito di sicurezza sarà {0}
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -508,6 +516,8 @@ takeOffer.fundsBox.networkFee=Totale commissioni di mining
|
|||||||
takeOffer.fundsBox.takeOfferSpinnerInfo=Accettare l'offerta: {0}
|
takeOffer.fundsBox.takeOfferSpinnerInfo=Accettare l'offerta: {0}
|
||||||
takeOffer.fundsBox.paymentLabel=Scambia Haveno con ID {0}
|
takeOffer.fundsBox.paymentLabel=Scambia Haveno con ID {0}
|
||||||
takeOffer.fundsBox.fundsStructure=({0} deposito cauzionale, {1} commissione commerciale, {2} commissione mineraria)
|
takeOffer.fundsBox.fundsStructure=({0} deposito cauzionale, {1} commissione commerciale, {2} commissione mineraria)
|
||||||
|
takeOffer.fundsBox.noFundingRequiredTitle=Nessun finanziamento richiesto
|
||||||
|
takeOffer.fundsBox.noFundingRequiredDescription=Ottieni la passphrase dell'offerta dal venditore fuori da Haveno per accettare questa offerta.
|
||||||
takeOffer.success.headline=Hai accettato con successo un'offerta.
|
takeOffer.success.headline=Hai accettato con successo un'offerta.
|
||||||
takeOffer.success.info=Puoi vedere lo stato del tuo scambio su \"Portafoglio/Scambi aperti\".
|
takeOffer.success.info=Puoi vedere lo stato del tuo scambio su \"Portafoglio/Scambi aperti\".
|
||||||
takeOffer.error.message=Si è verificato un errore durante l'accettazione dell'offerta.\n\n{0}
|
takeOffer.error.message=Si è verificato un errore durante l'accettazione dell'offerta.\n\n{0}
|
||||||
@ -1463,6 +1473,7 @@ offerDetailsWindow.confirm.maker=Conferma: Piazza l'offerta a {0} monero
|
|||||||
offerDetailsWindow.confirm.taker=Conferma: Accetta l'offerta a {0} monero
|
offerDetailsWindow.confirm.taker=Conferma: Accetta l'offerta a {0} monero
|
||||||
offerDetailsWindow.creationDate=Data di creazione
|
offerDetailsWindow.creationDate=Data di creazione
|
||||||
offerDetailsWindow.makersOnion=Indirizzo .onion del maker
|
offerDetailsWindow.makersOnion=Indirizzo .onion del maker
|
||||||
|
offerDetailsWindow.challenge=Passphrase dell'offerta
|
||||||
|
|
||||||
qRCodeWindow.headline=QR Code
|
qRCodeWindow.headline=QR Code
|
||||||
qRCodeWindow.msg=Please use this QR code for funding your Haveno wallet from your external wallet.
|
qRCodeWindow.msg=Please use this QR code for funding your Haveno wallet from your external wallet.
|
||||||
@ -1686,6 +1697,9 @@ popup.accountSigning.unsignedPubKeys.signed=Pubkeys were signed
|
|||||||
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
||||||
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign
|
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign
|
||||||
|
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit.headline=Nessun deposito richiesto dal compratore
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit=La tua offerta non richiederà un deposito di sicurezza o una commissione da parte dell'acquirente XMR.\n\nPer accettare la tua offerta, devi condividere una passphrase con il tuo partner commerciale al di fuori di Haveno.\n\nLa passphrase viene generata automaticamente e mostrata nei dettagli dell'offerta dopo la creazione.
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
# Notifications
|
# Notifications
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -1811,6 +1825,7 @@ navigation.support=\"Supporto\"
|
|||||||
|
|
||||||
formatter.formatVolumeLabel={0} importo{1}
|
formatter.formatVolumeLabel={0} importo{1}
|
||||||
formatter.makerTaker=Maker come {0} {1} / Taker come {2} {3}
|
formatter.makerTaker=Maker come {0} {1} / Taker come {2} {3}
|
||||||
|
formatter.makerTakerLocked=Maker come {0} {1} / Taker come {2} {3} 🔒
|
||||||
formatter.youAreAsMaker=You are: {1} {0} (maker) / Taker is: {3} {2}
|
formatter.youAreAsMaker=You are: {1} {0} (maker) / Taker is: {3} {2}
|
||||||
formatter.youAreAsTaker=You are: {1} {0} (taker) / Maker is: {3} {2}
|
formatter.youAreAsTaker=You are: {1} {0} (taker) / Maker is: {3} {2}
|
||||||
formatter.youAre=Sei {0} {1} ({2} {3})
|
formatter.youAre=Sei {0} {1} ({2} {3})
|
||||||
|
@ -40,6 +40,8 @@ shared.buyMonero=ビットコインを買う
|
|||||||
shared.sellMonero=ビットコインを売る
|
shared.sellMonero=ビットコインを売る
|
||||||
shared.buyCurrency={0}を買う
|
shared.buyCurrency={0}を買う
|
||||||
shared.sellCurrency={0}を売る
|
shared.sellCurrency={0}を売る
|
||||||
|
shared.buyCurrencyLocked={0}を買う 🔒
|
||||||
|
shared.sellCurrencyLocked={0}を売る 🔒
|
||||||
shared.buyingXMRWith=XMRを{0}で買う
|
shared.buyingXMRWith=XMRを{0}で買う
|
||||||
shared.sellingXMRFor=XMRを{0}で売る
|
shared.sellingXMRFor=XMRを{0}で売る
|
||||||
shared.buyingCurrency={0}を購入中 (XMRを売却中)
|
shared.buyingCurrency={0}を購入中 (XMRを売却中)
|
||||||
@ -330,6 +332,7 @@ offerbook.createOffer=オファーを作る
|
|||||||
offerbook.takeOffer=オファーを受ける
|
offerbook.takeOffer=オファーを受ける
|
||||||
offerbook.takeOfferToBuy={0}購入オファーを受ける
|
offerbook.takeOfferToBuy={0}購入オファーを受ける
|
||||||
offerbook.takeOfferToSell={0}売却オファーを受ける
|
offerbook.takeOfferToSell={0}売却オファーを受ける
|
||||||
|
offerbook.takeOffer.enterChallenge=オファーのパスフレーズを入力してください
|
||||||
offerbook.trader=取引者
|
offerbook.trader=取引者
|
||||||
offerbook.offerersBankId=メイカーの銀行ID (BIC/SWIFT): {0}
|
offerbook.offerersBankId=メイカーの銀行ID (BIC/SWIFT): {0}
|
||||||
offerbook.offerersBankName=メーカーの銀行名: {0}
|
offerbook.offerersBankName=メーカーの銀行名: {0}
|
||||||
@ -340,6 +343,8 @@ offerbook.availableOffers=利用可能なオファー
|
|||||||
offerbook.filterByCurrency=通貨でフィルター
|
offerbook.filterByCurrency=通貨でフィルター
|
||||||
offerbook.filterByPaymentMethod=支払い方法でフィルター
|
offerbook.filterByPaymentMethod=支払い方法でフィルター
|
||||||
offerbook.matchingOffers=アカウントと一致するオファー
|
offerbook.matchingOffers=アカウントと一致するオファー
|
||||||
|
offerbook.filterNoDeposit=デポジットなし
|
||||||
|
offerbook.noDepositOffers=預金不要のオファー(パスフレーズ必須)
|
||||||
offerbook.timeSinceSigning=アカウント情報
|
offerbook.timeSinceSigning=アカウント情報
|
||||||
offerbook.timeSinceSigning.info=このアカウントは認証されまして、{0}
|
offerbook.timeSinceSigning.info=このアカウントは認証されまして、{0}
|
||||||
offerbook.timeSinceSigning.info.arbitrator=調停人に署名されました。ピアアカウントも署名できます
|
offerbook.timeSinceSigning.info.arbitrator=調停人に署名されました。ピアアカウントも署名できます
|
||||||
@ -485,7 +490,10 @@ createOffer.setDepositAsBuyer=購入時のセキュリティデポジット (%)
|
|||||||
createOffer.setDepositForBothTraders=両方の取引者の保証金を設定する(%)
|
createOffer.setDepositForBothTraders=両方の取引者の保証金を設定する(%)
|
||||||
createOffer.securityDepositInfo=あなたの買い手のセキュリティデポジットは{0}です
|
createOffer.securityDepositInfo=あなたの買い手のセキュリティデポジットは{0}です
|
||||||
createOffer.securityDepositInfoAsBuyer=あなたの購入時のセキュリティデポジットは{0}です
|
createOffer.securityDepositInfoAsBuyer=あなたの購入時のセキュリティデポジットは{0}です
|
||||||
createOffer.minSecurityDepositUsed=最小値の買い手の保証金は使用されます
|
createOffer.minSecurityDepositUsed=最低セキュリティデポジットが使用されます
|
||||||
|
createOffer.buyerAsTakerWithoutDeposit=購入者に保証金は不要(パスフレーズ保護)
|
||||||
|
createOffer.myDeposit=私の保証金(%)
|
||||||
|
createOffer.myDepositInfo=あなたのセキュリティデポジットは{0}です
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -509,6 +517,8 @@ takeOffer.fundsBox.networkFee=合計マイニング手数料
|
|||||||
takeOffer.fundsBox.takeOfferSpinnerInfo=オファーを受け入れる: {0}
|
takeOffer.fundsBox.takeOfferSpinnerInfo=オファーを受け入れる: {0}
|
||||||
takeOffer.fundsBox.paymentLabel=次のIDとのHavenoトレード: {0}
|
takeOffer.fundsBox.paymentLabel=次のIDとのHavenoトレード: {0}
|
||||||
takeOffer.fundsBox.fundsStructure=({0} セキュリティデポジット, {1} 取引手数料, {2}マイニング手数料)
|
takeOffer.fundsBox.fundsStructure=({0} セキュリティデポジット, {1} 取引手数料, {2}マイニング手数料)
|
||||||
|
takeOffer.fundsBox.noFundingRequiredTitle=資金は必要ありません
|
||||||
|
takeOffer.fundsBox.noFundingRequiredDescription=このオファーを受けるには、Haveno外で売り手からオファーパスフレーズを取得してください。
|
||||||
takeOffer.success.headline=オファー受け入れに成功しました
|
takeOffer.success.headline=オファー受け入れに成功しました
|
||||||
takeOffer.success.info=あなたのトレード状態は「ポートフォリオ/オープントレード」で見られます
|
takeOffer.success.info=あなたのトレード状態は「ポートフォリオ/オープントレード」で見られます
|
||||||
takeOffer.error.message=オファーの受け入れ時にエラーが発生しました。\n\n{0}
|
takeOffer.error.message=オファーの受け入れ時にエラーが発生しました。\n\n{0}
|
||||||
@ -1464,6 +1474,7 @@ offerDetailsWindow.confirm.maker=承認: ビットコインを{0}オファーを
|
|||||||
offerDetailsWindow.confirm.taker=承認: ビットコインを{0}オファーを受ける
|
offerDetailsWindow.confirm.taker=承認: ビットコインを{0}オファーを受ける
|
||||||
offerDetailsWindow.creationDate=作成日
|
offerDetailsWindow.creationDate=作成日
|
||||||
offerDetailsWindow.makersOnion=メイカーのonionアドレス
|
offerDetailsWindow.makersOnion=メイカーのonionアドレス
|
||||||
|
offerDetailsWindow.challenge=オファーパスフレーズ
|
||||||
|
|
||||||
qRCodeWindow.headline=QRコード
|
qRCodeWindow.headline=QRコード
|
||||||
qRCodeWindow.msg=外部ウォレットからHavenoウォレットへ送金するのに、このQRコードを利用して下さい。
|
qRCodeWindow.msg=外部ウォレットからHavenoウォレットへ送金するのに、このQRコードを利用して下さい。
|
||||||
@ -1689,6 +1700,9 @@ popup.accountSigning.unsignedPubKeys.signed=パブリックキーは署名され
|
|||||||
popup.accountSigning.unsignedPubKeys.result.signed=署名されたパブリックキー
|
popup.accountSigning.unsignedPubKeys.result.signed=署名されたパブリックキー
|
||||||
popup.accountSigning.unsignedPubKeys.result.failed=署名が失敗しました
|
popup.accountSigning.unsignedPubKeys.result.failed=署名が失敗しました
|
||||||
|
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit.headline=購入者による保証金は不要
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit=あなたのオファーには、XMR購入者からのセキュリティデポジットや手数料は必要ありません。\n\nオファーを受け入れるには、Haveno外で取引相手とパスフレーズを共有する必要があります。\n\nパスフレーズは自動的に生成され、作成後にオファーの詳細に表示されます。
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
# Notifications
|
# Notifications
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -1814,6 +1828,7 @@ navigation.support=「サポート」
|
|||||||
|
|
||||||
formatter.formatVolumeLabel={0} 額{1}
|
formatter.formatVolumeLabel={0} 額{1}
|
||||||
formatter.makerTaker=メイカーは{0} {1} / テイカーは{2} {3}
|
formatter.makerTaker=メイカーは{0} {1} / テイカーは{2} {3}
|
||||||
|
formatter.makerTakerLocked=メイカーは{0} {1} / テイカーは{2} {3} 🔒
|
||||||
formatter.youAreAsMaker=あなたは:{1} {0}(メイカー) / テイカーは:{3} {2}
|
formatter.youAreAsMaker=あなたは:{1} {0}(メイカー) / テイカーは:{3} {2}
|
||||||
formatter.youAreAsTaker=あなたは:{1} {0}(テイカー) / メイカーは{3} {2}
|
formatter.youAreAsTaker=あなたは:{1} {0}(テイカー) / メイカーは{3} {2}
|
||||||
formatter.youAre=あなたは{0} {1} ({2} {3})
|
formatter.youAre=あなたは{0} {1} ({2} {3})
|
||||||
|
@ -40,6 +40,8 @@ shared.buyMonero=Comprar monero
|
|||||||
shared.sellMonero=Vender monero
|
shared.sellMonero=Vender monero
|
||||||
shared.buyCurrency=Comprar {0}
|
shared.buyCurrency=Comprar {0}
|
||||||
shared.sellCurrency=Vender {0}
|
shared.sellCurrency=Vender {0}
|
||||||
|
shared.buyCurrencyLocked=Comprar {0} 🔒
|
||||||
|
shared.sellCurrencyLocked=Vender {0} 🔒
|
||||||
shared.buyingXMRWith=comprando XMR com {0}
|
shared.buyingXMRWith=comprando XMR com {0}
|
||||||
shared.sellingXMRFor=vendendo XMR por {0}
|
shared.sellingXMRFor=vendendo XMR por {0}
|
||||||
shared.buyingCurrency=comprando {0} (vendendo XMR)
|
shared.buyingCurrency=comprando {0} (vendendo XMR)
|
||||||
@ -333,6 +335,7 @@ offerbook.createOffer=Criar oferta
|
|||||||
offerbook.takeOffer=Aceitar oferta
|
offerbook.takeOffer=Aceitar oferta
|
||||||
offerbook.takeOfferToBuy=Comprar {0}
|
offerbook.takeOfferToBuy=Comprar {0}
|
||||||
offerbook.takeOfferToSell=Vender {0}
|
offerbook.takeOfferToSell=Vender {0}
|
||||||
|
offerbook.takeOffer.enterChallenge=Digite a senha da oferta
|
||||||
offerbook.trader=Trader
|
offerbook.trader=Trader
|
||||||
offerbook.offerersBankId=ID do banco do ofertante (BIC/SWIFT): {0}
|
offerbook.offerersBankId=ID do banco do ofertante (BIC/SWIFT): {0}
|
||||||
offerbook.offerersBankName=Nome do banco do ofertante: {0}
|
offerbook.offerersBankName=Nome do banco do ofertante: {0}
|
||||||
@ -342,7 +345,9 @@ offerbook.offerersAcceptedBankSeats=Países aceitos como sede bancária (tomador
|
|||||||
offerbook.availableOffers=Ofertas disponíveis
|
offerbook.availableOffers=Ofertas disponíveis
|
||||||
offerbook.filterByCurrency=Filtrar por moeda
|
offerbook.filterByCurrency=Filtrar por moeda
|
||||||
offerbook.filterByPaymentMethod=Filtrar por método de pagamento
|
offerbook.filterByPaymentMethod=Filtrar por método de pagamento
|
||||||
offerbook.matchingOffers=Offers matching my accounts
|
offerbook.matchingOffers=Ofertas que correspondem às minhas contas
|
||||||
|
offerbook.filterNoDeposit=Sem depósito
|
||||||
|
offerbook.noDepositOffers=Ofertas sem depósito (senha necessária)
|
||||||
offerbook.timeSinceSigning=Account info
|
offerbook.timeSinceSigning=Account info
|
||||||
offerbook.timeSinceSigning.info=Esta conta foi verificada e {0}
|
offerbook.timeSinceSigning.info=Esta conta foi verificada e {0}
|
||||||
offerbook.timeSinceSigning.info.arbitrator=assinada por um árbitro e pode assinar contas de pares
|
offerbook.timeSinceSigning.info.arbitrator=assinada por um árbitro e pode assinar contas de pares
|
||||||
@ -487,7 +492,10 @@ createOffer.setDepositAsBuyer=Definir o meu depósito de segurança como comprad
|
|||||||
createOffer.setDepositForBothTraders=Set both traders' security deposit (%)
|
createOffer.setDepositForBothTraders=Set both traders' security deposit (%)
|
||||||
createOffer.securityDepositInfo=O seu depósito de segurança do comprador será de {0}
|
createOffer.securityDepositInfo=O seu depósito de segurança do comprador será de {0}
|
||||||
createOffer.securityDepositInfoAsBuyer=O seu depósito de segurança como comprador será de {0}
|
createOffer.securityDepositInfoAsBuyer=O seu depósito de segurança como comprador será de {0}
|
||||||
createOffer.minSecurityDepositUsed=Depósito de segurança mínimo para compradores foi usado
|
createOffer.minSecurityDepositUsed=O depósito de segurança mínimo é utilizado
|
||||||
|
createOffer.buyerAsTakerWithoutDeposit=Nenhum depósito necessário do comprador (protegido por senha)
|
||||||
|
createOffer.myDeposit=Meu depósito de segurança (%)
|
||||||
|
createOffer.myDepositInfo=Seu depósito de segurança será {0}
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -511,6 +519,8 @@ takeOffer.fundsBox.networkFee=Total em taxas de mineração
|
|||||||
takeOffer.fundsBox.takeOfferSpinnerInfo=Aceitando a oferta: {0}
|
takeOffer.fundsBox.takeOfferSpinnerInfo=Aceitando a oferta: {0}
|
||||||
takeOffer.fundsBox.paymentLabel=negociação Haveno com ID {0}
|
takeOffer.fundsBox.paymentLabel=negociação Haveno com ID {0}
|
||||||
takeOffer.fundsBox.fundsStructure=({0} depósito de segurança, {1} taxa de transação, {2} taxa de mineração)
|
takeOffer.fundsBox.fundsStructure=({0} depósito de segurança, {1} taxa de transação, {2} taxa de mineração)
|
||||||
|
takeOffer.fundsBox.noFundingRequiredTitle=Sem financiamento necessário
|
||||||
|
takeOffer.fundsBox.noFundingRequiredDescription=Obtenha a frase secreta da oferta com o vendedor fora do Haveno para aceitar esta oferta.
|
||||||
takeOffer.success.headline=Você aceitou uma oferta com sucesso.
|
takeOffer.success.headline=Você aceitou uma oferta com sucesso.
|
||||||
takeOffer.success.info=Você pode ver o status de sua negociação em \"Portfolio/Negociações em aberto\".
|
takeOffer.success.info=Você pode ver o status de sua negociação em \"Portfolio/Negociações em aberto\".
|
||||||
takeOffer.error.message=Ocorreu um erro ao aceitar a oferta.\n\n{0}
|
takeOffer.error.message=Ocorreu um erro ao aceitar a oferta.\n\n{0}
|
||||||
@ -1467,6 +1477,7 @@ offerDetailsWindow.confirm.maker=Criar oferta para {0} monero
|
|||||||
offerDetailsWindow.confirm.taker=Confirmar: Aceitar oferta de {0} monero
|
offerDetailsWindow.confirm.taker=Confirmar: Aceitar oferta de {0} monero
|
||||||
offerDetailsWindow.creationDate=Criada em
|
offerDetailsWindow.creationDate=Criada em
|
||||||
offerDetailsWindow.makersOnion=Endereço onion do ofertante
|
offerDetailsWindow.makersOnion=Endereço onion do ofertante
|
||||||
|
offerDetailsWindow.challenge=Passphrase da oferta
|
||||||
|
|
||||||
qRCodeWindow.headline=QR Code
|
qRCodeWindow.headline=QR Code
|
||||||
qRCodeWindow.msg=Please use this QR code for funding your Haveno wallet from your external wallet.
|
qRCodeWindow.msg=Please use this QR code for funding your Haveno wallet from your external wallet.
|
||||||
@ -1693,6 +1704,9 @@ popup.accountSigning.unsignedPubKeys.signed=Pubkeys were signed
|
|||||||
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
||||||
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign
|
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign
|
||||||
|
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit.headline=Nenhum depósito exigido do comprador
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit=Sua oferta não exigirá um depósito de segurança ou taxa do comprador de XMR.\n\nPara aceitar sua oferta, você deve compartilhar uma senha com seu parceiro de negociação fora do Haveno.\n\nA senha é gerada automaticamente e exibida nos detalhes da oferta após a criação.
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
# Notifications
|
# Notifications
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -1818,6 +1832,7 @@ navigation.support=\"Suporte\"
|
|||||||
|
|
||||||
formatter.formatVolumeLabel={0} quantia{1}
|
formatter.formatVolumeLabel={0} quantia{1}
|
||||||
formatter.makerTaker=Ofertante: {1} de {0} / Aceitador: {3} de {2}
|
formatter.makerTaker=Ofertante: {1} de {0} / Aceitador: {3} de {2}
|
||||||
|
formatter.makerTakerLocked=Ofertante: {1} de {0} / Aceitador: {3} de {2} 🔒
|
||||||
formatter.youAreAsMaker=You are: {1} {0} (maker) / Taker is: {3} {2}
|
formatter.youAreAsMaker=You are: {1} {0} (maker) / Taker is: {3} {2}
|
||||||
formatter.youAreAsTaker=You are: {1} {0} (taker) / Maker is: {3} {2}
|
formatter.youAreAsTaker=You are: {1} {0} (taker) / Maker is: {3} {2}
|
||||||
formatter.youAre=Você está {0} {1} ({2} {3})
|
formatter.youAre=Você está {0} {1} ({2} {3})
|
||||||
|
@ -40,6 +40,8 @@ shared.buyMonero=Comprar monero
|
|||||||
shared.sellMonero=Vender monero
|
shared.sellMonero=Vender monero
|
||||||
shared.buyCurrency=Comprar {0}
|
shared.buyCurrency=Comprar {0}
|
||||||
shared.sellCurrency=Vender {0}
|
shared.sellCurrency=Vender {0}
|
||||||
|
shared.buyCurrencyLocked=Comprar {0} 🔒
|
||||||
|
shared.sellCurrencyLocked=Vender {0} 🔒
|
||||||
shared.buyingXMRWith=comprando XMR com {0}
|
shared.buyingXMRWith=comprando XMR com {0}
|
||||||
shared.sellingXMRFor=vendendo XMR por {0}
|
shared.sellingXMRFor=vendendo XMR por {0}
|
||||||
shared.buyingCurrency=comprando {0} (vendendo XMR)
|
shared.buyingCurrency=comprando {0} (vendendo XMR)
|
||||||
@ -193,7 +195,7 @@ shared.iConfirm=Eu confirmo
|
|||||||
shared.openURL=Abrir {0}
|
shared.openURL=Abrir {0}
|
||||||
shared.fiat=Moeda fiduciária
|
shared.fiat=Moeda fiduciária
|
||||||
shared.crypto=Cripto
|
shared.crypto=Cripto
|
||||||
shared.preciousMetals=TODO
|
shared.preciousMetals=Metais Preciosos
|
||||||
shared.all=Tudo
|
shared.all=Tudo
|
||||||
shared.edit=Editar
|
shared.edit=Editar
|
||||||
shared.advancedOptions=Opções avançadas
|
shared.advancedOptions=Opções avançadas
|
||||||
@ -330,6 +332,7 @@ offerbook.createOffer=Criar oferta
|
|||||||
offerbook.takeOffer=Aceitar oferta
|
offerbook.takeOffer=Aceitar oferta
|
||||||
offerbook.takeOfferToBuy=Aceitar oferta para comprar {0}
|
offerbook.takeOfferToBuy=Aceitar oferta para comprar {0}
|
||||||
offerbook.takeOfferToSell=Aceitar oferta para vender {0}
|
offerbook.takeOfferToSell=Aceitar oferta para vender {0}
|
||||||
|
offerbook.takeOffer.enterChallenge=Digite a senha da oferta
|
||||||
offerbook.trader=Negociador
|
offerbook.trader=Negociador
|
||||||
offerbook.offerersBankId=ID do banco do ofertante (BIC/SWIFT): {0}
|
offerbook.offerersBankId=ID do banco do ofertante (BIC/SWIFT): {0}
|
||||||
offerbook.offerersBankName=Nome do banco do ofertante: {0}
|
offerbook.offerersBankName=Nome do banco do ofertante: {0}
|
||||||
@ -339,7 +342,9 @@ offerbook.offerersAcceptedBankSeats=Sede do banco aceite (aceitador):\n {0}
|
|||||||
offerbook.availableOffers=Ofertas disponíveis
|
offerbook.availableOffers=Ofertas disponíveis
|
||||||
offerbook.filterByCurrency=Filtrar por moeda
|
offerbook.filterByCurrency=Filtrar por moeda
|
||||||
offerbook.filterByPaymentMethod=Filtrar por método de pagamento
|
offerbook.filterByPaymentMethod=Filtrar por método de pagamento
|
||||||
offerbook.matchingOffers=Offers matching my accounts
|
offerbook.matchingOffers=Ofertas que correspondem às minhas contas
|
||||||
|
offerbook.filterNoDeposit=Sem depósito
|
||||||
|
offerbook.noDepositOffers=Ofertas sem depósito (senha necessária)
|
||||||
offerbook.timeSinceSigning=Account info
|
offerbook.timeSinceSigning=Account info
|
||||||
offerbook.timeSinceSigning.info=Esta conta foi verificada e {0}
|
offerbook.timeSinceSigning.info=Esta conta foi verificada e {0}
|
||||||
offerbook.timeSinceSigning.info.arbitrator=assinada pelo árbitro e pode assinar contas de pares
|
offerbook.timeSinceSigning.info.arbitrator=assinada pelo árbitro e pode assinar contas de pares
|
||||||
@ -484,7 +489,10 @@ createOffer.setDepositAsBuyer=Definir o meu depósito de segurança enquanto com
|
|||||||
createOffer.setDepositForBothTraders=Set both traders' security deposit (%)
|
createOffer.setDepositForBothTraders=Set both traders' security deposit (%)
|
||||||
createOffer.securityDepositInfo=O depósito de segurança do seu comprador será {0}
|
createOffer.securityDepositInfo=O depósito de segurança do seu comprador será {0}
|
||||||
createOffer.securityDepositInfoAsBuyer=O seu depósito de segurança enquanto comprador será {0}
|
createOffer.securityDepositInfoAsBuyer=O seu depósito de segurança enquanto comprador será {0}
|
||||||
createOffer.minSecurityDepositUsed=O mín. depósito de segurança para o comprador é utilizado
|
createOffer.minSecurityDepositUsed=O depósito de segurança mínimo é utilizado
|
||||||
|
createOffer.buyerAsTakerWithoutDeposit=Nenhum depósito exigido do comprador (protegido por frase secreta)
|
||||||
|
createOffer.myDeposit=Meu depósito de segurança (%)
|
||||||
|
createOffer.myDepositInfo=Seu depósito de segurança será {0}
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -508,6 +516,8 @@ takeOffer.fundsBox.networkFee=Total de taxas de mineração
|
|||||||
takeOffer.fundsBox.takeOfferSpinnerInfo=Aceitando a oferta: {0}
|
takeOffer.fundsBox.takeOfferSpinnerInfo=Aceitando a oferta: {0}
|
||||||
takeOffer.fundsBox.paymentLabel=negócio do Haveno com ID {0}
|
takeOffer.fundsBox.paymentLabel=negócio do Haveno com ID {0}
|
||||||
takeOffer.fundsBox.fundsStructure=({0} depósito de segurança, {1} taxa de negócio, {2} taxa de mineração)
|
takeOffer.fundsBox.fundsStructure=({0} depósito de segurança, {1} taxa de negócio, {2} taxa de mineração)
|
||||||
|
takeOffer.fundsBox.noFundingRequiredTitle=Nenhum financiamento necessário
|
||||||
|
takeOffer.fundsBox.noFundingRequiredDescription=Obtenha a senha da oferta com o vendedor fora do Haveno para aceitar esta oferta.
|
||||||
takeOffer.success.headline=Você aceitou uma oferta com sucesso.
|
takeOffer.success.headline=Você aceitou uma oferta com sucesso.
|
||||||
takeOffer.success.info=Você pode ver o estado de seu negócio em \"Portefólio/Negócios abertos\".
|
takeOffer.success.info=Você pode ver o estado de seu negócio em \"Portefólio/Negócios abertos\".
|
||||||
takeOffer.error.message=Ocorreu um erro ao aceitar a oferta .\n\n{0}
|
takeOffer.error.message=Ocorreu um erro ao aceitar a oferta .\n\n{0}
|
||||||
@ -1460,6 +1470,7 @@ offerDetailsWindow.confirm.maker=Confirmar: Criar oferta para {0} monero
|
|||||||
offerDetailsWindow.confirm.taker=Confirmar: Aceitar oferta de {0} monero
|
offerDetailsWindow.confirm.taker=Confirmar: Aceitar oferta de {0} monero
|
||||||
offerDetailsWindow.creationDate=Data de criação
|
offerDetailsWindow.creationDate=Data de criação
|
||||||
offerDetailsWindow.makersOnion=Endereço onion do ofertante
|
offerDetailsWindow.makersOnion=Endereço onion do ofertante
|
||||||
|
offerDetailsWindow.challenge=Passphrase da oferta
|
||||||
|
|
||||||
qRCodeWindow.headline=QR Code
|
qRCodeWindow.headline=QR Code
|
||||||
qRCodeWindow.msg=Please use this QR code for funding your Haveno wallet from your external wallet.
|
qRCodeWindow.msg=Please use this QR code for funding your Haveno wallet from your external wallet.
|
||||||
@ -1683,6 +1694,9 @@ popup.accountSigning.unsignedPubKeys.signed=Pubkeys were signed
|
|||||||
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
||||||
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign
|
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign
|
||||||
|
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit.headline=Nenhum depósito exigido do comprador
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit=Sua oferta não exigirá um depósito de segurança ou taxa do comprador de XMR.\n\nPara aceitar sua oferta, você deve compartilhar uma senha com seu parceiro comercial fora do Haveno.\n\nA senha é gerada automaticamente e exibida nos detalhes da oferta após a criação.
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
# Notifications
|
# Notifications
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -1808,6 +1822,7 @@ navigation.support=\"Apoio\"
|
|||||||
|
|
||||||
formatter.formatVolumeLabel={0} quantia{1}
|
formatter.formatVolumeLabel={0} quantia{1}
|
||||||
formatter.makerTaker=Ofertante como {0} {1} / Aceitador como {2} {3}
|
formatter.makerTaker=Ofertante como {0} {1} / Aceitador como {2} {3}
|
||||||
|
formatter.makerTakerLocked=Ofertante como {0} {1} / Aceitador como {2} {3} 🔒
|
||||||
formatter.youAreAsMaker=You are: {1} {0} (maker) / Taker is: {3} {2}
|
formatter.youAreAsMaker=You are: {1} {0} (maker) / Taker is: {3} {2}
|
||||||
formatter.youAreAsTaker=You are: {1} {0} (taker) / Maker is: {3} {2}
|
formatter.youAreAsTaker=You are: {1} {0} (taker) / Maker is: {3} {2}
|
||||||
formatter.youAre=Você é {0} {1} ({2} {3})
|
formatter.youAre=Você é {0} {1} ({2} {3})
|
||||||
|
@ -40,6 +40,8 @@ shared.buyMonero=Купить биткойн
|
|||||||
shared.sellMonero=Продать биткойн
|
shared.sellMonero=Продать биткойн
|
||||||
shared.buyCurrency=Купить {0}
|
shared.buyCurrency=Купить {0}
|
||||||
shared.sellCurrency=Продать {0}
|
shared.sellCurrency=Продать {0}
|
||||||
|
shared.buyCurrencyLocked=Купить {0} 🔒
|
||||||
|
shared.sellCurrencyLocked=Продать {0} 🔒
|
||||||
shared.buyingXMRWith=покупка ВТС за {0}
|
shared.buyingXMRWith=покупка ВТС за {0}
|
||||||
shared.sellingXMRFor=продажа ВТС за {0}
|
shared.sellingXMRFor=продажа ВТС за {0}
|
||||||
shared.buyingCurrency=покупка {0} (продажа ВТС)
|
shared.buyingCurrency=покупка {0} (продажа ВТС)
|
||||||
@ -330,6 +332,7 @@ offerbook.createOffer=Создать предложение
|
|||||||
offerbook.takeOffer=Принять предложение
|
offerbook.takeOffer=Принять предложение
|
||||||
offerbook.takeOfferToBuy=Принять предложение купить {0}
|
offerbook.takeOfferToBuy=Принять предложение купить {0}
|
||||||
offerbook.takeOfferToSell=Принять предложение продать {0}
|
offerbook.takeOfferToSell=Принять предложение продать {0}
|
||||||
|
offerbook.takeOffer.enterChallenge=Введите фразу-пароль предложения
|
||||||
offerbook.trader=Трейдер
|
offerbook.trader=Трейдер
|
||||||
offerbook.offerersBankId=Идент. банка (BIC/SWIFT) мейкера: {0}
|
offerbook.offerersBankId=Идент. банка (BIC/SWIFT) мейкера: {0}
|
||||||
offerbook.offerersBankName=Название банка мейкера: {0}
|
offerbook.offerersBankName=Название банка мейкера: {0}
|
||||||
@ -339,7 +342,9 @@ offerbook.offerersAcceptedBankSeats=Допустимые страны банка
|
|||||||
offerbook.availableOffers=Доступные предложения
|
offerbook.availableOffers=Доступные предложения
|
||||||
offerbook.filterByCurrency=Фильтровать по валюте
|
offerbook.filterByCurrency=Фильтровать по валюте
|
||||||
offerbook.filterByPaymentMethod=Фильтровать по способу оплаты
|
offerbook.filterByPaymentMethod=Фильтровать по способу оплаты
|
||||||
offerbook.matchingOffers=Offers matching my accounts
|
offerbook.matchingOffers=Предложения, соответствующие моим аккаунтам
|
||||||
|
offerbook.filterNoDeposit=Нет депозита
|
||||||
|
offerbook.noDepositOffers=Предложения без депозита (требуется пароль)
|
||||||
offerbook.timeSinceSigning=Account info
|
offerbook.timeSinceSigning=Account info
|
||||||
offerbook.timeSinceSigning.info=This account was verified and {0}
|
offerbook.timeSinceSigning.info=This account was verified and {0}
|
||||||
offerbook.timeSinceSigning.info.arbitrator=signed by an arbitrator and can sign peer accounts
|
offerbook.timeSinceSigning.info.arbitrator=signed by an arbitrator and can sign peer accounts
|
||||||
@ -484,7 +489,10 @@ createOffer.setDepositAsBuyer=Установить мой залог как по
|
|||||||
createOffer.setDepositForBothTraders=Set both traders' security deposit (%)
|
createOffer.setDepositForBothTraders=Set both traders' security deposit (%)
|
||||||
createOffer.securityDepositInfo=Сумма залога покупателя: {0}
|
createOffer.securityDepositInfo=Сумма залога покупателя: {0}
|
||||||
createOffer.securityDepositInfoAsBuyer=Сумма вашего залога: {0}
|
createOffer.securityDepositInfoAsBuyer=Сумма вашего залога: {0}
|
||||||
createOffer.minSecurityDepositUsed=Min. buyer security deposit is used
|
createOffer.minSecurityDepositUsed=Минимальный залог используется
|
||||||
|
createOffer.buyerAsTakerWithoutDeposit=Залог от покупателя не требуется (защищено паролем)
|
||||||
|
createOffer.myDeposit=Мой залог (%)
|
||||||
|
createOffer.myDepositInfo=Ваш залог составит {0}
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -508,6 +516,8 @@ takeOffer.fundsBox.networkFee=Oбщая комиссия майнера
|
|||||||
takeOffer.fundsBox.takeOfferSpinnerInfo=Принятие предложения: {0}
|
takeOffer.fundsBox.takeOfferSpinnerInfo=Принятие предложения: {0}
|
||||||
takeOffer.fundsBox.paymentLabel=Сделка в Haveno с идентификатором {0}
|
takeOffer.fundsBox.paymentLabel=Сделка в Haveno с идентификатором {0}
|
||||||
takeOffer.fundsBox.fundsStructure=({0} — залог, {1} — комиссия за сделку, {2} — комиссия майнера)
|
takeOffer.fundsBox.fundsStructure=({0} — залог, {1} — комиссия за сделку, {2} — комиссия майнера)
|
||||||
|
takeOffer.fundsBox.noFundingRequiredTitle=Не требуется финансирование
|
||||||
|
takeOffer.fundsBox.noFundingRequiredDescription=Получите пароль предложения от продавца вне Haveno, чтобы принять это предложение.
|
||||||
takeOffer.success.headline=Вы успешно приняли предложение.
|
takeOffer.success.headline=Вы успешно приняли предложение.
|
||||||
takeOffer.success.info=Статус вашей сделки отображается в разделе \«Папка/Текущие сделки\».
|
takeOffer.success.info=Статус вашей сделки отображается в разделе \«Папка/Текущие сделки\».
|
||||||
takeOffer.error.message=Ошибка при принятии предложения:\n\n{0}
|
takeOffer.error.message=Ошибка при принятии предложения:\n\n{0}
|
||||||
@ -1461,6 +1471,7 @@ offerDetailsWindow.confirm.maker=Подтвердите: разместить п
|
|||||||
offerDetailsWindow.confirm.taker=Подтвердите: принять предложение {0} биткойн
|
offerDetailsWindow.confirm.taker=Подтвердите: принять предложение {0} биткойн
|
||||||
offerDetailsWindow.creationDate=Дата создания
|
offerDetailsWindow.creationDate=Дата создания
|
||||||
offerDetailsWindow.makersOnion=Onion-адрес мейкера
|
offerDetailsWindow.makersOnion=Onion-адрес мейкера
|
||||||
|
offerDetailsWindow.challenge=Пароль предложения
|
||||||
|
|
||||||
qRCodeWindow.headline=QR Code
|
qRCodeWindow.headline=QR Code
|
||||||
qRCodeWindow.msg=Please use this QR code for funding your Haveno wallet from your external wallet.
|
qRCodeWindow.msg=Please use this QR code for funding your Haveno wallet from your external wallet.
|
||||||
@ -1684,6 +1695,9 @@ popup.accountSigning.unsignedPubKeys.signed=Pubkeys were signed
|
|||||||
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
||||||
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign
|
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign
|
||||||
|
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit.headline=Депозит от покупателя не требуется
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit=Ваше предложение не потребует залога или комиссии от покупателя XMR.\n\nЧтобы принять ваше предложение, вы должны поделиться парольной фразой с вашим торговым партнером вне Haveno.\n\nПарольная фраза генерируется автоматически и отображается в деталях предложения после его создания.
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
# Notifications
|
# Notifications
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -1809,6 +1823,7 @@ navigation.support=\«Поддержка\»
|
|||||||
|
|
||||||
formatter.formatVolumeLabel={0} сумма {1}
|
formatter.formatVolumeLabel={0} сумма {1}
|
||||||
formatter.makerTaker=Мейкер как {0} {1} / Тейкер как {2} {3}
|
formatter.makerTaker=Мейкер как {0} {1} / Тейкер как {2} {3}
|
||||||
|
formatter.makerTakerLocked=Мейкер как {0} {1} / Тейкер как {2} {3} 🔒
|
||||||
formatter.youAreAsMaker=You are: {1} {0} (maker) / Taker is: {3} {2}
|
formatter.youAreAsMaker=You are: {1} {0} (maker) / Taker is: {3} {2}
|
||||||
formatter.youAreAsTaker=You are: {1} {0} (taker) / Maker is: {3} {2}
|
formatter.youAreAsTaker=You are: {1} {0} (taker) / Maker is: {3} {2}
|
||||||
formatter.youAre=Вы {0} {1} ({2} {3})
|
formatter.youAre=Вы {0} {1} ({2} {3})
|
||||||
|
@ -40,6 +40,8 @@ shared.buyMonero=ซื้อ monero (บิตคอยน์)
|
|||||||
shared.sellMonero=ขาย monero (บิตคอยน์)
|
shared.sellMonero=ขาย monero (บิตคอยน์)
|
||||||
shared.buyCurrency=ซื้อ {0}
|
shared.buyCurrency=ซื้อ {0}
|
||||||
shared.sellCurrency=ขาย {0}
|
shared.sellCurrency=ขาย {0}
|
||||||
|
shared.buyCurrencyLocked=ซื้อ {0} 🔒
|
||||||
|
shared.sellCurrencyLocked=ขาย {0} 🔒
|
||||||
shared.buyingXMRWith=การซื้อ XMR กับ {0}
|
shared.buyingXMRWith=การซื้อ XMR กับ {0}
|
||||||
shared.sellingXMRFor=การขาย XMR แก่ {0}
|
shared.sellingXMRFor=การขาย XMR แก่ {0}
|
||||||
shared.buyingCurrency=การซื้อ {0} (การขาย XMR)
|
shared.buyingCurrency=การซื้อ {0} (การขาย XMR)
|
||||||
@ -330,6 +332,7 @@ offerbook.createOffer=สร้างข้อเสนอ
|
|||||||
offerbook.takeOffer=รับข้อเสนอ
|
offerbook.takeOffer=รับข้อเสนอ
|
||||||
offerbook.takeOfferToBuy=Take offer to buy {0}
|
offerbook.takeOfferToBuy=Take offer to buy {0}
|
||||||
offerbook.takeOfferToSell=Take offer to sell {0}
|
offerbook.takeOfferToSell=Take offer to sell {0}
|
||||||
|
offerbook.takeOffer.enterChallenge=กรอกพาสเฟรสข้อเสนอ
|
||||||
offerbook.trader=Trader (เทรดเดอร์)
|
offerbook.trader=Trader (เทรดเดอร์)
|
||||||
offerbook.offerersBankId=รหัสธนาคารของผู้สร้าง (BIC / SWIFT): {0}
|
offerbook.offerersBankId=รหัสธนาคารของผู้สร้าง (BIC / SWIFT): {0}
|
||||||
offerbook.offerersBankName=ชื่อธนาคารของผู้สร้าง: {0}
|
offerbook.offerersBankName=ชื่อธนาคารของผู้สร้าง: {0}
|
||||||
@ -339,7 +342,9 @@ offerbook.offerersAcceptedBankSeats=ยอมรับตำแหน่งป
|
|||||||
offerbook.availableOffers=ข้อเสนอที่พร้อมใช้งาน
|
offerbook.availableOffers=ข้อเสนอที่พร้อมใช้งาน
|
||||||
offerbook.filterByCurrency=กรองตามสกุลเงิน
|
offerbook.filterByCurrency=กรองตามสกุลเงิน
|
||||||
offerbook.filterByPaymentMethod=ตัวกรองตามวิธีการชำระเงิน
|
offerbook.filterByPaymentMethod=ตัวกรองตามวิธีการชำระเงิน
|
||||||
offerbook.matchingOffers=Offers matching my accounts
|
offerbook.matchingOffers=ข้อเสนอที่ตรงกับบัญชีของฉัน
|
||||||
|
offerbook.filterNoDeposit=ไม่มีเงินมัดจำ
|
||||||
|
offerbook.noDepositOffers=ข้อเสนอที่ไม่มีเงินมัดจำ (ต้องการรหัสผ่าน)
|
||||||
offerbook.timeSinceSigning=Account info
|
offerbook.timeSinceSigning=Account info
|
||||||
offerbook.timeSinceSigning.info=This account was verified and {0}
|
offerbook.timeSinceSigning.info=This account was verified and {0}
|
||||||
offerbook.timeSinceSigning.info.arbitrator=signed by an arbitrator and can sign peer accounts
|
offerbook.timeSinceSigning.info.arbitrator=signed by an arbitrator and can sign peer accounts
|
||||||
@ -484,7 +489,10 @@ createOffer.setDepositAsBuyer=Set my security deposit as buyer (%)
|
|||||||
createOffer.setDepositForBothTraders=Set both traders' security deposit (%)
|
createOffer.setDepositForBothTraders=Set both traders' security deposit (%)
|
||||||
createOffer.securityDepositInfo=Your buyer''s security deposit will be {0}
|
createOffer.securityDepositInfo=Your buyer''s security deposit will be {0}
|
||||||
createOffer.securityDepositInfoAsBuyer=Your security deposit as buyer will be {0}
|
createOffer.securityDepositInfoAsBuyer=Your security deposit as buyer will be {0}
|
||||||
createOffer.minSecurityDepositUsed=Min. buyer security deposit is used
|
createOffer.minSecurityDepositUsed=เงินประกันความปลอดภัยขั้นต่ำถูกใช้
|
||||||
|
createOffer.buyerAsTakerWithoutDeposit=ไม่ต้องวางมัดจำจากผู้ซื้อ (ป้องกันด้วยรหัสผ่าน)
|
||||||
|
createOffer.myDeposit=เงินประกันความปลอดภัยของฉัน (%)
|
||||||
|
createOffer.myDepositInfo=เงินประกันความปลอดภัยของคุณจะเป็น {0}
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -508,6 +516,8 @@ takeOffer.fundsBox.networkFee=ยอดรวมค่าธรรมเนี
|
|||||||
takeOffer.fundsBox.takeOfferSpinnerInfo=ยอมรับข้อเสนอ: {0}
|
takeOffer.fundsBox.takeOfferSpinnerInfo=ยอมรับข้อเสนอ: {0}
|
||||||
takeOffer.fundsBox.paymentLabel=การซื้อขาย Haveno ด้วย ID {0}
|
takeOffer.fundsBox.paymentLabel=การซื้อขาย Haveno ด้วย ID {0}
|
||||||
takeOffer.fundsBox.fundsStructure=({0} เงินประกัน {1} ค่าธรรมเนียมการซื้อขาย {2} ค่าธรรมเนียมการขุด)
|
takeOffer.fundsBox.fundsStructure=({0} เงินประกัน {1} ค่าธรรมเนียมการซื้อขาย {2} ค่าธรรมเนียมการขุด)
|
||||||
|
takeOffer.fundsBox.noFundingRequiredTitle=ไม่ต้องใช้เงินทุน
|
||||||
|
takeOffer.fundsBox.noFundingRequiredDescription=รับรหัสผ่านข้อเสนอจากผู้ขายภายนอก Haveno เพื่อรับข้อเสนอนี้
|
||||||
takeOffer.success.headline=คุณได้รับข้อเสนอเป็นที่เรีบยร้อยแล้ว
|
takeOffer.success.headline=คุณได้รับข้อเสนอเป็นที่เรีบยร้อยแล้ว
|
||||||
takeOffer.success.info=คุณสามารถดูสถานะการค้าของคุณได้ที่ \ "Portfolio (แฟ้มผลงาน) / เปิดการซื้อขาย \"
|
takeOffer.success.info=คุณสามารถดูสถานะการค้าของคุณได้ที่ \ "Portfolio (แฟ้มผลงาน) / เปิดการซื้อขาย \"
|
||||||
takeOffer.error.message=เกิดข้อผิดพลาดขณะรับข้อเสนอ\n\n{0}
|
takeOffer.error.message=เกิดข้อผิดพลาดขณะรับข้อเสนอ\n\n{0}
|
||||||
@ -1461,6 +1471,7 @@ offerDetailsWindow.confirm.maker=ยืนยัน: ยื่นข้อเส
|
|||||||
offerDetailsWindow.confirm.taker=ยืนยัน: รับข้อเสนอไปยัง {0} บิทคอยน์
|
offerDetailsWindow.confirm.taker=ยืนยัน: รับข้อเสนอไปยัง {0} บิทคอยน์
|
||||||
offerDetailsWindow.creationDate=วันที่สร้าง
|
offerDetailsWindow.creationDate=วันที่สร้าง
|
||||||
offerDetailsWindow.makersOnion=ที่อยู่ onion ของผู้สร้าง
|
offerDetailsWindow.makersOnion=ที่อยู่ onion ของผู้สร้าง
|
||||||
|
offerDetailsWindow.challenge=รหัสผ่านสำหรับข้อเสนอ
|
||||||
|
|
||||||
qRCodeWindow.headline=QR Code
|
qRCodeWindow.headline=QR Code
|
||||||
qRCodeWindow.msg=Please use this QR code for funding your Haveno wallet from your external wallet.
|
qRCodeWindow.msg=Please use this QR code for funding your Haveno wallet from your external wallet.
|
||||||
@ -1684,6 +1695,9 @@ popup.accountSigning.unsignedPubKeys.signed=Pubkeys were signed
|
|||||||
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
||||||
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign
|
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign
|
||||||
|
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit.headline=ไม่ต้องมีเงินมัดจำจากผู้ซื้อ
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit=ข้อเสนอของคุณจะไม่ต้องการเงินมัดจำหรือค่าธรรมเนียมจากผู้ซื้อ XMR\n\nในการยอมรับข้อเสนอของคุณ คุณต้องแบ่งปันรหัสผ่านกับคู่ค้าการค้าของคุณภายนอก Haveno\n\nรหัสผ่านจะถูกสร้างโดยอัตโนมัติและแสดงในรายละเอียดข้อเสนอหลังจากการสร้าง
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
# Notifications
|
# Notifications
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -1809,6 +1823,7 @@ navigation.support=\"ช่วยเหลือและสนับสนุ
|
|||||||
|
|
||||||
formatter.formatVolumeLabel={0} จำนวนยอด{1}
|
formatter.formatVolumeLabel={0} จำนวนยอด{1}
|
||||||
formatter.makerTaker=ผู้สร้าง เป็น {0} {1} / ผู้รับเป็น {2} {3}
|
formatter.makerTaker=ผู้สร้าง เป็น {0} {1} / ผู้รับเป็น {2} {3}
|
||||||
|
formatter.makerTakerLocked=ผู้สร้าง เป็น {0} {1} / ผู้รับเป็น {2} {3} 🔒
|
||||||
formatter.youAreAsMaker=You are: {1} {0} (maker) / Taker is: {3} {2}
|
formatter.youAreAsMaker=You are: {1} {0} (maker) / Taker is: {3} {2}
|
||||||
formatter.youAreAsTaker=You are: {1} {0} (taker) / Maker is: {3} {2}
|
formatter.youAreAsTaker=You are: {1} {0} (taker) / Maker is: {3} {2}
|
||||||
formatter.youAre=คุณคือ {0} {1} ({2} {3})
|
formatter.youAre=คุณคือ {0} {1} ({2} {3})
|
||||||
|
@ -43,6 +43,8 @@ shared.buyMonero=Monero Satın Al
|
|||||||
shared.sellMonero=Monero Sat
|
shared.sellMonero=Monero Sat
|
||||||
shared.buyCurrency={0} satın al
|
shared.buyCurrency={0} satın al
|
||||||
shared.sellCurrency={0} sat
|
shared.sellCurrency={0} sat
|
||||||
|
shared.buyCurrencyLocked={0} satın al 🔒
|
||||||
|
shared.sellCurrencyLocked={0} sat 🔒
|
||||||
shared.buyingXMRWith={0} ile XMR satın alınıyor
|
shared.buyingXMRWith={0} ile XMR satın alınıyor
|
||||||
shared.sellingXMRFor={0} karşılığında XMR satılıyor
|
shared.sellingXMRFor={0} karşılığında XMR satılıyor
|
||||||
shared.buyingCurrency={0} satın alınıyor (XMR satılıyor)
|
shared.buyingCurrency={0} satın alınıyor (XMR satılıyor)
|
||||||
@ -346,6 +348,7 @@ market.trades.showVolumeInUSD=Hacmi USD olarak göster
|
|||||||
offerbook.createOffer=Teklif oluştur
|
offerbook.createOffer=Teklif oluştur
|
||||||
offerbook.takeOffer=Teklif al
|
offerbook.takeOffer=Teklif al
|
||||||
offerbook.takeOffer.createAccount=Hesap oluştur ve teklifi al
|
offerbook.takeOffer.createAccount=Hesap oluştur ve teklifi al
|
||||||
|
offerbook.takeOffer.enterChallenge=Teklif şifresini girin
|
||||||
offerbook.trader=Yatırımcı
|
offerbook.trader=Yatırımcı
|
||||||
offerbook.offerersBankId=Yapıcının banka kimliği (BIC/SWIFT): {0}
|
offerbook.offerersBankId=Yapıcının banka kimliği (BIC/SWIFT): {0}
|
||||||
offerbook.offerersBankName=Yapıcının banka adı: {0}
|
offerbook.offerersBankName=Yapıcının banka adı: {0}
|
||||||
@ -357,6 +360,8 @@ offerbook.availableOffersToSell={0} için {1} sat
|
|||||||
offerbook.filterByCurrency=Para birimini seç
|
offerbook.filterByCurrency=Para birimini seç
|
||||||
offerbook.filterByPaymentMethod=Ödeme yöntemini seç
|
offerbook.filterByPaymentMethod=Ödeme yöntemini seç
|
||||||
offerbook.matchingOffers=Uygun Teklif
|
offerbook.matchingOffers=Uygun Teklif
|
||||||
|
offerbook.filterNoDeposit=Depozito yok
|
||||||
|
offerbook.noDepositOffers=Depozitosuz teklifler (şifre gereklidir)
|
||||||
offerbook.timeSinceSigning=Hesap bilgisi
|
offerbook.timeSinceSigning=Hesap bilgisi
|
||||||
offerbook.timeSinceSigning.info.arbitrator=bir hakem tarafından imzalandı ve eş hesaplarını imzalayabilir
|
offerbook.timeSinceSigning.info.arbitrator=bir hakem tarafından imzalandı ve eş hesaplarını imzalayabilir
|
||||||
offerbook.timeSinceSigning.info.peer=bir eş tarafından imzalandı, limitlerin kaldırılması için %d gün bekleniyor
|
offerbook.timeSinceSigning.info.peer=bir eş tarafından imzalandı, limitlerin kaldırılması için %d gün bekleniyor
|
||||||
@ -524,7 +529,10 @@ createOffer.setDepositAsBuyer=Alıcı olarak benim güvenlik teminatımı ayarla
|
|||||||
createOffer.setDepositForBothTraders=Tüccarların güvenlik teminatı (%)
|
createOffer.setDepositForBothTraders=Tüccarların güvenlik teminatı (%)
|
||||||
createOffer.securityDepositInfo=Alıcının güvenlik teminatı {0} olacak
|
createOffer.securityDepositInfo=Alıcının güvenlik teminatı {0} olacak
|
||||||
createOffer.securityDepositInfoAsBuyer=Alıcı olarak güvenlik teminatınız {0} olacak
|
createOffer.securityDepositInfoAsBuyer=Alıcı olarak güvenlik teminatınız {0} olacak
|
||||||
createOffer.minSecurityDepositUsed=Minimum alıcı güvenlik teminatı kullanıldı
|
createOffer.minSecurityDepositUsed=Minimum güvenlik depozitosu kullanılır
|
||||||
|
createOffer.buyerAsTakerWithoutDeposit=Alıcıdan depozito gerekmez (şifre korumalı)
|
||||||
|
createOffer.myDeposit=Güvenlik depozitam (%)
|
||||||
|
createOffer.myDepositInfo=Güvenlik depozitonuz {0} olacaktır.
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -550,6 +558,8 @@ takeOffer.fundsBox.networkFee=Toplam madencilik ücretleri
|
|||||||
takeOffer.fundsBox.takeOfferSpinnerInfo=Teklif alınıyor: {0}
|
takeOffer.fundsBox.takeOfferSpinnerInfo=Teklif alınıyor: {0}
|
||||||
takeOffer.fundsBox.paymentLabel=ID {0} ile Haveno işlemi
|
takeOffer.fundsBox.paymentLabel=ID {0} ile Haveno işlemi
|
||||||
takeOffer.fundsBox.fundsStructure=({0} güvenlik teminatı, {1} işlem ücreti)
|
takeOffer.fundsBox.fundsStructure=({0} güvenlik teminatı, {1} işlem ücreti)
|
||||||
|
takeOffer.fundsBox.noFundingRequiredTitle=Fonlama gerekmez
|
||||||
|
takeOffer.fundsBox.noFundingRequiredDescription=Bu teklifi almak için satıcıdan passphrase'i Haveno dışında alınız.
|
||||||
takeOffer.success.headline=Teklifi başarıyla aldınız.
|
takeOffer.success.headline=Teklifi başarıyla aldınız.
|
||||||
takeOffer.success.info=İşleminizin durumunu \"Portföy/Açık işlemler\" kısmında görebilirsiniz.
|
takeOffer.success.info=İşleminizin durumunu \"Portföy/Açık işlemler\" kısmında görebilirsiniz.
|
||||||
takeOffer.error.message=Teklif alımı sırasında bir hata oluştu.\n\n{0}
|
takeOffer.error.message=Teklif alımı sırasında bir hata oluştu.\n\n{0}
|
||||||
@ -1963,6 +1973,7 @@ offerDetailsWindow.confirm.taker=Onayla: {0} monero teklifi al
|
|||||||
offerDetailsWindow.confirm.takerCrypto=Onayla: {0} {1} teklifi al
|
offerDetailsWindow.confirm.takerCrypto=Onayla: {0} {1} teklifi al
|
||||||
offerDetailsWindow.creationDate=Oluşturma tarihi
|
offerDetailsWindow.creationDate=Oluşturma tarihi
|
||||||
offerDetailsWindow.makersOnion=Yapıcı'nın onion adresi
|
offerDetailsWindow.makersOnion=Yapıcı'nın onion adresi
|
||||||
|
offerDetailsWindow.challenge=Teklif şifresi
|
||||||
|
|
||||||
qRCodeWindow.headline=QR Kodu
|
qRCodeWindow.headline=QR Kodu
|
||||||
qRCodeWindow.msg=Harici cüzdanınızdan Haveno cüzdanınızı finanse etmek için bu QR kodunu kullanın.
|
qRCodeWindow.msg=Harici cüzdanınızdan Haveno cüzdanınızı finanse etmek için bu QR kodunu kullanın.
|
||||||
@ -2260,6 +2271,9 @@ popup.accountSigning.unsignedPubKeys.signed=Pubkey'ler imzalandı
|
|||||||
popup.accountSigning.unsignedPubKeys.result.signed=İmzalanmış pubkey'ler
|
popup.accountSigning.unsignedPubKeys.result.signed=İmzalanmış pubkey'ler
|
||||||
popup.accountSigning.unsignedPubKeys.result.failed=İmzalama başarısız oldu
|
popup.accountSigning.unsignedPubKeys.result.failed=İmzalama başarısız oldu
|
||||||
|
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit.headline=Alıcıdan depozito gerekmez
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit=Teklifiniz, XMR alıcısından güvenlik depozitosu veya ücret talep etmeyecektir.\n\nTeklifinizi kabul etmek için, ticaret ortağınızla Haveno dışında bir şifre paylaşmalısınız.\n\nŞifre otomatik olarak oluşturulur ve oluşturulduktan sonra teklif detaylarında görüntülenir.
|
||||||
|
|
||||||
popup.info.torMigration.msg=Haveno düğümünüz muhtemelen eski bir Tor v2 adresi kullanıyor. \
|
popup.info.torMigration.msg=Haveno düğümünüz muhtemelen eski bir Tor v2 adresi kullanıyor. \
|
||||||
Lütfen Haveno düğümünüzü bir Tor v3 adresine geçirin. \
|
Lütfen Haveno düğümünüzü bir Tor v3 adresine geçirin. \
|
||||||
Önceden veri dizininizi yedeklediğinizden emin olun.
|
Önceden veri dizininizi yedeklediğinizden emin olun.
|
||||||
@ -2399,6 +2413,7 @@ navigation.support="Destek"
|
|||||||
|
|
||||||
formatter.formatVolumeLabel={0} miktar{1}
|
formatter.formatVolumeLabel={0} miktar{1}
|
||||||
formatter.makerTaker=Yapan olarak {0} {1} / Alan olarak {2} {3}
|
formatter.makerTaker=Yapan olarak {0} {1} / Alan olarak {2} {3}
|
||||||
|
formatter.makerTakerLocked=Yapıcı olarak {0} {1} / Alan olarak {2} {3} 🔒
|
||||||
formatter.youAreAsMaker=Yapan sizsiniz: {1} {0} (maker) / Alan: {3} {2}
|
formatter.youAreAsMaker=Yapan sizsiniz: {1} {0} (maker) / Alan: {3} {2}
|
||||||
formatter.youAreAsTaker=Alan sizsiniz: {1} {0} (taker) / Yapan: {3} {2}
|
formatter.youAreAsTaker=Alan sizsiniz: {1} {0} (taker) / Yapan: {3} {2}
|
||||||
formatter.youAre=Şu anda sizsiniz {0} {1} ({2} {3})
|
formatter.youAre=Şu anda sizsiniz {0} {1} ({2} {3})
|
||||||
|
@ -40,6 +40,8 @@ shared.buyMonero=Mua monero
|
|||||||
shared.sellMonero=Bán monero
|
shared.sellMonero=Bán monero
|
||||||
shared.buyCurrency=Mua {0}
|
shared.buyCurrency=Mua {0}
|
||||||
shared.sellCurrency=Bán {0}
|
shared.sellCurrency=Bán {0}
|
||||||
|
shared.buyCurrencyLocked=Mua {0} 🔒
|
||||||
|
shared.sellCurrencyLocked=Bán {0} 🔒
|
||||||
shared.buyingXMRWith=đang mua XMR với {0}
|
shared.buyingXMRWith=đang mua XMR với {0}
|
||||||
shared.sellingXMRFor=đang bán XMR với {0}
|
shared.sellingXMRFor=đang bán XMR với {0}
|
||||||
shared.buyingCurrency=đang mua {0} (đang bán XMR)
|
shared.buyingCurrency=đang mua {0} (đang bán XMR)
|
||||||
@ -330,6 +332,7 @@ offerbook.createOffer=Tạo chào giá
|
|||||||
offerbook.takeOffer=Nhận chào giá
|
offerbook.takeOffer=Nhận chào giá
|
||||||
offerbook.takeOfferToBuy=Nhận chào giá mua {0}
|
offerbook.takeOfferToBuy=Nhận chào giá mua {0}
|
||||||
offerbook.takeOfferToSell=Nhận chào giá bán {0}
|
offerbook.takeOfferToSell=Nhận chào giá bán {0}
|
||||||
|
offerbook.takeOffer.enterChallenge=Nhập mật khẩu đề nghị
|
||||||
offerbook.trader=Trader
|
offerbook.trader=Trader
|
||||||
offerbook.offerersBankId=ID ngân hàng của người tạo (BIC/SWIFT): {0}
|
offerbook.offerersBankId=ID ngân hàng của người tạo (BIC/SWIFT): {0}
|
||||||
offerbook.offerersBankName=Tên ngân hàng của người tạo: {0}
|
offerbook.offerersBankName=Tên ngân hàng của người tạo: {0}
|
||||||
@ -339,7 +342,9 @@ offerbook.offerersAcceptedBankSeats=Các quốc gia có ngân hàng được ch
|
|||||||
offerbook.availableOffers=Các chào giá hiện có
|
offerbook.availableOffers=Các chào giá hiện có
|
||||||
offerbook.filterByCurrency=Lọc theo tiền tệ
|
offerbook.filterByCurrency=Lọc theo tiền tệ
|
||||||
offerbook.filterByPaymentMethod=Lọc theo phương thức thanh toán
|
offerbook.filterByPaymentMethod=Lọc theo phương thức thanh toán
|
||||||
offerbook.matchingOffers=Offers matching my accounts
|
offerbook.matchingOffers=Các ưu đãi phù hợp với tài khoản của tôi
|
||||||
|
offerbook.filterNoDeposit=Không đặt cọc
|
||||||
|
offerbook.noDepositOffers=Các ưu đãi không yêu cầu đặt cọc (cần mật khẩu)
|
||||||
offerbook.timeSinceSigning=Account info
|
offerbook.timeSinceSigning=Account info
|
||||||
offerbook.timeSinceSigning.info=This account was verified and {0}
|
offerbook.timeSinceSigning.info=This account was verified and {0}
|
||||||
offerbook.timeSinceSigning.info.arbitrator=signed by an arbitrator and can sign peer accounts
|
offerbook.timeSinceSigning.info.arbitrator=signed by an arbitrator and can sign peer accounts
|
||||||
@ -484,7 +489,10 @@ createOffer.setDepositAsBuyer=Cài đặt tiền đặt cọc của tôi với v
|
|||||||
createOffer.setDepositForBothTraders=Set both traders' security deposit (%)
|
createOffer.setDepositForBothTraders=Set both traders' security deposit (%)
|
||||||
createOffer.securityDepositInfo=Số tiền đặt cọc cho người mua của bạn sẽ là {0}
|
createOffer.securityDepositInfo=Số tiền đặt cọc cho người mua của bạn sẽ là {0}
|
||||||
createOffer.securityDepositInfoAsBuyer=Số tiền đặt cọc của bạn với vai trò người mua sẽ là {0}
|
createOffer.securityDepositInfoAsBuyer=Số tiền đặt cọc của bạn với vai trò người mua sẽ là {0}
|
||||||
createOffer.minSecurityDepositUsed=Min. buyer security deposit is used
|
createOffer.minSecurityDepositUsed=Khoản tiền đặt cọc bảo mật tối thiểu được sử dụng
|
||||||
|
createOffer.buyerAsTakerWithoutDeposit=Không cần đặt cọc từ người mua (được bảo vệ bằng mật khẩu)
|
||||||
|
createOffer.myDeposit=Tiền đặt cọc bảo mật của tôi (%)
|
||||||
|
createOffer.myDepositInfo=Khoản tiền đặt cọc của bạn sẽ là {0}
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -508,6 +516,8 @@ takeOffer.fundsBox.networkFee=Tổng phí đào
|
|||||||
takeOffer.fundsBox.takeOfferSpinnerInfo=Chấp nhận đề xuất: {0}
|
takeOffer.fundsBox.takeOfferSpinnerInfo=Chấp nhận đề xuất: {0}
|
||||||
takeOffer.fundsBox.paymentLabel=giao dịch Haveno có ID {0}
|
takeOffer.fundsBox.paymentLabel=giao dịch Haveno có ID {0}
|
||||||
takeOffer.fundsBox.fundsStructure=({0} tiền gửi đại lý, {1} phí giao dịch, {2} phí đào)
|
takeOffer.fundsBox.fundsStructure=({0} tiền gửi đại lý, {1} phí giao dịch, {2} phí đào)
|
||||||
|
takeOffer.fundsBox.noFundingRequiredTitle=Không cần tài trợ
|
||||||
|
takeOffer.fundsBox.noFundingRequiredDescription=Lấy mật khẩu giao dịch từ người bán ngoài Haveno để nhận đề nghị này.
|
||||||
takeOffer.success.headline=Bạn đã nhận báo giá thành công.
|
takeOffer.success.headline=Bạn đã nhận báo giá thành công.
|
||||||
takeOffer.success.info=Bạn có thể xem trạng thái giao dịch của bạn tại \"Portfolio/Các giao dịch mở\".
|
takeOffer.success.info=Bạn có thể xem trạng thái giao dịch của bạn tại \"Portfolio/Các giao dịch mở\".
|
||||||
takeOffer.error.message=Có lỗi xảy ra khi nhận báo giá.\n\n{0}
|
takeOffer.error.message=Có lỗi xảy ra khi nhận báo giá.\n\n{0}
|
||||||
@ -1463,6 +1473,7 @@ offerDetailsWindow.confirm.maker=Xác nhận: Đặt chào giá cho {0} monero
|
|||||||
offerDetailsWindow.confirm.taker=Xác nhận: Nhận chào giáo cho {0} monero
|
offerDetailsWindow.confirm.taker=Xác nhận: Nhận chào giáo cho {0} monero
|
||||||
offerDetailsWindow.creationDate=Ngày tạo
|
offerDetailsWindow.creationDate=Ngày tạo
|
||||||
offerDetailsWindow.makersOnion=Địa chỉ onion của người tạo
|
offerDetailsWindow.makersOnion=Địa chỉ onion của người tạo
|
||||||
|
offerDetailsWindow.challenge=Mã bảo vệ giao dịch
|
||||||
|
|
||||||
qRCodeWindow.headline=QR Code
|
qRCodeWindow.headline=QR Code
|
||||||
qRCodeWindow.msg=Please use this QR code for funding your Haveno wallet from your external wallet.
|
qRCodeWindow.msg=Please use this QR code for funding your Haveno wallet from your external wallet.
|
||||||
@ -1686,6 +1697,9 @@ popup.accountSigning.unsignedPubKeys.signed=Pubkeys were signed
|
|||||||
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
||||||
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign
|
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign
|
||||||
|
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit.headline=Không cần đặt cọc từ người mua
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit=Lời đề nghị của bạn sẽ không yêu cầu khoản đặt cọc bảo mật hoặc phí từ người mua XMR.\n\nĐể chấp nhận lời đề nghị của bạn, bạn phải chia sẻ một mật khẩu với đối tác giao dịch ngoài Haveno.\n\nMật khẩu được tạo tự động và hiển thị trong chi tiết lời đề nghị sau khi tạo.
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
# Notifications
|
# Notifications
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -1811,6 +1825,7 @@ navigation.support=\"Hỗ trợ\"
|
|||||||
|
|
||||||
formatter.formatVolumeLabel={0} giá trị {1}
|
formatter.formatVolumeLabel={0} giá trị {1}
|
||||||
formatter.makerTaker=Người tạo là {0} {1} / Người nhận là {2} {3}
|
formatter.makerTaker=Người tạo là {0} {1} / Người nhận là {2} {3}
|
||||||
|
formatter.makerTakerLocked=Người tạo là {0} {1} / Người nhận là {2} {3} 🔒
|
||||||
formatter.youAreAsMaker=You are: {1} {0} (maker) / Taker is: {3} {2}
|
formatter.youAreAsMaker=You are: {1} {0} (maker) / Taker is: {3} {2}
|
||||||
formatter.youAreAsTaker=You are: {1} {0} (taker) / Maker is: {3} {2}
|
formatter.youAreAsTaker=You are: {1} {0} (taker) / Maker is: {3} {2}
|
||||||
formatter.youAre=Bạn là {0} {1} ({2} {3})
|
formatter.youAre=Bạn là {0} {1} ({2} {3})
|
||||||
|
@ -40,6 +40,8 @@ shared.buyMonero=买入比特币
|
|||||||
shared.sellMonero=卖出比特币
|
shared.sellMonero=卖出比特币
|
||||||
shared.buyCurrency=买入 {0}
|
shared.buyCurrency=买入 {0}
|
||||||
shared.sellCurrency=卖出 {0}
|
shared.sellCurrency=卖出 {0}
|
||||||
|
shared.buyCurrencyLocked=买入 {0} 🔒
|
||||||
|
shared.sellCurrencyLocked=卖出 {0} 🔒
|
||||||
shared.buyingXMRWith=用 {0} 买入 XMR
|
shared.buyingXMRWith=用 {0} 买入 XMR
|
||||||
shared.sellingXMRFor=卖出 XMR 为 {0}
|
shared.sellingXMRFor=卖出 XMR 为 {0}
|
||||||
shared.buyingCurrency=买入 {0}(卖出 XMR)
|
shared.buyingCurrency=买入 {0}(卖出 XMR)
|
||||||
@ -330,6 +332,7 @@ offerbook.createOffer=创建报价
|
|||||||
offerbook.takeOffer=接受报价
|
offerbook.takeOffer=接受报价
|
||||||
offerbook.takeOfferToBuy=接受报价来收购 {0}
|
offerbook.takeOfferToBuy=接受报价来收购 {0}
|
||||||
offerbook.takeOfferToSell=接受报价来出售 {0}
|
offerbook.takeOfferToSell=接受报价来出售 {0}
|
||||||
|
offerbook.takeOffer.enterChallenge=输入报价密码
|
||||||
offerbook.trader=商人
|
offerbook.trader=商人
|
||||||
offerbook.offerersBankId=卖家的银行 ID(BIC/SWIFT):{0}
|
offerbook.offerersBankId=卖家的银行 ID(BIC/SWIFT):{0}
|
||||||
offerbook.offerersBankName=卖家的银行名称:{0}
|
offerbook.offerersBankName=卖家的银行名称:{0}
|
||||||
@ -339,7 +342,9 @@ offerbook.offerersAcceptedBankSeats=接受的银行所在国家(买家):\n
|
|||||||
offerbook.availableOffers=可用报价
|
offerbook.availableOffers=可用报价
|
||||||
offerbook.filterByCurrency=以货币筛选
|
offerbook.filterByCurrency=以货币筛选
|
||||||
offerbook.filterByPaymentMethod=以支付方式筛选
|
offerbook.filterByPaymentMethod=以支付方式筛选
|
||||||
offerbook.matchingOffers=Offers matching my accounts
|
offerbook.matchingOffers=匹配我的账户的报价
|
||||||
|
offerbook.filterNoDeposit=无押金
|
||||||
|
offerbook.noDepositOffers=无押金的报价(需要密码短语)
|
||||||
offerbook.timeSinceSigning=账户信息
|
offerbook.timeSinceSigning=账户信息
|
||||||
offerbook.timeSinceSigning.info=此账户已验证,{0}
|
offerbook.timeSinceSigning.info=此账户已验证,{0}
|
||||||
offerbook.timeSinceSigning.info.arbitrator=由仲裁员验证,并可以验证伙伴账户
|
offerbook.timeSinceSigning.info.arbitrator=由仲裁员验证,并可以验证伙伴账户
|
||||||
@ -485,7 +490,10 @@ createOffer.setDepositAsBuyer=设置自己作为买家的保证金(%)
|
|||||||
createOffer.setDepositForBothTraders=设置双方的保证金比例(%)
|
createOffer.setDepositForBothTraders=设置双方的保证金比例(%)
|
||||||
createOffer.securityDepositInfo=您的买家的保证金将会是 {0}
|
createOffer.securityDepositInfo=您的买家的保证金将会是 {0}
|
||||||
createOffer.securityDepositInfoAsBuyer=您作为买家的保证金将会是 {0}
|
createOffer.securityDepositInfoAsBuyer=您作为买家的保证金将会是 {0}
|
||||||
createOffer.minSecurityDepositUsed=已使用最低买家保证金
|
createOffer.minSecurityDepositUsed=最低安全押金已使用
|
||||||
|
createOffer.buyerAsTakerWithoutDeposit=无需买家支付押金(使用口令保护)
|
||||||
|
createOffer.myDeposit=我的安全押金 (%)
|
||||||
|
createOffer.myDepositInfo=您的保证金为 {0}
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -509,6 +517,8 @@ takeOffer.fundsBox.networkFee=总共挖矿手续费
|
|||||||
takeOffer.fundsBox.takeOfferSpinnerInfo=接受报价:{0}
|
takeOffer.fundsBox.takeOfferSpinnerInfo=接受报价:{0}
|
||||||
takeOffer.fundsBox.paymentLabel=Haveno 交易 ID {0}
|
takeOffer.fundsBox.paymentLabel=Haveno 交易 ID {0}
|
||||||
takeOffer.fundsBox.fundsStructure=({0} 保证金,{1} 交易费,{2} 采矿费)
|
takeOffer.fundsBox.fundsStructure=({0} 保证金,{1} 交易费,{2} 采矿费)
|
||||||
|
takeOffer.fundsBox.noFundingRequiredTitle=无需资金
|
||||||
|
takeOffer.fundsBox.noFundingRequiredDescription=从卖方处获取交易密码(在Haveno之外)以接受此报价。
|
||||||
takeOffer.success.headline=你已成功下单一个报价。
|
takeOffer.success.headline=你已成功下单一个报价。
|
||||||
takeOffer.success.info=你可以在“业务/未完成交易”页面内查看您的未完成交易。
|
takeOffer.success.info=你可以在“业务/未完成交易”页面内查看您的未完成交易。
|
||||||
takeOffer.error.message=下单时发生了一个错误。\n\n{0}
|
takeOffer.error.message=下单时发生了一个错误。\n\n{0}
|
||||||
@ -1465,6 +1475,7 @@ offerDetailsWindow.confirm.maker=确定:发布报价 {0} 比特币
|
|||||||
offerDetailsWindow.confirm.taker=确定:下单买入 {0} 比特币
|
offerDetailsWindow.confirm.taker=确定:下单买入 {0} 比特币
|
||||||
offerDetailsWindow.creationDate=创建时间
|
offerDetailsWindow.creationDate=创建时间
|
||||||
offerDetailsWindow.makersOnion=卖家的匿名地址
|
offerDetailsWindow.makersOnion=卖家的匿名地址
|
||||||
|
offerDetailsWindow.challenge=提供密码
|
||||||
|
|
||||||
qRCodeWindow.headline=二维码
|
qRCodeWindow.headline=二维码
|
||||||
qRCodeWindow.msg=请使用二维码从外部钱包充值至 Haveno 钱包
|
qRCodeWindow.msg=请使用二维码从外部钱包充值至 Haveno 钱包
|
||||||
@ -1693,6 +1704,9 @@ popup.accountSigning.unsignedPubKeys.signed=公钥已被验证
|
|||||||
popup.accountSigning.unsignedPubKeys.result.signed=已验证公钥
|
popup.accountSigning.unsignedPubKeys.result.signed=已验证公钥
|
||||||
popup.accountSigning.unsignedPubKeys.result.failed=未能验证公钥
|
popup.accountSigning.unsignedPubKeys.result.failed=未能验证公钥
|
||||||
|
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit.headline=买家无需支付保证金
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit=您的报价将不需要来自XMR买家的保证金或费用。\n\n要接受您的报价,您必须与交易伙伴在Haveno外共享一个密码短语。\n\n密码短语会自动生成,并在创建后显示在报价详情中。
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
# Notifications
|
# Notifications
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -1818,6 +1832,7 @@ navigation.support=“帮助”
|
|||||||
|
|
||||||
formatter.formatVolumeLabel={0} 数量 {1}
|
formatter.formatVolumeLabel={0} 数量 {1}
|
||||||
formatter.makerTaker=卖家 {0} {1} / 买家 {2} {3}
|
formatter.makerTaker=卖家 {0} {1} / 买家 {2} {3}
|
||||||
|
formatter.makerTakerLocked=卖家 {0} {1} / 买家 {2} {3} 🔒
|
||||||
formatter.youAreAsMaker=您是 {1} {0} 卖家 / 买家是 {3} {2}
|
formatter.youAreAsMaker=您是 {1} {0} 卖家 / 买家是 {3} {2}
|
||||||
formatter.youAreAsTaker=您是 {1} {0} 买家 / 卖家是 {3} {2}
|
formatter.youAreAsTaker=您是 {1} {0} 买家 / 卖家是 {3} {2}
|
||||||
formatter.youAre=您是 {0} {1} ({2} {3})
|
formatter.youAre=您是 {0} {1} ({2} {3})
|
||||||
|
@ -40,6 +40,8 @@ shared.buyMonero=買入比特幣
|
|||||||
shared.sellMonero=賣出比特幣
|
shared.sellMonero=賣出比特幣
|
||||||
shared.buyCurrency=買入 {0}
|
shared.buyCurrency=買入 {0}
|
||||||
shared.sellCurrency=賣出 {0}
|
shared.sellCurrency=賣出 {0}
|
||||||
|
shared.buyCurrencyLocked=買入 {0} 🔒
|
||||||
|
shared.sellCurrencyLocked=賣出 {0} 🔒
|
||||||
shared.buyingXMRWith=用 {0} 買入 XMR
|
shared.buyingXMRWith=用 {0} 買入 XMR
|
||||||
shared.sellingXMRFor=賣出 XMR 為 {0}
|
shared.sellingXMRFor=賣出 XMR 為 {0}
|
||||||
shared.buyingCurrency=買入 {0}(賣出 XMR)
|
shared.buyingCurrency=買入 {0}(賣出 XMR)
|
||||||
@ -330,6 +332,7 @@ offerbook.createOffer=創建報價
|
|||||||
offerbook.takeOffer=接受報價
|
offerbook.takeOffer=接受報價
|
||||||
offerbook.takeOfferToBuy=接受報價來收購 {0}
|
offerbook.takeOfferToBuy=接受報價來收購 {0}
|
||||||
offerbook.takeOfferToSell=接受報價來出售 {0}
|
offerbook.takeOfferToSell=接受報價來出售 {0}
|
||||||
|
offerbook.takeOffer.enterChallenge=輸入報價密碼
|
||||||
offerbook.trader=商人
|
offerbook.trader=商人
|
||||||
offerbook.offerersBankId=賣家的銀行 ID(BIC/SWIFT):{0}
|
offerbook.offerersBankId=賣家的銀行 ID(BIC/SWIFT):{0}
|
||||||
offerbook.offerersBankName=賣家的銀行名稱:{0}
|
offerbook.offerersBankName=賣家的銀行名稱:{0}
|
||||||
@ -339,7 +342,9 @@ offerbook.offerersAcceptedBankSeats=接受的銀行所在國家(買家):\n
|
|||||||
offerbook.availableOffers=可用報價
|
offerbook.availableOffers=可用報價
|
||||||
offerbook.filterByCurrency=以貨幣篩選
|
offerbook.filterByCurrency=以貨幣篩選
|
||||||
offerbook.filterByPaymentMethod=以支付方式篩選
|
offerbook.filterByPaymentMethod=以支付方式篩選
|
||||||
offerbook.matchingOffers=Offers matching my accounts
|
offerbook.matchingOffers=符合我的帳戶的報價
|
||||||
|
offerbook.filterNoDeposit=無押金
|
||||||
|
offerbook.noDepositOffers=無押金的報價(需要密碼短語)
|
||||||
offerbook.timeSinceSigning=賬户信息
|
offerbook.timeSinceSigning=賬户信息
|
||||||
offerbook.timeSinceSigning.info=此賬户已驗證,{0}
|
offerbook.timeSinceSigning.info=此賬户已驗證,{0}
|
||||||
offerbook.timeSinceSigning.info.arbitrator=由仲裁員驗證,並可以驗證夥伴賬户
|
offerbook.timeSinceSigning.info.arbitrator=由仲裁員驗證,並可以驗證夥伴賬户
|
||||||
@ -485,7 +490,10 @@ createOffer.setDepositAsBuyer=設置自己作為買家的保證金(%)
|
|||||||
createOffer.setDepositForBothTraders=設置雙方的保證金比例(%)
|
createOffer.setDepositForBothTraders=設置雙方的保證金比例(%)
|
||||||
createOffer.securityDepositInfo=您的買家的保證金將會是 {0}
|
createOffer.securityDepositInfo=您的買家的保證金將會是 {0}
|
||||||
createOffer.securityDepositInfoAsBuyer=您作為買家的保證金將會是 {0}
|
createOffer.securityDepositInfoAsBuyer=您作為買家的保證金將會是 {0}
|
||||||
createOffer.minSecurityDepositUsed=已使用最低買家保證金
|
createOffer.minSecurityDepositUsed=最低保證金已使用
|
||||||
|
createOffer.buyerAsTakerWithoutDeposit=買家無需支付保證金(通行密碼保護)
|
||||||
|
createOffer.myDeposit=我的保證金(%)
|
||||||
|
createOffer.myDepositInfo=您的保證金將為 {0}
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -509,6 +517,8 @@ takeOffer.fundsBox.networkFee=總共挖礦手續費
|
|||||||
takeOffer.fundsBox.takeOfferSpinnerInfo=接受報價:{0}
|
takeOffer.fundsBox.takeOfferSpinnerInfo=接受報價:{0}
|
||||||
takeOffer.fundsBox.paymentLabel=Haveno 交易 ID {0}
|
takeOffer.fundsBox.paymentLabel=Haveno 交易 ID {0}
|
||||||
takeOffer.fundsBox.fundsStructure=({0} 保證金,{1} 交易費,{2} 採礦費)
|
takeOffer.fundsBox.fundsStructure=({0} 保證金,{1} 交易費,{2} 採礦費)
|
||||||
|
takeOffer.fundsBox.noFundingRequiredTitle=無需資金
|
||||||
|
takeOffer.fundsBox.noFundingRequiredDescription=從賣家那裡在 Haveno 之外獲取優惠密碼以接受此優惠。
|
||||||
takeOffer.success.headline=你已成功下單一個報價。
|
takeOffer.success.headline=你已成功下單一個報價。
|
||||||
takeOffer.success.info=你可以在“業務/未完成交易”頁面內查看您的未完成交易。
|
takeOffer.success.info=你可以在“業務/未完成交易”頁面內查看您的未完成交易。
|
||||||
takeOffer.error.message=下單時發生了一個錯誤。\n\n{0}
|
takeOffer.error.message=下單時發生了一個錯誤。\n\n{0}
|
||||||
@ -1465,6 +1475,7 @@ offerDetailsWindow.confirm.maker=確定:發佈報價 {0} 比特幣
|
|||||||
offerDetailsWindow.confirm.taker=確定:下單買入 {0} 比特幣
|
offerDetailsWindow.confirm.taker=確定:下單買入 {0} 比特幣
|
||||||
offerDetailsWindow.creationDate=創建時間
|
offerDetailsWindow.creationDate=創建時間
|
||||||
offerDetailsWindow.makersOnion=賣家的匿名地址
|
offerDetailsWindow.makersOnion=賣家的匿名地址
|
||||||
|
offerDetailsWindow.challenge=提供密碼
|
||||||
|
|
||||||
qRCodeWindow.headline=二維碼
|
qRCodeWindow.headline=二維碼
|
||||||
qRCodeWindow.msg=請使用二維碼從外部錢包充值至 Haveno 錢包
|
qRCodeWindow.msg=請使用二維碼從外部錢包充值至 Haveno 錢包
|
||||||
@ -1687,6 +1698,9 @@ popup.accountSigning.unsignedPubKeys.signed=公鑰已被驗證
|
|||||||
popup.accountSigning.unsignedPubKeys.result.signed=已驗證公鑰
|
popup.accountSigning.unsignedPubKeys.result.signed=已驗證公鑰
|
||||||
popup.accountSigning.unsignedPubKeys.result.failed=未能驗證公鑰
|
popup.accountSigning.unsignedPubKeys.result.failed=未能驗證公鑰
|
||||||
|
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit.headline=買家無需支付保證金
|
||||||
|
popup.info.buyerAsTakerWithoutDeposit=您的報價不需要來自XMR買家的保證金或費用。\n\n要接受您的報價,您必須與您的交易夥伴在Haveno之外分享密碼短語。\n\n密碼短語會自動生成並在報價創建後顯示在報價詳情中。
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
# Notifications
|
# Notifications
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -1812,6 +1826,7 @@ navigation.support=“幫助”
|
|||||||
|
|
||||||
formatter.formatVolumeLabel={0} 數量 {1}
|
formatter.formatVolumeLabel={0} 數量 {1}
|
||||||
formatter.makerTaker=賣家 {0} {1} / 買家 {2} {3}
|
formatter.makerTaker=賣家 {0} {1} / 買家 {2} {3}
|
||||||
|
formatter.makerTakerLocked=賣家 {0} {1} / 買家 {2} {3} 🔒
|
||||||
formatter.youAreAsMaker=You are: {1} {0} (maker) / Taker is: {3} {2}
|
formatter.youAreAsMaker=You are: {1} {0} (maker) / Taker is: {3} {2}
|
||||||
formatter.youAreAsTaker=You are: {1} {0} (taker) / Maker is: {3} {2}
|
formatter.youAreAsTaker=You are: {1} {0} (taker) / Maker is: {3} {2}
|
||||||
formatter.youAre=您是 {0} {1} ({2} {3})
|
formatter.youAre=您是 {0} {1} ({2} {3})
|
||||||
|
@ -150,10 +150,12 @@ class GrpcOffersService extends OffersImplBase {
|
|||||||
req.getMarketPriceMarginPct(),
|
req.getMarketPriceMarginPct(),
|
||||||
req.getAmount(),
|
req.getAmount(),
|
||||||
req.getMinAmount(),
|
req.getMinAmount(),
|
||||||
req.getBuyerSecurityDepositPct(),
|
req.getSecurityDepositPct(),
|
||||||
req.getTriggerPrice(),
|
req.getTriggerPrice(),
|
||||||
req.getReserveExactAmount(),
|
req.getReserveExactAmount(),
|
||||||
req.getPaymentAccountId(),
|
req.getPaymentAccountId(),
|
||||||
|
req.getIsPrivateOffer(),
|
||||||
|
req.getBuyerAsTakerWithoutDeposit(),
|
||||||
offer -> {
|
offer -> {
|
||||||
// This result handling consumer's accept operation will return
|
// This result handling consumer's accept operation will return
|
||||||
// the new offer to the gRPC client after async placement is done.
|
// the new offer to the gRPC client after async placement is done.
|
||||||
|
@ -138,6 +138,7 @@ class GrpcTradesService extends TradesImplBase {
|
|||||||
coreApi.takeOffer(req.getOfferId(),
|
coreApi.takeOffer(req.getOfferId(),
|
||||||
req.getPaymentAccountId(),
|
req.getPaymentAccountId(),
|
||||||
req.getAmount(),
|
req.getAmount(),
|
||||||
|
req.getChallenge(),
|
||||||
trade -> {
|
trade -> {
|
||||||
TradeInfo tradeInfo = toTradeInfo(trade);
|
TradeInfo tradeInfo = toTradeInfo(trade);
|
||||||
var reply = TakeOfferReply.newBuilder()
|
var reply = TakeOfferReply.newBuilder()
|
||||||
|
@ -184,14 +184,14 @@ public abstract class PaymentMethodForm {
|
|||||||
Res.get("payment.maxPeriodAndLimitCrypto",
|
Res.get("payment.maxPeriodAndLimitCrypto",
|
||||||
getTimeText(hours),
|
getTimeText(hours),
|
||||||
HavenoUtils.formatXmr(accountAgeWitnessService.getMyTradeLimit(
|
HavenoUtils.formatXmr(accountAgeWitnessService.getMyTradeLimit(
|
||||||
paymentAccount, tradeCurrency.getCode(), OfferDirection.BUY), true))
|
paymentAccount, tradeCurrency.getCode(), OfferDirection.BUY, false), true))
|
||||||
:
|
:
|
||||||
Res.get("payment.maxPeriodAndLimit",
|
Res.get("payment.maxPeriodAndLimit",
|
||||||
getTimeText(hours),
|
getTimeText(hours),
|
||||||
HavenoUtils.formatXmr(accountAgeWitnessService.getMyTradeLimit(
|
HavenoUtils.formatXmr(accountAgeWitnessService.getMyTradeLimit(
|
||||||
paymentAccount, tradeCurrency.getCode(), OfferDirection.BUY), true),
|
paymentAccount, tradeCurrency.getCode(), OfferDirection.BUY, false), true),
|
||||||
HavenoUtils.formatXmr(accountAgeWitnessService.getMyTradeLimit(
|
HavenoUtils.formatXmr(accountAgeWitnessService.getMyTradeLimit(
|
||||||
paymentAccount, tradeCurrency.getCode(), OfferDirection.SELL), true),
|
paymentAccount, tradeCurrency.getCode(), OfferDirection.SELL, false), true),
|
||||||
DisplayUtils.formatAccountAge(accountAge));
|
DisplayUtils.formatAccountAge(accountAge));
|
||||||
return limitationsText;
|
return limitationsText;
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,10 @@
|
|||||||
-fx-image: url("../../images/sell_red.png");
|
-fx-image: url("../../images/sell_red.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#image-lock2x {
|
||||||
|
-fx-image: url("../../images/lock@2x.png");
|
||||||
|
}
|
||||||
|
|
||||||
#image-expand {
|
#image-expand {
|
||||||
-fx-image: url("../../images/expand.png");
|
-fx-image: url("../../images/expand.png");
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,8 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||||||
protected final ObjectProperty<Volume> minVolume = new SimpleObjectProperty<>();
|
protected final ObjectProperty<Volume> minVolume = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
// Percentage value of buyer security deposit. E.g. 0.01 means 1% of trade amount
|
// Percentage value of buyer security deposit. E.g. 0.01 means 1% of trade amount
|
||||||
protected final DoubleProperty buyerSecurityDepositPct = new SimpleDoubleProperty();
|
protected final DoubleProperty securityDepositPct = new SimpleDoubleProperty();
|
||||||
|
protected final BooleanProperty buyerAsTakerWithoutDeposit = new SimpleBooleanProperty();
|
||||||
|
|
||||||
protected final ObservableList<PaymentAccount> paymentAccounts = FXCollections.observableArrayList();
|
protected final ObservableList<PaymentAccount> paymentAccounts = FXCollections.observableArrayList();
|
||||||
|
|
||||||
@ -166,7 +167,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||||||
reserveExactAmount = preferences.getSplitOfferOutput();
|
reserveExactAmount = preferences.getSplitOfferOutput();
|
||||||
|
|
||||||
useMarketBasedPrice.set(preferences.isUsePercentageBasedPrice());
|
useMarketBasedPrice.set(preferences.isUsePercentageBasedPrice());
|
||||||
buyerSecurityDepositPct.set(Restrictions.getMinBuyerSecurityDepositAsPercent());
|
securityDepositPct.set(Restrictions.getMinSecurityDepositAsPercent());
|
||||||
|
|
||||||
paymentAccountsChangeListener = change -> fillPaymentAccounts();
|
paymentAccountsChangeListener = change -> fillPaymentAccounts();
|
||||||
}
|
}
|
||||||
@ -301,8 +302,10 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||||||
useMarketBasedPrice.get() ? null : price.get(),
|
useMarketBasedPrice.get() ? null : price.get(),
|
||||||
useMarketBasedPrice.get(),
|
useMarketBasedPrice.get(),
|
||||||
useMarketBasedPrice.get() ? marketPriceMargin : 0,
|
useMarketBasedPrice.get() ? marketPriceMargin : 0,
|
||||||
buyerSecurityDepositPct.get(),
|
securityDepositPct.get(),
|
||||||
paymentAccount);
|
paymentAccount,
|
||||||
|
buyerAsTakerWithoutDeposit.get(), // private offer if buyer as taker without deposit
|
||||||
|
buyerAsTakerWithoutDeposit.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void onPlaceOffer(Offer offer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
void onPlaceOffer(Offer offer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||||
@ -329,10 +332,10 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setSuggestedSecurityDeposit(PaymentAccount paymentAccount) {
|
private void setSuggestedSecurityDeposit(PaymentAccount paymentAccount) {
|
||||||
var minSecurityDeposit = Restrictions.getMinBuyerSecurityDepositAsPercent();
|
var minSecurityDeposit = Restrictions.getMinSecurityDepositAsPercent();
|
||||||
try {
|
try {
|
||||||
if (getTradeCurrency() == null) {
|
if (getTradeCurrency() == null) {
|
||||||
setBuyerSecurityDeposit(minSecurityDeposit);
|
setSecurityDepositPct(minSecurityDeposit);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Get average historic prices over for the prior trade period equaling the lock time
|
// Get average historic prices over for the prior trade period equaling the lock time
|
||||||
@ -355,16 +358,16 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||||||
var min = extremes[0];
|
var min = extremes[0];
|
||||||
var max = extremes[1];
|
var max = extremes[1];
|
||||||
if (min == 0d || max == 0d) {
|
if (min == 0d || max == 0d) {
|
||||||
setBuyerSecurityDeposit(minSecurityDeposit);
|
setSecurityDepositPct(minSecurityDeposit);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Suggested deposit is double the trade range over the previous lock time period, bounded by min/max deposit
|
// Suggested deposit is double the trade range over the previous lock time period, bounded by min/max deposit
|
||||||
var suggestedSecurityDeposit =
|
var suggestedSecurityDeposit =
|
||||||
Math.min(2 * (max - min) / max, Restrictions.getMaxBuyerSecurityDepositAsPercent());
|
Math.min(2 * (max - min) / max, Restrictions.getMaxSecurityDepositAsPercent());
|
||||||
buyerSecurityDepositPct.set(Math.max(suggestedSecurityDeposit, minSecurityDeposit));
|
securityDepositPct.set(Math.max(suggestedSecurityDeposit, minSecurityDeposit));
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
log.error(t.toString());
|
log.error(t.toString());
|
||||||
buyerSecurityDepositPct.set(minSecurityDeposit);
|
securityDepositPct.set(minSecurityDeposit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,6 +458,10 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||||||
preferences.setUsePercentageBasedPrice(useMarketBasedPrice);
|
preferences.setUsePercentageBasedPrice(useMarketBasedPrice);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void setBuyerAsTakerWithoutDeposit(boolean buyerAsTakerWithoutDeposit) {
|
||||||
|
this.buyerAsTakerWithoutDeposit.set(buyerAsTakerWithoutDeposit);
|
||||||
|
}
|
||||||
|
|
||||||
public ObservableList<PaymentAccount> getPaymentAccounts() {
|
public ObservableList<PaymentAccount> getPaymentAccounts() {
|
||||||
return paymentAccounts;
|
return paymentAccounts;
|
||||||
}
|
}
|
||||||
@ -467,11 +474,11 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||||||
|
|
||||||
// disallow offers which no buyer can take due to trade limits on release
|
// disallow offers which no buyer can take due to trade limits on release
|
||||||
if (HavenoUtils.isReleasedWithinDays(HavenoUtils.RELEASE_LIMIT_DAYS)) {
|
if (HavenoUtils.isReleasedWithinDays(HavenoUtils.RELEASE_LIMIT_DAYS)) {
|
||||||
return accountAgeWitnessService.getMyTradeLimit(paymentAccount, tradeCurrencyCode.get(), OfferDirection.BUY);
|
return accountAgeWitnessService.getMyTradeLimit(paymentAccount, tradeCurrencyCode.get(), OfferDirection.BUY, buyerAsTakerWithoutDeposit.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (paymentAccount != null) {
|
if (paymentAccount != null) {
|
||||||
return accountAgeWitnessService.getMyTradeLimit(paymentAccount, tradeCurrencyCode.get(), direction);
|
return accountAgeWitnessService.getMyTradeLimit(paymentAccount, tradeCurrencyCode.get(), direction, buyerAsTakerWithoutDeposit.get());
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -560,10 +567,6 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BigInteger getSecurityDeposit() {
|
|
||||||
return isBuyOffer() ? getBuyerSecurityDeposit() : getSellerSecurityDeposit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void swapTradeToSavings() {
|
void swapTradeToSavings() {
|
||||||
xmrWalletService.resetAddressEntriesForOpenOffer(offerId);
|
xmrWalletService.resetAddressEntriesForOpenOffer(offerId);
|
||||||
}
|
}
|
||||||
@ -588,8 +591,8 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||||||
this.volume.set(volume);
|
this.volume.set(volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setBuyerSecurityDeposit(double value) {
|
protected void setSecurityDepositPct(double value) {
|
||||||
this.buyerSecurityDepositPct.set(value);
|
this.securityDepositPct.set(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -620,6 +623,10 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||||||
return minVolume;
|
return minVolume;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReadOnlyBooleanProperty getBuyerAsTakerWithoutDeposit() {
|
||||||
|
return buyerAsTakerWithoutDeposit;
|
||||||
|
}
|
||||||
|
|
||||||
protected void setMinAmount(BigInteger minAmount) {
|
protected void setMinAmount(BigInteger minAmount) {
|
||||||
this.minAmount.set(minAmount);
|
this.minAmount.set(minAmount);
|
||||||
}
|
}
|
||||||
@ -644,35 +651,19 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||||||
return useMarketBasedPrice;
|
return useMarketBasedPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadOnlyDoubleProperty getBuyerSecurityDepositPct() {
|
ReadOnlyDoubleProperty getSecurityDepositPct() {
|
||||||
return buyerSecurityDepositPct;
|
return securityDepositPct;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected BigInteger getBuyerSecurityDeposit() {
|
protected BigInteger getSecurityDeposit() {
|
||||||
BigInteger percentOfAmount = CoinUtil.getPercentOfAmount(buyerSecurityDepositPct.get(), amount.get());
|
|
||||||
return getBoundedBuyerSecurityDeposit(percentOfAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigInteger getSellerSecurityDeposit() {
|
|
||||||
BigInteger amount = this.amount.get();
|
BigInteger amount = this.amount.get();
|
||||||
if (amount == null)
|
if (amount == null) amount = BigInteger.ZERO;
|
||||||
amount = BigInteger.ZERO;
|
BigInteger percentOfAmount = CoinUtil.getPercentOfAmount(securityDepositPct.get(), amount);
|
||||||
|
return getBoundedSecurityDeposit(percentOfAmount);
|
||||||
BigInteger percentOfAmount = CoinUtil.getPercentOfAmount(
|
|
||||||
createOfferService.getSellerSecurityDepositAsDouble(buyerSecurityDepositPct.get()), amount);
|
|
||||||
return getBoundedSellerSecurityDeposit(percentOfAmount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected BigInteger getBoundedBuyerSecurityDeposit(BigInteger value) {
|
protected BigInteger getBoundedSecurityDeposit(BigInteger value) {
|
||||||
// We need to ensure that for small amount values we don't get a too low BTC amount. We limit it with using the
|
return Restrictions.getMinSecurityDeposit().max(value);
|
||||||
// MinBuyerSecurityDeposit from Restrictions.
|
|
||||||
return Restrictions.getMinBuyerSecurityDeposit().max(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigInteger getBoundedSellerSecurityDeposit(BigInteger value) {
|
|
||||||
// We need to ensure that for small amount values we don't get a too low BTC amount. We limit it with using the
|
|
||||||
// MinSellerSecurityDeposit from Restrictions.
|
|
||||||
return Restrictions.getMinSellerSecurityDeposit().max(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadOnlyObjectProperty<BigInteger> totalToPayAsProperty() {
|
ReadOnlyObjectProperty<BigInteger> totalToPayAsProperty() {
|
||||||
@ -684,7 +675,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public BigInteger getMaxMakerFee() {
|
public BigInteger getMaxMakerFee() {
|
||||||
return HavenoUtils.multiply(amount.get(), HavenoUtils.MAKER_FEE_PCT);
|
return HavenoUtils.multiply(amount.get(), buyerAsTakerWithoutDeposit.get() ? HavenoUtils.MAKER_FEE_FOR_TAKER_WITHOUT_DEPOSIT_PCT : HavenoUtils.MAKER_FEE_PCT);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean canPlaceOffer() {
|
boolean canPlaceOffer() {
|
||||||
@ -692,8 +683,8 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||||||
GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation);
|
GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMinBuyerSecurityDeposit() {
|
public boolean isMinSecurityDeposit() {
|
||||||
return getBuyerSecurityDeposit().compareTo(Restrictions.getMinBuyerSecurityDeposit()) <= 0;
|
return getSecurityDeposit().compareTo(Restrictions.getMinSecurityDeposit()) <= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTriggerPrice(long triggerPrice) {
|
public void setTriggerPrice(long triggerPrice) {
|
||||||
|
@ -77,6 +77,7 @@ import javafx.scene.control.Label;
|
|||||||
import javafx.scene.control.ScrollPane;
|
import javafx.scene.control.ScrollPane;
|
||||||
import javafx.scene.control.Separator;
|
import javafx.scene.control.Separator;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.control.ToggleButton;
|
||||||
import javafx.scene.control.Tooltip;
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
@ -132,16 +133,17 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
private AutoTooltipButton nextButton, cancelButton1, cancelButton2, placeOfferButton, fundFromSavingsWalletButton;
|
private AutoTooltipButton nextButton, cancelButton1, cancelButton2, placeOfferButton, fundFromSavingsWalletButton;
|
||||||
private Button priceTypeToggleButton;
|
private Button priceTypeToggleButton;
|
||||||
private InputTextField fixedPriceTextField, marketBasedPriceTextField, triggerPriceInputTextField;
|
private InputTextField fixedPriceTextField, marketBasedPriceTextField, triggerPriceInputTextField;
|
||||||
protected InputTextField amountTextField, minAmountTextField, volumeTextField, buyerSecurityDepositInputTextField;
|
protected InputTextField amountTextField, minAmountTextField, volumeTextField, securityDepositInputTextField;
|
||||||
private TextField currencyTextField;
|
private TextField currencyTextField;
|
||||||
private AddressTextField addressTextField;
|
private AddressTextField addressTextField;
|
||||||
private BalanceTextField balanceTextField;
|
private BalanceTextField balanceTextField;
|
||||||
private CheckBox reserveExactAmountCheckbox;
|
private CheckBox reserveExactAmountCheckbox;
|
||||||
|
private ToggleButton buyerAsTakerWithoutDepositSlider;
|
||||||
private FundsTextField totalToPayTextField;
|
private FundsTextField totalToPayTextField;
|
||||||
private Label amountDescriptionLabel, priceCurrencyLabel, priceDescriptionLabel, volumeDescriptionLabel,
|
private Label amountDescriptionLabel, priceCurrencyLabel, priceDescriptionLabel, volumeDescriptionLabel,
|
||||||
waitingForFundsLabel, marketBasedPriceLabel, percentagePriceDescriptionLabel, tradeFeeDescriptionLabel,
|
waitingForFundsLabel, marketBasedPriceLabel, percentagePriceDescriptionLabel, tradeFeeDescriptionLabel,
|
||||||
resultLabel, tradeFeeInXmrLabel, xLabel, fakeXLabel, buyerSecurityDepositLabel,
|
resultLabel, tradeFeeInXmrLabel, xLabel, fakeXLabel, securityDepositLabel,
|
||||||
buyerSecurityDepositPercentageLabel, triggerPriceCurrencyLabel, triggerPriceDescriptionLabel;
|
securityDepositPercentageLabel, triggerPriceCurrencyLabel, triggerPriceDescriptionLabel;
|
||||||
protected Label amountBtcLabel, volumeCurrencyLabel, minAmountBtcLabel;
|
protected Label amountBtcLabel, volumeCurrencyLabel, minAmountBtcLabel;
|
||||||
private ComboBox<PaymentAccount> paymentAccountsComboBox;
|
private ComboBox<PaymentAccount> paymentAccountsComboBox;
|
||||||
private ComboBox<TradeCurrency> currencyComboBox;
|
private ComboBox<TradeCurrency> currencyComboBox;
|
||||||
@ -149,16 +151,16 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
private VBox currencySelection, fixedPriceBox, percentagePriceBox, currencyTextFieldBox, triggerPriceVBox;
|
private VBox currencySelection, fixedPriceBox, percentagePriceBox, currencyTextFieldBox, triggerPriceVBox;
|
||||||
private HBox fundingHBox, firstRowHBox, secondRowHBox, placeOfferBox, amountValueCurrencyBox,
|
private HBox fundingHBox, firstRowHBox, secondRowHBox, placeOfferBox, amountValueCurrencyBox,
|
||||||
priceAsPercentageValueCurrencyBox, volumeValueCurrencyBox, priceValueCurrencyBox,
|
priceAsPercentageValueCurrencyBox, volumeValueCurrencyBox, priceValueCurrencyBox,
|
||||||
minAmountValueCurrencyBox, advancedOptionsBox, triggerPriceHBox;
|
minAmountValueCurrencyBox, securityDepositAndFeeBox, triggerPriceHBox;
|
||||||
|
|
||||||
private Subscription isWaitingForFundsSubscription, balanceSubscription;
|
private Subscription isWaitingForFundsSubscription, balanceSubscription;
|
||||||
private ChangeListener<Boolean> amountFocusedListener, minAmountFocusedListener, volumeFocusedListener,
|
private ChangeListener<Boolean> amountFocusedListener, minAmountFocusedListener, volumeFocusedListener,
|
||||||
buyerSecurityDepositFocusedListener, priceFocusedListener, placeOfferCompletedListener,
|
securityDepositFocusedListener, priceFocusedListener, placeOfferCompletedListener,
|
||||||
priceAsPercentageFocusedListener, getShowWalletFundedNotificationListener,
|
priceAsPercentageFocusedListener, getShowWalletFundedNotificationListener,
|
||||||
isMinBuyerSecurityDepositListener, triggerPriceFocusedListener;
|
isMinSecurityDepositListener, buyerAsTakerWithoutDepositListener, triggerPriceFocusedListener;
|
||||||
private ChangeListener<BigInteger> missingCoinListener;
|
private ChangeListener<BigInteger> missingCoinListener;
|
||||||
private ChangeListener<String> tradeCurrencyCodeListener, errorMessageListener,
|
private ChangeListener<String> tradeCurrencyCodeListener, errorMessageListener,
|
||||||
marketPriceMarginListener, volumeListener, buyerSecurityDepositInBTCListener;
|
marketPriceMarginListener, volumeListener, securityDepositInXMRListener;
|
||||||
private ChangeListener<Number> marketPriceAvailableListener;
|
private ChangeListener<Number> marketPriceAvailableListener;
|
||||||
private EventHandler<ActionEvent> currencyComboBoxSelectionHandler, paymentAccountsComboBoxSelectionHandler;
|
private EventHandler<ActionEvent> currencyComboBoxSelectionHandler, paymentAccountsComboBoxSelectionHandler;
|
||||||
private OfferView.CloseHandler closeHandler;
|
private OfferView.CloseHandler closeHandler;
|
||||||
@ -168,7 +170,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
private final HashMap<String, Boolean> paymentAccountWarningDisplayed = new HashMap<>();
|
private final HashMap<String, Boolean> paymentAccountWarningDisplayed = new HashMap<>();
|
||||||
private boolean zelleWarningDisplayed, fasterPaymentsWarningDisplayed, isActivated;
|
private boolean zelleWarningDisplayed, fasterPaymentsWarningDisplayed, isActivated;
|
||||||
private InfoInputTextField marketBasedPriceInfoInputTextField, volumeInfoInputTextField,
|
private InfoInputTextField marketBasedPriceInfoInputTextField, volumeInfoInputTextField,
|
||||||
buyerSecurityDepositInfoInputTextField, triggerPriceInfoInputTextField;
|
securityDepositInfoInputTextField, triggerPriceInfoInputTextField;
|
||||||
private Text xIcon, fakeXIcon;
|
private Text xIcon, fakeXIcon;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@ -252,6 +254,8 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
|
|
||||||
Label popOverLabel = OfferViewUtil.createPopOverLabel(Res.get("createOffer.triggerPrice.tooltip"));
|
Label popOverLabel = OfferViewUtil.createPopOverLabel(Res.get("createOffer.triggerPrice.tooltip"));
|
||||||
triggerPriceInfoInputTextField.setContentForPopOver(popOverLabel, AwesomeIcon.SHIELD);
|
triggerPriceInfoInputTextField.setContentForPopOver(popOverLabel, AwesomeIcon.SHIELD);
|
||||||
|
|
||||||
|
buyerAsTakerWithoutDepositSlider.setSelected(model.dataModel.getBuyerAsTakerWithoutDeposit().get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,6 +327,9 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
fundFromSavingsWalletButton.setId("sell-button");
|
fundFromSavingsWalletButton.setId("sell-button");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buyerAsTakerWithoutDepositSlider.setVisible(model.isSellOffer());
|
||||||
|
buyerAsTakerWithoutDepositSlider.setManaged(model.isSellOffer());
|
||||||
|
|
||||||
placeOfferButton.updateText(placeOfferButtonLabel);
|
placeOfferButton.updateText(placeOfferButtonLabel);
|
||||||
updatePriceToggle();
|
updatePriceToggle();
|
||||||
}
|
}
|
||||||
@ -375,8 +382,11 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
setDepositTitledGroupBg.setVisible(false);
|
setDepositTitledGroupBg.setVisible(false);
|
||||||
setDepositTitledGroupBg.setManaged(false);
|
setDepositTitledGroupBg.setManaged(false);
|
||||||
|
|
||||||
advancedOptionsBox.setVisible(false);
|
securityDepositAndFeeBox.setVisible(false);
|
||||||
advancedOptionsBox.setManaged(false);
|
securityDepositAndFeeBox.setManaged(false);
|
||||||
|
|
||||||
|
buyerAsTakerWithoutDepositSlider.setVisible(false);
|
||||||
|
buyerAsTakerWithoutDepositSlider.setManaged(false);
|
||||||
|
|
||||||
updateQrCode();
|
updateQrCode();
|
||||||
|
|
||||||
@ -556,8 +566,8 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
volumeTextField.promptTextProperty().bind(model.volumePromptLabel);
|
volumeTextField.promptTextProperty().bind(model.volumePromptLabel);
|
||||||
totalToPayTextField.textProperty().bind(model.totalToPay);
|
totalToPayTextField.textProperty().bind(model.totalToPay);
|
||||||
addressTextField.amountAsProperty().bind(model.getDataModel().getMissingCoin());
|
addressTextField.amountAsProperty().bind(model.getDataModel().getMissingCoin());
|
||||||
buyerSecurityDepositInputTextField.textProperty().bindBidirectional(model.buyerSecurityDeposit);
|
securityDepositInputTextField.textProperty().bindBidirectional(model.securityDeposit);
|
||||||
buyerSecurityDepositLabel.textProperty().bind(model.buyerSecurityDepositLabel);
|
securityDepositLabel.textProperty().bind(model.securityDepositLabel);
|
||||||
tradeFeeInXmrLabel.textProperty().bind(model.tradeFeeInXmrWithFiat);
|
tradeFeeInXmrLabel.textProperty().bind(model.tradeFeeInXmrWithFiat);
|
||||||
tradeFeeDescriptionLabel.textProperty().bind(model.tradeFeeDescription);
|
tradeFeeDescriptionLabel.textProperty().bind(model.tradeFeeDescription);
|
||||||
|
|
||||||
@ -567,7 +577,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
fixedPriceTextField.validationResultProperty().bind(model.priceValidationResult);
|
fixedPriceTextField.validationResultProperty().bind(model.priceValidationResult);
|
||||||
triggerPriceInputTextField.validationResultProperty().bind(model.triggerPriceValidationResult);
|
triggerPriceInputTextField.validationResultProperty().bind(model.triggerPriceValidationResult);
|
||||||
volumeTextField.validationResultProperty().bind(model.volumeValidationResult);
|
volumeTextField.validationResultProperty().bind(model.volumeValidationResult);
|
||||||
buyerSecurityDepositInputTextField.validationResultProperty().bind(model.buyerSecurityDepositValidationResult);
|
securityDepositInputTextField.validationResultProperty().bind(model.securityDepositValidationResult);
|
||||||
|
|
||||||
// funding
|
// funding
|
||||||
fundingHBox.visibleProperty().bind(model.getDataModel().getIsXmrWalletFunded().not().and(model.showPayFundsScreenDisplayed));
|
fundingHBox.visibleProperty().bind(model.getDataModel().getIsXmrWalletFunded().not().and(model.showPayFundsScreenDisplayed));
|
||||||
@ -604,8 +614,8 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
volumeTextField.promptTextProperty().unbindBidirectional(model.volume);
|
volumeTextField.promptTextProperty().unbindBidirectional(model.volume);
|
||||||
totalToPayTextField.textProperty().unbind();
|
totalToPayTextField.textProperty().unbind();
|
||||||
addressTextField.amountAsProperty().unbind();
|
addressTextField.amountAsProperty().unbind();
|
||||||
buyerSecurityDepositInputTextField.textProperty().unbindBidirectional(model.buyerSecurityDeposit);
|
securityDepositInputTextField.textProperty().unbindBidirectional(model.securityDeposit);
|
||||||
buyerSecurityDepositLabel.textProperty().unbind();
|
securityDepositLabel.textProperty().unbind();
|
||||||
tradeFeeInXmrLabel.textProperty().unbind();
|
tradeFeeInXmrLabel.textProperty().unbind();
|
||||||
tradeFeeDescriptionLabel.textProperty().unbind();
|
tradeFeeDescriptionLabel.textProperty().unbind();
|
||||||
tradeFeeInXmrLabel.visibleProperty().unbind();
|
tradeFeeInXmrLabel.visibleProperty().unbind();
|
||||||
@ -617,7 +627,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
fixedPriceTextField.validationResultProperty().unbind();
|
fixedPriceTextField.validationResultProperty().unbind();
|
||||||
triggerPriceInputTextField.validationResultProperty().unbind();
|
triggerPriceInputTextField.validationResultProperty().unbind();
|
||||||
volumeTextField.validationResultProperty().unbind();
|
volumeTextField.validationResultProperty().unbind();
|
||||||
buyerSecurityDepositInputTextField.validationResultProperty().unbind();
|
securityDepositInputTextField.validationResultProperty().unbind();
|
||||||
|
|
||||||
// funding
|
// funding
|
||||||
fundingHBox.visibleProperty().unbind();
|
fundingHBox.visibleProperty().unbind();
|
||||||
@ -679,9 +689,9 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
model.onFocusOutVolumeTextField(oldValue, newValue);
|
model.onFocusOutVolumeTextField(oldValue, newValue);
|
||||||
volumeTextField.setText(model.volume.get());
|
volumeTextField.setText(model.volume.get());
|
||||||
};
|
};
|
||||||
buyerSecurityDepositFocusedListener = (o, oldValue, newValue) -> {
|
securityDepositFocusedListener = (o, oldValue, newValue) -> {
|
||||||
model.onFocusOutBuyerSecurityDepositTextField(oldValue, newValue);
|
model.onFocusOutSecurityDepositTextField(oldValue, newValue);
|
||||||
buyerSecurityDepositInputTextField.setText(model.buyerSecurityDeposit.get());
|
securityDepositInputTextField.setText(model.securityDeposit.get());
|
||||||
};
|
};
|
||||||
|
|
||||||
triggerPriceFocusedListener = (o, oldValue, newValue) -> {
|
triggerPriceFocusedListener = (o, oldValue, newValue) -> {
|
||||||
@ -750,12 +760,11 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
buyerSecurityDepositInBTCListener = (observable, oldValue, newValue) -> {
|
securityDepositInXMRListener = (observable, oldValue, newValue) -> {
|
||||||
if (!newValue.equals("")) {
|
if (!newValue.equals("")) {
|
||||||
Label depositInBTCInfo = OfferViewUtil.createPopOverLabel(model.getSecurityDepositPopOverLabel(newValue));
|
updateSecurityDepositLabels();
|
||||||
buyerSecurityDepositInfoInputTextField.setContentForInfoPopOver(depositInBTCInfo);
|
|
||||||
} else {
|
} else {
|
||||||
buyerSecurityDepositInfoInputTextField.setContentForInfoPopOver(null);
|
securityDepositInfoInputTextField.setContentForInfoPopOver(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -805,17 +814,29 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
isMinBuyerSecurityDepositListener = ((observable, oldValue, newValue) -> {
|
isMinSecurityDepositListener = ((observable, oldValue, newValue) -> {
|
||||||
if (newValue) {
|
updateSecurityDepositLabels();
|
||||||
// show BTC
|
});
|
||||||
buyerSecurityDepositPercentageLabel.setText(Res.getBaseCurrencyCode());
|
|
||||||
buyerSecurityDepositInputTextField.setDisable(true);
|
buyerAsTakerWithoutDepositListener = ((observable, oldValue, newValue) -> {
|
||||||
|
updateSecurityDepositLabels();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSecurityDepositLabels() {
|
||||||
|
if (model.isMinSecurityDeposit.get()) {
|
||||||
|
// show XMR
|
||||||
|
securityDepositPercentageLabel.setText(Res.getBaseCurrencyCode());
|
||||||
|
securityDepositInputTextField.setDisable(true);
|
||||||
} else {
|
} else {
|
||||||
// show %
|
// show %
|
||||||
buyerSecurityDepositPercentageLabel.setText("%");
|
securityDepositPercentageLabel.setText("%");
|
||||||
buyerSecurityDepositInputTextField.setDisable(false);
|
securityDepositInputTextField.setDisable(model.getDataModel().buyerAsTakerWithoutDeposit.get());
|
||||||
|
}
|
||||||
|
if (model.securityDepositInXMR.get() != null && !model.securityDepositInXMR.get().equals("")) {
|
||||||
|
Label depositInBTCInfo = OfferViewUtil.createPopOverLabel(model.getSecurityDepositPopOverLabel(model.securityDepositInXMR.get()));
|
||||||
|
securityDepositInfoInputTextField.setContentForInfoPopOver(depositInBTCInfo);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateQrCode() {
|
private void updateQrCode() {
|
||||||
@ -856,8 +877,9 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
model.marketPriceMargin.addListener(marketPriceMarginListener);
|
model.marketPriceMargin.addListener(marketPriceMarginListener);
|
||||||
model.volume.addListener(volumeListener);
|
model.volume.addListener(volumeListener);
|
||||||
model.getDataModel().missingCoin.addListener(missingCoinListener);
|
model.getDataModel().missingCoin.addListener(missingCoinListener);
|
||||||
model.buyerSecurityDepositInBTC.addListener(buyerSecurityDepositInBTCListener);
|
model.securityDepositInXMR.addListener(securityDepositInXMRListener);
|
||||||
model.isMinBuyerSecurityDeposit.addListener(isMinBuyerSecurityDepositListener);
|
model.isMinSecurityDeposit.addListener(isMinSecurityDepositListener);
|
||||||
|
model.getDataModel().buyerAsTakerWithoutDeposit.addListener(buyerAsTakerWithoutDepositListener);
|
||||||
|
|
||||||
// focus out
|
// focus out
|
||||||
amountTextField.focusedProperty().addListener(amountFocusedListener);
|
amountTextField.focusedProperty().addListener(amountFocusedListener);
|
||||||
@ -866,7 +888,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
triggerPriceInputTextField.focusedProperty().addListener(triggerPriceFocusedListener);
|
triggerPriceInputTextField.focusedProperty().addListener(triggerPriceFocusedListener);
|
||||||
marketBasedPriceTextField.focusedProperty().addListener(priceAsPercentageFocusedListener);
|
marketBasedPriceTextField.focusedProperty().addListener(priceAsPercentageFocusedListener);
|
||||||
volumeTextField.focusedProperty().addListener(volumeFocusedListener);
|
volumeTextField.focusedProperty().addListener(volumeFocusedListener);
|
||||||
buyerSecurityDepositInputTextField.focusedProperty().addListener(buyerSecurityDepositFocusedListener);
|
securityDepositInputTextField.focusedProperty().addListener(securityDepositFocusedListener);
|
||||||
|
|
||||||
// notifications
|
// notifications
|
||||||
model.getDataModel().getShowWalletFundedNotification().addListener(getShowWalletFundedNotificationListener);
|
model.getDataModel().getShowWalletFundedNotification().addListener(getShowWalletFundedNotificationListener);
|
||||||
@ -888,8 +910,9 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
model.marketPriceMargin.removeListener(marketPriceMarginListener);
|
model.marketPriceMargin.removeListener(marketPriceMarginListener);
|
||||||
model.volume.removeListener(volumeListener);
|
model.volume.removeListener(volumeListener);
|
||||||
model.getDataModel().missingCoin.removeListener(missingCoinListener);
|
model.getDataModel().missingCoin.removeListener(missingCoinListener);
|
||||||
model.buyerSecurityDepositInBTC.removeListener(buyerSecurityDepositInBTCListener);
|
model.securityDepositInXMR.removeListener(securityDepositInXMRListener);
|
||||||
model.isMinBuyerSecurityDeposit.removeListener(isMinBuyerSecurityDepositListener);
|
model.isMinSecurityDeposit.removeListener(isMinSecurityDepositListener);
|
||||||
|
model.getDataModel().buyerAsTakerWithoutDeposit.removeListener(buyerAsTakerWithoutDepositListener);
|
||||||
|
|
||||||
// focus out
|
// focus out
|
||||||
amountTextField.focusedProperty().removeListener(amountFocusedListener);
|
amountTextField.focusedProperty().removeListener(amountFocusedListener);
|
||||||
@ -898,7 +921,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
triggerPriceInputTextField.focusedProperty().removeListener(triggerPriceFocusedListener);
|
triggerPriceInputTextField.focusedProperty().removeListener(triggerPriceFocusedListener);
|
||||||
marketBasedPriceTextField.focusedProperty().removeListener(priceAsPercentageFocusedListener);
|
marketBasedPriceTextField.focusedProperty().removeListener(priceAsPercentageFocusedListener);
|
||||||
volumeTextField.focusedProperty().removeListener(volumeFocusedListener);
|
volumeTextField.focusedProperty().removeListener(volumeFocusedListener);
|
||||||
buyerSecurityDepositInputTextField.focusedProperty().removeListener(buyerSecurityDepositFocusedListener);
|
securityDepositInputTextField.focusedProperty().removeListener(securityDepositFocusedListener);
|
||||||
|
|
||||||
// notifications
|
// notifications
|
||||||
model.getDataModel().getShowWalletFundedNotification().removeListener(getShowWalletFundedNotificationListener);
|
model.getDataModel().getShowWalletFundedNotification().removeListener(getShowWalletFundedNotificationListener);
|
||||||
@ -997,22 +1020,46 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void addOptionsGroup() {
|
private void addOptionsGroup() {
|
||||||
setDepositTitledGroupBg = addTitledGroupBg(gridPane, ++gridRow, 1,
|
setDepositTitledGroupBg = addTitledGroupBg(gridPane, ++gridRow, 2,
|
||||||
Res.get("shared.advancedOptions"), Layout.COMPACT_GROUP_DISTANCE);
|
Res.get("shared.advancedOptions"), Layout.COMPACT_GROUP_DISTANCE);
|
||||||
|
|
||||||
advancedOptionsBox = new HBox();
|
securityDepositAndFeeBox = new HBox();
|
||||||
advancedOptionsBox.setSpacing(40);
|
securityDepositAndFeeBox.setSpacing(40);
|
||||||
|
|
||||||
GridPane.setRowIndex(advancedOptionsBox, gridRow);
|
GridPane.setRowIndex(securityDepositAndFeeBox, gridRow);
|
||||||
GridPane.setColumnSpan(advancedOptionsBox, GridPane.REMAINING);
|
GridPane.setColumnSpan(securityDepositAndFeeBox, GridPane.REMAINING);
|
||||||
GridPane.setColumnIndex(advancedOptionsBox, 0);
|
GridPane.setColumnIndex(securityDepositAndFeeBox, 0);
|
||||||
GridPane.setHalignment(advancedOptionsBox, HPos.LEFT);
|
GridPane.setHalignment(securityDepositAndFeeBox, HPos.LEFT);
|
||||||
GridPane.setMargin(advancedOptionsBox, new Insets(Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE, 0, 0, 0));
|
GridPane.setMargin(securityDepositAndFeeBox, new Insets(Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE, 0, 0, 0));
|
||||||
gridPane.getChildren().add(advancedOptionsBox);
|
gridPane.getChildren().add(securityDepositAndFeeBox);
|
||||||
|
|
||||||
VBox tradeFeeFieldsBox = getTradeFeeFieldsBox();
|
VBox tradeFeeFieldsBox = getTradeFeeFieldsBox();
|
||||||
tradeFeeFieldsBox.setMinWidth(240);
|
tradeFeeFieldsBox.setMinWidth(240);
|
||||||
advancedOptionsBox.getChildren().addAll(getBuyerSecurityDepositBox(), tradeFeeFieldsBox);
|
securityDepositAndFeeBox.getChildren().addAll(getSecurityDepositBox(), tradeFeeFieldsBox);
|
||||||
|
|
||||||
|
buyerAsTakerWithoutDepositSlider = FormBuilder.addSlideToggleButton(gridPane, ++gridRow, Res.get("createOffer.buyerAsTakerWithoutDeposit"));
|
||||||
|
buyerAsTakerWithoutDepositSlider.setOnAction(event -> {
|
||||||
|
|
||||||
|
// popup info box
|
||||||
|
String key = "popup.info.buyerAsTakerWithoutDeposit";
|
||||||
|
if (buyerAsTakerWithoutDepositSlider.isSelected() && DontShowAgainLookup.showAgain(key)) {
|
||||||
|
new Popup().headLine(Res.get(key + ".headline"))
|
||||||
|
.information(Res.get(key))
|
||||||
|
.closeButtonText(Res.get("shared.cancel"))
|
||||||
|
.actionButtonText(Res.get("shared.ok"))
|
||||||
|
.onAction(() -> model.dataModel.setBuyerAsTakerWithoutDeposit(true))
|
||||||
|
.onClose(() -> {
|
||||||
|
buyerAsTakerWithoutDepositSlider.setSelected(false);
|
||||||
|
model.dataModel.setBuyerAsTakerWithoutDeposit(false);
|
||||||
|
})
|
||||||
|
.dontShowAgainId(key)
|
||||||
|
.show();
|
||||||
|
} else {
|
||||||
|
model.dataModel.setBuyerAsTakerWithoutDeposit(buyerAsTakerWithoutDepositSlider.isSelected());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
GridPane.setHalignment(buyerAsTakerWithoutDepositSlider, HPos.LEFT);
|
||||||
|
GridPane.setMargin(buyerAsTakerWithoutDepositSlider, new Insets(0, 0, 0, 0));
|
||||||
|
|
||||||
Tuple2<Button, Button> tuple = add2ButtonsAfterGroup(gridPane, ++gridRow,
|
Tuple2<Button, Button> tuple = add2ButtonsAfterGroup(gridPane, ++gridRow,
|
||||||
Res.get("shared.nextStep"), Res.get("shared.cancel"));
|
Res.get("shared.nextStep"), Res.get("shared.cancel"));
|
||||||
@ -1060,26 +1107,28 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||||||
nextButton.setManaged(false);
|
nextButton.setManaged(false);
|
||||||
cancelButton1.setVisible(false);
|
cancelButton1.setVisible(false);
|
||||||
cancelButton1.setManaged(false);
|
cancelButton1.setManaged(false);
|
||||||
advancedOptionsBox.setVisible(false);
|
securityDepositAndFeeBox.setVisible(false);
|
||||||
advancedOptionsBox.setManaged(false);
|
securityDepositAndFeeBox.setManaged(false);
|
||||||
|
buyerAsTakerWithoutDepositSlider.setVisible(false);
|
||||||
|
buyerAsTakerWithoutDepositSlider.setManaged(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private VBox getBuyerSecurityDepositBox() {
|
private VBox getSecurityDepositBox() {
|
||||||
Tuple3<HBox, InfoInputTextField, Label> tuple = getEditableValueBoxWithInfo(
|
Tuple3<HBox, InfoInputTextField, Label> tuple = getEditableValueBoxWithInfo(
|
||||||
Res.get("createOffer.securityDeposit.prompt"));
|
Res.get("createOffer.securityDeposit.prompt"));
|
||||||
buyerSecurityDepositInfoInputTextField = tuple.second;
|
securityDepositInfoInputTextField = tuple.second;
|
||||||
buyerSecurityDepositInputTextField = buyerSecurityDepositInfoInputTextField.getInputTextField();
|
securityDepositInputTextField = securityDepositInfoInputTextField.getInputTextField();
|
||||||
buyerSecurityDepositPercentageLabel = tuple.third;
|
securityDepositPercentageLabel = tuple.third;
|
||||||
// getEditableValueBox delivers BTC, so we overwrite it with %
|
// getEditableValueBox delivers BTC, so we overwrite it with %
|
||||||
buyerSecurityDepositPercentageLabel.setText("%");
|
securityDepositPercentageLabel.setText("%");
|
||||||
|
|
||||||
Tuple2<Label, VBox> tradeInputBoxTuple = getTradeInputBox(tuple.first, model.getSecurityDepositLabel());
|
Tuple2<Label, VBox> tradeInputBoxTuple = getTradeInputBox(tuple.first, model.getSecurityDepositLabel());
|
||||||
VBox depositBox = tradeInputBoxTuple.second;
|
VBox depositBox = tradeInputBoxTuple.second;
|
||||||
buyerSecurityDepositLabel = tradeInputBoxTuple.first;
|
securityDepositLabel = tradeInputBoxTuple.first;
|
||||||
depositBox.setMaxWidth(310);
|
depositBox.setMaxWidth(310);
|
||||||
|
|
||||||
editOfferElements.add(buyerSecurityDepositInputTextField);
|
editOfferElements.add(securityDepositInputTextField);
|
||||||
editOfferElements.add(buyerSecurityDepositPercentageLabel);
|
editOfferElements.add(securityDepositPercentageLabel);
|
||||||
|
|
||||||
return depositBox;
|
return depositBox;
|
||||||
}
|
}
|
||||||
|
@ -113,9 +113,9 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
|
|
||||||
public final StringProperty amount = new SimpleStringProperty();
|
public final StringProperty amount = new SimpleStringProperty();
|
||||||
public final StringProperty minAmount = new SimpleStringProperty();
|
public final StringProperty minAmount = new SimpleStringProperty();
|
||||||
protected final StringProperty buyerSecurityDeposit = new SimpleStringProperty();
|
protected final StringProperty securityDeposit = new SimpleStringProperty();
|
||||||
final StringProperty buyerSecurityDepositInBTC = new SimpleStringProperty();
|
final StringProperty securityDepositInXMR = new SimpleStringProperty();
|
||||||
final StringProperty buyerSecurityDepositLabel = new SimpleStringProperty();
|
final StringProperty securityDepositLabel = new SimpleStringProperty();
|
||||||
|
|
||||||
// Price in the viewModel is always dependent on fiat/crypto: Fiat Fiat/BTC, for cryptos we use inverted price.
|
// Price in the viewModel is always dependent on fiat/crypto: Fiat Fiat/BTC, for cryptos we use inverted price.
|
||||||
// The domain (dataModel) uses always the same price model (otherCurrencyBTC)
|
// The domain (dataModel) uses always the same price model (otherCurrencyBTC)
|
||||||
@ -151,14 +151,14 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
final BooleanProperty showPayFundsScreenDisplayed = new SimpleBooleanProperty();
|
final BooleanProperty showPayFundsScreenDisplayed = new SimpleBooleanProperty();
|
||||||
private final BooleanProperty showTransactionPublishedScreen = new SimpleBooleanProperty();
|
private final BooleanProperty showTransactionPublishedScreen = new SimpleBooleanProperty();
|
||||||
final BooleanProperty isWaitingForFunds = new SimpleBooleanProperty();
|
final BooleanProperty isWaitingForFunds = new SimpleBooleanProperty();
|
||||||
final BooleanProperty isMinBuyerSecurityDeposit = new SimpleBooleanProperty();
|
final BooleanProperty isMinSecurityDeposit = new SimpleBooleanProperty();
|
||||||
|
|
||||||
final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>();
|
final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>();
|
||||||
final ObjectProperty<InputValidator.ValidationResult> minAmountValidationResult = new SimpleObjectProperty<>();
|
final ObjectProperty<InputValidator.ValidationResult> minAmountValidationResult = new SimpleObjectProperty<>();
|
||||||
final ObjectProperty<InputValidator.ValidationResult> priceValidationResult = new SimpleObjectProperty<>();
|
final ObjectProperty<InputValidator.ValidationResult> priceValidationResult = new SimpleObjectProperty<>();
|
||||||
final ObjectProperty<InputValidator.ValidationResult> triggerPriceValidationResult = new SimpleObjectProperty<>(new InputValidator.ValidationResult(true));
|
final ObjectProperty<InputValidator.ValidationResult> triggerPriceValidationResult = new SimpleObjectProperty<>(new InputValidator.ValidationResult(true));
|
||||||
final ObjectProperty<InputValidator.ValidationResult> volumeValidationResult = new SimpleObjectProperty<>();
|
final ObjectProperty<InputValidator.ValidationResult> volumeValidationResult = new SimpleObjectProperty<>();
|
||||||
final ObjectProperty<InputValidator.ValidationResult> buyerSecurityDepositValidationResult = new SimpleObjectProperty<>();
|
final ObjectProperty<InputValidator.ValidationResult> securityDepositValidationResult = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
private ChangeListener<String> amountStringListener;
|
private ChangeListener<String> amountStringListener;
|
||||||
private ChangeListener<String> minAmountStringListener;
|
private ChangeListener<String> minAmountStringListener;
|
||||||
@ -171,6 +171,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
private ChangeListener<Price> priceListener;
|
private ChangeListener<Price> priceListener;
|
||||||
private ChangeListener<Volume> volumeListener;
|
private ChangeListener<Volume> volumeListener;
|
||||||
private ChangeListener<Number> securityDepositAsDoubleListener;
|
private ChangeListener<Number> securityDepositAsDoubleListener;
|
||||||
|
private ChangeListener<Boolean> buyerAsTakerWithoutDepositListener;
|
||||||
|
|
||||||
private ChangeListener<Boolean> isWalletFundedListener;
|
private ChangeListener<Boolean> isWalletFundedListener;
|
||||||
private ChangeListener<String> errorMessageListener;
|
private ChangeListener<String> errorMessageListener;
|
||||||
@ -303,7 +304,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
dataModel.calculateVolume();
|
dataModel.calculateVolume();
|
||||||
dataModel.calculateTotalToPay();
|
dataModel.calculateTotalToPay();
|
||||||
}
|
}
|
||||||
updateBuyerSecurityDeposit();
|
updateSecurityDeposit();
|
||||||
updateButtonDisableState();
|
updateButtonDisableState();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -419,34 +420,36 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
updateButtonDisableState();
|
updateButtonDisableState();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
securityDepositStringListener = (ov, oldValue, newValue) -> {
|
securityDepositStringListener = (ov, oldValue, newValue) -> {
|
||||||
if (!ignoreSecurityDepositStringListener) {
|
if (!ignoreSecurityDepositStringListener) {
|
||||||
if (securityDepositValidator.validate(newValue).isValid) {
|
if (securityDepositValidator.validate(newValue).isValid) {
|
||||||
setBuyerSecurityDepositToModel();
|
setSecurityDepositToModel();
|
||||||
dataModel.calculateTotalToPay();
|
dataModel.calculateTotalToPay();
|
||||||
}
|
}
|
||||||
updateButtonDisableState();
|
updateButtonDisableState();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
amountListener = (ov, oldValue, newValue) -> {
|
amountListener = (ov, oldValue, newValue) -> {
|
||||||
if (newValue != null) {
|
if (newValue != null) {
|
||||||
amount.set(HavenoUtils.formatXmr(newValue));
|
amount.set(HavenoUtils.formatXmr(newValue));
|
||||||
buyerSecurityDepositInBTC.set(HavenoUtils.formatXmr(dataModel.getBuyerSecurityDeposit(), true));
|
securityDepositInXMR.set(HavenoUtils.formatXmr(dataModel.getSecurityDeposit(), true));
|
||||||
} else {
|
} else {
|
||||||
amount.set("");
|
amount.set("");
|
||||||
buyerSecurityDepositInBTC.set("");
|
securityDepositInXMR.set("");
|
||||||
}
|
}
|
||||||
|
|
||||||
applyMakerFee();
|
applyMakerFee();
|
||||||
};
|
};
|
||||||
|
|
||||||
minAmountListener = (ov, oldValue, newValue) -> {
|
minAmountListener = (ov, oldValue, newValue) -> {
|
||||||
if (newValue != null)
|
if (newValue != null)
|
||||||
minAmount.set(HavenoUtils.formatXmr(newValue));
|
minAmount.set(HavenoUtils.formatXmr(newValue));
|
||||||
else
|
else
|
||||||
minAmount.set("");
|
minAmount.set("");
|
||||||
};
|
};
|
||||||
|
|
||||||
priceListener = (ov, oldValue, newValue) -> {
|
priceListener = (ov, oldValue, newValue) -> {
|
||||||
ignorePriceStringListener = true;
|
ignorePriceStringListener = true;
|
||||||
if (newValue != null)
|
if (newValue != null)
|
||||||
@ -457,6 +460,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
ignorePriceStringListener = false;
|
ignorePriceStringListener = false;
|
||||||
applyMakerFee();
|
applyMakerFee();
|
||||||
};
|
};
|
||||||
|
|
||||||
volumeListener = (ov, oldValue, newValue) -> {
|
volumeListener = (ov, oldValue, newValue) -> {
|
||||||
ignoreVolumeStringListener = true;
|
ignoreVolumeStringListener = true;
|
||||||
if (newValue != null)
|
if (newValue != null)
|
||||||
@ -470,17 +474,26 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
|
|
||||||
securityDepositAsDoubleListener = (ov, oldValue, newValue) -> {
|
securityDepositAsDoubleListener = (ov, oldValue, newValue) -> {
|
||||||
if (newValue != null) {
|
if (newValue != null) {
|
||||||
buyerSecurityDeposit.set(FormattingUtils.formatToPercent((double) newValue));
|
securityDeposit.set(FormattingUtils.formatToPercent((double) newValue));
|
||||||
if (dataModel.getAmount().get() != null) {
|
if (dataModel.getAmount().get() != null) {
|
||||||
buyerSecurityDepositInBTC.set(HavenoUtils.formatXmr(dataModel.getBuyerSecurityDeposit(), true));
|
securityDepositInXMR.set(HavenoUtils.formatXmr(dataModel.getSecurityDeposit(), true));
|
||||||
}
|
}
|
||||||
updateBuyerSecurityDeposit();
|
updateSecurityDeposit();
|
||||||
} else {
|
} else {
|
||||||
buyerSecurityDeposit.set("");
|
securityDeposit.set("");
|
||||||
buyerSecurityDepositInBTC.set("");
|
securityDepositInXMR.set("");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
buyerAsTakerWithoutDepositListener = (ov, oldValue, newValue) -> {
|
||||||
|
if (dataModel.paymentAccount != null) xmrValidator.setMaxValue(dataModel.paymentAccount.getPaymentMethod().getMaxTradeLimit(dataModel.getTradeCurrencyCode().get()));
|
||||||
|
xmrValidator.setMaxTradeLimit(BigInteger.valueOf(dataModel.getMaxTradeLimit()));
|
||||||
|
if (amount.get() != null) amountValidationResult.set(isXmrInputValid(amount.get()));
|
||||||
|
updateSecurityDeposit();
|
||||||
|
applyMakerFee();
|
||||||
|
dataModel.calculateTotalToPay();
|
||||||
|
updateButtonDisableState();
|
||||||
|
};
|
||||||
|
|
||||||
isWalletFundedListener = (ov, oldValue, newValue) -> updateButtonDisableState();
|
isWalletFundedListener = (ov, oldValue, newValue) -> updateButtonDisableState();
|
||||||
/* feeFromFundingTxListener = (ov, oldValue, newValue) -> {
|
/* feeFromFundingTxListener = (ov, oldValue, newValue) -> {
|
||||||
@ -525,14 +538,15 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
marketPriceMargin.addListener(marketPriceMarginStringListener);
|
marketPriceMargin.addListener(marketPriceMarginStringListener);
|
||||||
dataModel.getUseMarketBasedPrice().addListener(useMarketBasedPriceListener);
|
dataModel.getUseMarketBasedPrice().addListener(useMarketBasedPriceListener);
|
||||||
volume.addListener(volumeStringListener);
|
volume.addListener(volumeStringListener);
|
||||||
buyerSecurityDeposit.addListener(securityDepositStringListener);
|
securityDeposit.addListener(securityDepositStringListener);
|
||||||
|
|
||||||
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding
|
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding
|
||||||
dataModel.getAmount().addListener(amountListener);
|
dataModel.getAmount().addListener(amountListener);
|
||||||
dataModel.getMinAmount().addListener(minAmountListener);
|
dataModel.getMinAmount().addListener(minAmountListener);
|
||||||
dataModel.getPrice().addListener(priceListener);
|
dataModel.getPrice().addListener(priceListener);
|
||||||
dataModel.getVolume().addListener(volumeListener);
|
dataModel.getVolume().addListener(volumeListener);
|
||||||
dataModel.getBuyerSecurityDepositPct().addListener(securityDepositAsDoubleListener);
|
dataModel.getSecurityDepositPct().addListener(securityDepositAsDoubleListener);
|
||||||
|
dataModel.getBuyerAsTakerWithoutDeposit().addListener(buyerAsTakerWithoutDepositListener);
|
||||||
|
|
||||||
// dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener);
|
// dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener);
|
||||||
dataModel.getIsXmrWalletFunded().addListener(isWalletFundedListener);
|
dataModel.getIsXmrWalletFunded().addListener(isWalletFundedListener);
|
||||||
@ -547,14 +561,15 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
marketPriceMargin.removeListener(marketPriceMarginStringListener);
|
marketPriceMargin.removeListener(marketPriceMarginStringListener);
|
||||||
dataModel.getUseMarketBasedPrice().removeListener(useMarketBasedPriceListener);
|
dataModel.getUseMarketBasedPrice().removeListener(useMarketBasedPriceListener);
|
||||||
volume.removeListener(volumeStringListener);
|
volume.removeListener(volumeStringListener);
|
||||||
buyerSecurityDeposit.removeListener(securityDepositStringListener);
|
securityDeposit.removeListener(securityDepositStringListener);
|
||||||
|
|
||||||
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding
|
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding
|
||||||
dataModel.getAmount().removeListener(amountListener);
|
dataModel.getAmount().removeListener(amountListener);
|
||||||
dataModel.getMinAmount().removeListener(minAmountListener);
|
dataModel.getMinAmount().removeListener(minAmountListener);
|
||||||
dataModel.getPrice().removeListener(priceListener);
|
dataModel.getPrice().removeListener(priceListener);
|
||||||
dataModel.getVolume().removeListener(volumeListener);
|
dataModel.getVolume().removeListener(volumeListener);
|
||||||
dataModel.getBuyerSecurityDepositPct().removeListener(securityDepositAsDoubleListener);
|
dataModel.getSecurityDepositPct().removeListener(securityDepositAsDoubleListener);
|
||||||
|
dataModel.getBuyerAsTakerWithoutDeposit().removeListener(buyerAsTakerWithoutDepositListener);
|
||||||
|
|
||||||
//dataModel.feeFromFundingTxProperty.removeListener(feeFromFundingTxListener);
|
//dataModel.feeFromFundingTxProperty.removeListener(feeFromFundingTxListener);
|
||||||
dataModel.getIsXmrWalletFunded().removeListener(isWalletFundedListener);
|
dataModel.getIsXmrWalletFunded().removeListener(isWalletFundedListener);
|
||||||
@ -593,9 +608,9 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
}
|
}
|
||||||
|
|
||||||
securityDepositValidator.setPaymentAccount(dataModel.paymentAccount);
|
securityDepositValidator.setPaymentAccount(dataModel.paymentAccount);
|
||||||
validateAndSetBuyerSecurityDepositToModel();
|
validateAndSetSecurityDepositToModel();
|
||||||
buyerSecurityDeposit.set(FormattingUtils.formatToPercent(dataModel.getBuyerSecurityDepositPct().get()));
|
securityDeposit.set(FormattingUtils.formatToPercent(dataModel.getSecurityDepositPct().get()));
|
||||||
buyerSecurityDepositLabel.set(getSecurityDepositLabel());
|
securityDepositLabel.set(getSecurityDepositLabel());
|
||||||
|
|
||||||
applyMakerFee();
|
applyMakerFee();
|
||||||
return result;
|
return result;
|
||||||
@ -932,14 +947,14 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFocusOutBuyerSecurityDepositTextField(boolean oldValue, boolean newValue) {
|
void onFocusOutSecurityDepositTextField(boolean oldValue, boolean newValue) {
|
||||||
if (oldValue && !newValue) {
|
if (oldValue && !newValue) {
|
||||||
InputValidator.ValidationResult result = securityDepositValidator.validate(buyerSecurityDeposit.get());
|
InputValidator.ValidationResult result = securityDepositValidator.validate(securityDeposit.get());
|
||||||
buyerSecurityDepositValidationResult.set(result);
|
securityDepositValidationResult.set(result);
|
||||||
if (result.isValid) {
|
if (result.isValid) {
|
||||||
double defaultSecurityDeposit = Restrictions.getDefaultBuyerSecurityDepositAsPercent();
|
double defaultSecurityDeposit = Restrictions.getDefaultSecurityDepositAsPercent();
|
||||||
String key = "buyerSecurityDepositIsLowerAsDefault";
|
String key = "buyerSecurityDepositIsLowerAsDefault";
|
||||||
double depositAsDouble = ParsingUtils.parsePercentStringToDouble(buyerSecurityDeposit.get());
|
double depositAsDouble = ParsingUtils.parsePercentStringToDouble(securityDeposit.get());
|
||||||
if (preferences.showAgain(key) && depositAsDouble < defaultSecurityDeposit) {
|
if (preferences.showAgain(key) && depositAsDouble < defaultSecurityDeposit) {
|
||||||
String postfix = dataModel.isBuyOffer() ?
|
String postfix = dataModel.isBuyOffer() ?
|
||||||
Res.get("createOffer.tooLowSecDeposit.makerIsBuyer") :
|
Res.get("createOffer.tooLowSecDeposit.makerIsBuyer") :
|
||||||
@ -950,26 +965,26 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
.width(800)
|
.width(800)
|
||||||
.actionButtonText(Res.get("createOffer.resetToDefault"))
|
.actionButtonText(Res.get("createOffer.resetToDefault"))
|
||||||
.onAction(() -> {
|
.onAction(() -> {
|
||||||
dataModel.setBuyerSecurityDeposit(defaultSecurityDeposit);
|
dataModel.setSecurityDepositPct(defaultSecurityDeposit);
|
||||||
ignoreSecurityDepositStringListener = true;
|
ignoreSecurityDepositStringListener = true;
|
||||||
buyerSecurityDeposit.set(FormattingUtils.formatToPercent(dataModel.getBuyerSecurityDepositPct().get()));
|
securityDeposit.set(FormattingUtils.formatToPercent(dataModel.getSecurityDepositPct().get()));
|
||||||
ignoreSecurityDepositStringListener = false;
|
ignoreSecurityDepositStringListener = false;
|
||||||
})
|
})
|
||||||
.closeButtonText(Res.get("createOffer.useLowerValue"))
|
.closeButtonText(Res.get("createOffer.useLowerValue"))
|
||||||
.onClose(this::applyBuyerSecurityDepositOnFocusOut)
|
.onClose(this::applySecurityDepositOnFocusOut)
|
||||||
.dontShowAgainId(key)
|
.dontShowAgainId(key)
|
||||||
.show();
|
.show();
|
||||||
} else {
|
} else {
|
||||||
applyBuyerSecurityDepositOnFocusOut();
|
applySecurityDepositOnFocusOut();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyBuyerSecurityDepositOnFocusOut() {
|
private void applySecurityDepositOnFocusOut() {
|
||||||
setBuyerSecurityDepositToModel();
|
setSecurityDepositToModel();
|
||||||
ignoreSecurityDepositStringListener = true;
|
ignoreSecurityDepositStringListener = true;
|
||||||
buyerSecurityDeposit.set(FormattingUtils.formatToPercent(dataModel.getBuyerSecurityDepositPct().get()));
|
securityDeposit.set(FormattingUtils.formatToPercent(dataModel.getSecurityDepositPct().get()));
|
||||||
ignoreSecurityDepositStringListener = false;
|
ignoreSecurityDepositStringListener = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1024,13 +1039,15 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getSecurityDepositLabel() {
|
public String getSecurityDepositLabel() {
|
||||||
return Preferences.USE_SYMMETRIC_SECURITY_DEPOSIT ? Res.get("createOffer.setDepositForBothTraders") :
|
return dataModel.buyerAsTakerWithoutDeposit.get() && dataModel.isSellOffer() ? Res.get("createOffer.myDeposit") :
|
||||||
|
Preferences.USE_SYMMETRIC_SECURITY_DEPOSIT ? Res.get("createOffer.setDepositForBothTraders") :
|
||||||
dataModel.isBuyOffer() ? Res.get("createOffer.setDepositAsBuyer") : Res.get("createOffer.setDeposit");
|
dataModel.isBuyOffer() ? Res.get("createOffer.setDepositAsBuyer") : Res.get("createOffer.setDeposit");
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSecurityDepositPopOverLabel(String depositInBTC) {
|
public String getSecurityDepositPopOverLabel(String depositInXMR) {
|
||||||
return dataModel.isBuyOffer() ? Res.get("createOffer.securityDepositInfoAsBuyer", depositInBTC) :
|
return dataModel.buyerAsTakerWithoutDeposit.get() && dataModel.isSellOffer() ? Res.get("createOffer.myDepositInfo", depositInXMR) :
|
||||||
Res.get("createOffer.securityDepositInfo", depositInBTC);
|
dataModel.isBuyOffer() ? Res.get("createOffer.securityDepositInfoAsBuyer", depositInXMR) :
|
||||||
|
Res.get("createOffer.securityDepositInfo", depositInXMR);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSecurityDepositInfo() {
|
public String getSecurityDepositInfo() {
|
||||||
@ -1193,19 +1210,19 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setBuyerSecurityDepositToModel() {
|
private void setSecurityDepositToModel() {
|
||||||
if (buyerSecurityDeposit.get() != null && !buyerSecurityDeposit.get().isEmpty()) {
|
if (!(dataModel.buyerAsTakerWithoutDeposit.get() && dataModel.isSellOffer()) && securityDeposit.get() != null && !securityDeposit.get().isEmpty()) {
|
||||||
dataModel.setBuyerSecurityDeposit(ParsingUtils.parsePercentStringToDouble(buyerSecurityDeposit.get()));
|
dataModel.setSecurityDepositPct(ParsingUtils.parsePercentStringToDouble(securityDeposit.get()));
|
||||||
} else {
|
} else {
|
||||||
dataModel.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent());
|
dataModel.setSecurityDepositPct(Restrictions.getDefaultSecurityDepositAsPercent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateAndSetBuyerSecurityDepositToModel() {
|
private void validateAndSetSecurityDepositToModel() {
|
||||||
// If the security deposit in the model is not valid percent
|
// If the security deposit in the model is not valid percent
|
||||||
String value = FormattingUtils.formatToPercent(dataModel.getBuyerSecurityDepositPct().get());
|
String value = FormattingUtils.formatToPercent(dataModel.getSecurityDepositPct().get());
|
||||||
if (!securityDepositValidator.validate(value).isValid) {
|
if (!securityDepositValidator.validate(value).isValid) {
|
||||||
dataModel.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent());
|
dataModel.setSecurityDepositPct(Restrictions.getDefaultSecurityDepositAsPercent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1263,15 +1280,17 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
isWaitingForFunds.set(!waitingForFundsText.get().isEmpty());
|
isWaitingForFunds.set(!waitingForFundsText.get().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateBuyerSecurityDeposit() {
|
private void updateSecurityDeposit() {
|
||||||
isMinBuyerSecurityDeposit.set(dataModel.isMinBuyerSecurityDeposit());
|
isMinSecurityDeposit.set(dataModel.isMinSecurityDeposit());
|
||||||
|
if (dataModel.isMinSecurityDeposit()) {
|
||||||
if (dataModel.isMinBuyerSecurityDeposit()) {
|
securityDepositLabel.set(Res.get("createOffer.minSecurityDepositUsed"));
|
||||||
buyerSecurityDepositLabel.set(Res.get("createOffer.minSecurityDepositUsed"));
|
securityDeposit.set(HavenoUtils.formatXmr(Restrictions.getMinSecurityDeposit()));
|
||||||
buyerSecurityDeposit.set(HavenoUtils.formatXmr(Restrictions.getMinBuyerSecurityDeposit()));
|
|
||||||
} else {
|
} else {
|
||||||
buyerSecurityDepositLabel.set(getSecurityDepositLabel());
|
securityDepositLabel.set(getSecurityDepositLabel());
|
||||||
buyerSecurityDeposit.set(FormattingUtils.formatToPercent(dataModel.getBuyerSecurityDepositPct().get()));
|
boolean hasBuyerAsTakerWithoutDeposit = dataModel.buyerAsTakerWithoutDeposit.get() && dataModel.isSellOffer();
|
||||||
|
securityDeposit.set(FormattingUtils.formatToPercent(hasBuyerAsTakerWithoutDeposit ?
|
||||||
|
Restrictions.getDefaultSecurityDepositAsPercent() : // use default percent if no deposit from buyer
|
||||||
|
dataModel.getSecurityDepositPct().get()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1293,8 +1312,8 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
}
|
}
|
||||||
|
|
||||||
// validating the percentage deposit value only makes sense if it is actually used
|
// validating the percentage deposit value only makes sense if it is actually used
|
||||||
if (!dataModel.isMinBuyerSecurityDeposit()) {
|
if (!dataModel.isMinSecurityDeposit()) {
|
||||||
inputDataValid = inputDataValid && securityDepositValidator.validate(buyerSecurityDeposit.get()).isValid;
|
inputDataValid = inputDataValid && securityDepositValidator.validate(securityDeposit.get()).isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
isNextButtonDisabled.set(!inputDataValid);
|
isNextButtonDisabled.set(!inputDataValid);
|
||||||
|
@ -87,4 +87,8 @@ public abstract class OfferDataModel extends ActivatableDataModel {
|
|||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasTotalToPay() {
|
||||||
|
return totalToPay.get() != null && totalToPay.get().compareTo(BigInteger.ZERO) > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package haveno.desktop.main.offer.offerbook;
|
package haveno.desktop.main.offer.offerbook;
|
||||||
|
|
||||||
|
import de.jensd.fx.fontawesome.AwesomeDude;
|
||||||
import de.jensd.fx.fontawesome.AwesomeIcon;
|
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||||
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon;
|
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon;
|
||||||
import haveno.common.UserThread;
|
import haveno.common.UserThread;
|
||||||
@ -44,7 +45,6 @@ import haveno.desktop.common.view.ActivatableViewAndModel;
|
|||||||
import haveno.desktop.components.AccountStatusTooltipLabel;
|
import haveno.desktop.components.AccountStatusTooltipLabel;
|
||||||
import haveno.desktop.components.AutoTooltipButton;
|
import haveno.desktop.components.AutoTooltipButton;
|
||||||
import haveno.desktop.components.AutoTooltipLabel;
|
import haveno.desktop.components.AutoTooltipLabel;
|
||||||
import haveno.desktop.components.AutoTooltipSlideToggleButton;
|
|
||||||
import haveno.desktop.components.AutoTooltipTableColumn;
|
import haveno.desktop.components.AutoTooltipTableColumn;
|
||||||
import haveno.desktop.components.AutoTooltipTextField;
|
import haveno.desktop.components.AutoTooltipTextField;
|
||||||
import haveno.desktop.components.AutocompleteComboBox;
|
import haveno.desktop.components.AutocompleteComboBox;
|
||||||
@ -83,6 +83,7 @@ import javafx.scene.control.TableCell;
|
|||||||
import javafx.scene.control.TableColumn;
|
import javafx.scene.control.TableColumn;
|
||||||
import javafx.scene.control.TableRow;
|
import javafx.scene.control.TableRow;
|
||||||
import javafx.scene.control.TableView;
|
import javafx.scene.control.TableView;
|
||||||
|
import javafx.scene.control.ToggleButton;
|
||||||
import javafx.scene.control.Tooltip;
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
@ -120,7 +121,8 @@ abstract public class OfferBookView<R extends GridPane, M extends OfferBookViewM
|
|||||||
private AutocompleteComboBox<PaymentMethod> paymentMethodComboBox;
|
private AutocompleteComboBox<PaymentMethod> paymentMethodComboBox;
|
||||||
private AutoTooltipButton createOfferButton;
|
private AutoTooltipButton createOfferButton;
|
||||||
private AutoTooltipTextField filterInputField;
|
private AutoTooltipTextField filterInputField;
|
||||||
private AutoTooltipSlideToggleButton matchingOffersToggle;
|
private ToggleButton matchingOffersToggleButton;
|
||||||
|
private ToggleButton noDepositOffersToggleButton;
|
||||||
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> amountColumn;
|
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> amountColumn;
|
||||||
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> volumeColumn;
|
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> volumeColumn;
|
||||||
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> marketColumn;
|
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> marketColumn;
|
||||||
@ -183,9 +185,17 @@ abstract public class OfferBookView<R extends GridPane, M extends OfferBookViewM
|
|||||||
paymentMethodComboBox.setCellFactory(GUIUtil.getPaymentMethodCellFactory());
|
paymentMethodComboBox.setCellFactory(GUIUtil.getPaymentMethodCellFactory());
|
||||||
paymentMethodComboBox.setPrefWidth(250);
|
paymentMethodComboBox.setPrefWidth(250);
|
||||||
|
|
||||||
matchingOffersToggle = new AutoTooltipSlideToggleButton();
|
matchingOffersToggleButton = AwesomeDude.createIconToggleButton(AwesomeIcon.USER, null, "1.3em", null);
|
||||||
matchingOffersToggle.setText(Res.get("offerbook.matchingOffers"));
|
matchingOffersToggleButton.getStyleClass().add("toggle-button-no-slider");
|
||||||
HBox.setMargin(matchingOffersToggle, new Insets(7, 0, -9, -15));
|
matchingOffersToggleButton.setPrefHeight(27);
|
||||||
|
Tooltip matchingOffersTooltip = new Tooltip(Res.get("offerbook.matchingOffers"));
|
||||||
|
Tooltip.install(matchingOffersToggleButton, matchingOffersTooltip);
|
||||||
|
|
||||||
|
noDepositOffersToggleButton = new ToggleButton(Res.get("offerbook.filterNoDeposit"));
|
||||||
|
noDepositOffersToggleButton.getStyleClass().add("toggle-button-no-slider");
|
||||||
|
noDepositOffersToggleButton.setPrefHeight(27);
|
||||||
|
Tooltip noDepositOffersTooltip = new Tooltip(Res.get("offerbook.noDepositOffers"));
|
||||||
|
Tooltip.install(noDepositOffersToggleButton, noDepositOffersTooltip);
|
||||||
|
|
||||||
createOfferButton = new AutoTooltipButton("");
|
createOfferButton = new AutoTooltipButton("");
|
||||||
createOfferButton.setMinHeight(40);
|
createOfferButton.setMinHeight(40);
|
||||||
@ -208,7 +218,7 @@ abstract public class OfferBookView<R extends GridPane, M extends OfferBookViewM
|
|||||||
filterInputField.setPromptText(Res.get("market.offerBook.filterPrompt"));
|
filterInputField.setPromptText(Res.get("market.offerBook.filterPrompt"));
|
||||||
|
|
||||||
offerToolsBox.getChildren().addAll(currencyBoxTuple.first, paymentBoxTuple.first,
|
offerToolsBox.getChildren().addAll(currencyBoxTuple.first, paymentBoxTuple.first,
|
||||||
filterBox, matchingOffersToggle, getSpacer(), createOfferButtonStack);
|
filterBox, matchingOffersToggleButton, noDepositOffersToggleButton, getSpacer(), createOfferButtonStack);
|
||||||
|
|
||||||
GridPane.setHgrow(offerToolsBox, Priority.ALWAYS);
|
GridPane.setHgrow(offerToolsBox, Priority.ALWAYS);
|
||||||
GridPane.setRowIndex(offerToolsBox, gridRow);
|
GridPane.setRowIndex(offerToolsBox, gridRow);
|
||||||
@ -346,9 +356,12 @@ abstract public class OfferBookView<R extends GridPane, M extends OfferBookViewM
|
|||||||
|
|
||||||
currencyComboBox.getEditor().setText(new CurrencyStringConverter(currencyComboBox).toString(currencyComboBox.getSelectionModel().getSelectedItem()));
|
currencyComboBox.getEditor().setText(new CurrencyStringConverter(currencyComboBox).toString(currencyComboBox.getSelectionModel().getSelectedItem()));
|
||||||
|
|
||||||
matchingOffersToggle.setSelected(model.useOffersMatchingMyAccountsFilter);
|
matchingOffersToggleButton.setSelected(model.useOffersMatchingMyAccountsFilter);
|
||||||
matchingOffersToggle.disableProperty().bind(model.disableMatchToggle);
|
matchingOffersToggleButton.disableProperty().bind(model.disableMatchToggle);
|
||||||
matchingOffersToggle.setOnAction(e -> model.onShowOffersMatchingMyAccounts(matchingOffersToggle.isSelected()));
|
matchingOffersToggleButton.setOnAction(e -> model.onShowOffersMatchingMyAccounts(matchingOffersToggleButton.isSelected()));
|
||||||
|
|
||||||
|
noDepositOffersToggleButton.setSelected(model.showPrivateOffers);
|
||||||
|
noDepositOffersToggleButton.setOnAction(e -> model.onShowPrivateOffers(noDepositOffersToggleButton.isSelected()));
|
||||||
|
|
||||||
model.getOfferList().comparatorProperty().bind(tableView.comparatorProperty());
|
model.getOfferList().comparatorProperty().bind(tableView.comparatorProperty());
|
||||||
|
|
||||||
@ -452,8 +465,10 @@ abstract public class OfferBookView<R extends GridPane, M extends OfferBookViewM
|
|||||||
@Override
|
@Override
|
||||||
protected void deactivate() {
|
protected void deactivate() {
|
||||||
createOfferButton.setOnAction(null);
|
createOfferButton.setOnAction(null);
|
||||||
matchingOffersToggle.setOnAction(null);
|
matchingOffersToggleButton.setOnAction(null);
|
||||||
matchingOffersToggle.disableProperty().unbind();
|
matchingOffersToggleButton.disableProperty().unbind();
|
||||||
|
noDepositOffersToggleButton.setOnAction(null);
|
||||||
|
noDepositOffersToggleButton.disableProperty().unbind();
|
||||||
model.getOfferList().comparatorProperty().unbind();
|
model.getOfferList().comparatorProperty().unbind();
|
||||||
|
|
||||||
volumeColumn.sortableProperty().unbind();
|
volumeColumn.sortableProperty().unbind();
|
||||||
@ -574,6 +589,10 @@ abstract public class OfferBookView<R extends GridPane, M extends OfferBookViewM
|
|||||||
iconView.setId(direction == OfferDirection.SELL ? "image-sell-white" : "image-buy-white");
|
iconView.setId(direction == OfferDirection.SELL ? "image-sell-white" : "image-buy-white");
|
||||||
createOfferButton.setId(direction == OfferDirection.SELL ? "sell-button-big" : "buy-button-big");
|
createOfferButton.setId(direction == OfferDirection.SELL ? "sell-button-big" : "buy-button-big");
|
||||||
avatarColumn.setTitle(direction == OfferDirection.SELL ? Res.get("shared.buyerUpperCase") : Res.get("shared.sellerUpperCase"));
|
avatarColumn.setTitle(direction == OfferDirection.SELL ? Res.get("shared.buyerUpperCase") : Res.get("shared.sellerUpperCase"));
|
||||||
|
if (direction == OfferDirection.SELL) {
|
||||||
|
noDepositOffersToggleButton.setVisible(false);
|
||||||
|
noDepositOffersToggleButton.setManaged(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOfferActionHandler(OfferView.OfferActionHandler offerActionHandler) {
|
public void setOfferActionHandler(OfferView.OfferActionHandler offerActionHandler) {
|
||||||
@ -658,7 +677,7 @@ abstract public class OfferBookView<R extends GridPane, M extends OfferBookViewM
|
|||||||
Optional<PaymentAccount> account = model.getMostMaturePaymentAccountForOffer(offer);
|
Optional<PaymentAccount> account = model.getMostMaturePaymentAccountForOffer(offer);
|
||||||
if (account.isPresent()) {
|
if (account.isPresent()) {
|
||||||
long tradeLimit = model.accountAgeWitnessService.getMyTradeLimit(account.get(),
|
long tradeLimit = model.accountAgeWitnessService.getMyTradeLimit(account.get(),
|
||||||
offer.getCurrencyCode(), offer.getMirroredDirection());
|
offer.getCurrencyCode(), offer.getMirroredDirection(), offer.hasBuyerAsTakerWithoutDeposit());
|
||||||
new Popup()
|
new Popup()
|
||||||
.warning(Res.get("popup.warning.tradeLimitDueAccountAgeRestriction.buyer",
|
.warning(Res.get("popup.warning.tradeLimitDueAccountAgeRestriction.buyer",
|
||||||
HavenoUtils.formatXmr(tradeLimit, true),
|
HavenoUtils.formatXmr(tradeLimit, true),
|
||||||
@ -1123,7 +1142,10 @@ abstract public class OfferBookView<R extends GridPane, M extends OfferBookViewM
|
|||||||
button2.setVisible(true);
|
button2.setVisible(true);
|
||||||
} else {
|
} else {
|
||||||
boolean isSellOffer = OfferViewUtil.isShownAsSellOffer(offer);
|
boolean isSellOffer = OfferViewUtil.isShownAsSellOffer(offer);
|
||||||
iconView.setId(isSellOffer ? "image-buy-white" : "image-sell-white");
|
boolean isPrivateOffer = offer.isPrivateOffer();
|
||||||
|
iconView.setId(isPrivateOffer ? "image-lock2x" : isSellOffer ? "image-buy-white" : "image-sell-white");
|
||||||
|
iconView.setFitHeight(16);
|
||||||
|
iconView.setFitWidth(16);
|
||||||
button.setId(isSellOffer ? "buy-button" : "sell-button");
|
button.setId(isSellOffer ? "buy-button" : "sell-button");
|
||||||
button.setStyle("-fx-text-fill: white");
|
button.setStyle("-fx-text-fill: white");
|
||||||
title = Res.get("offerbook.takeOffer");
|
title = Res.get("offerbook.takeOffer");
|
||||||
|
@ -130,6 +130,7 @@ abstract class OfferBookViewModel extends ActivatableViewModel {
|
|||||||
final IntegerProperty maxPlacesForMarketPriceMargin = new SimpleIntegerProperty();
|
final IntegerProperty maxPlacesForMarketPriceMargin = new SimpleIntegerProperty();
|
||||||
boolean showAllPaymentMethods = true;
|
boolean showAllPaymentMethods = true;
|
||||||
boolean useOffersMatchingMyAccountsFilter;
|
boolean useOffersMatchingMyAccountsFilter;
|
||||||
|
boolean showPrivateOffers;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -213,6 +214,7 @@ abstract class OfferBookViewModel extends ActivatableViewModel {
|
|||||||
disableMatchToggle.set(user.getPaymentAccounts() == null || user.getPaymentAccounts().isEmpty());
|
disableMatchToggle.set(user.getPaymentAccounts() == null || user.getPaymentAccounts().isEmpty());
|
||||||
}
|
}
|
||||||
useOffersMatchingMyAccountsFilter = !disableMatchToggle.get() && isShowOffersMatchingMyAccounts();
|
useOffersMatchingMyAccountsFilter = !disableMatchToggle.get() && isShowOffersMatchingMyAccounts();
|
||||||
|
showPrivateOffers = preferences.isShowPrivateOffers();
|
||||||
|
|
||||||
fillCurrencies();
|
fillCurrencies();
|
||||||
updateSelectedTradeCurrency();
|
updateSelectedTradeCurrency();
|
||||||
@ -307,6 +309,12 @@ abstract class OfferBookViewModel extends ActivatableViewModel {
|
|||||||
filterOffers();
|
filterOffers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onShowPrivateOffers(boolean isSelected) {
|
||||||
|
showPrivateOffers = isSelected;
|
||||||
|
preferences.setShowPrivateOffers(isSelected);
|
||||||
|
filterOffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Getters
|
// Getters
|
||||||
@ -571,6 +579,8 @@ abstract class OfferBookViewModel extends ActivatableViewModel {
|
|||||||
getCurrencyAndMethodPredicate(direction, selectedTradeCurrency).and(getOffersMatchingMyAccountsPredicate()) :
|
getCurrencyAndMethodPredicate(direction, selectedTradeCurrency).and(getOffersMatchingMyAccountsPredicate()) :
|
||||||
getCurrencyAndMethodPredicate(direction, selectedTradeCurrency);
|
getCurrencyAndMethodPredicate(direction, selectedTradeCurrency);
|
||||||
|
|
||||||
|
predicate = predicate.and(offerBookListItem -> offerBookListItem.getOffer().isPrivateOffer() == showPrivateOffers);
|
||||||
|
|
||||||
if (!filterText.isEmpty()) {
|
if (!filterText.isEmpty()) {
|
||||||
|
|
||||||
// filter node address
|
// filter node address
|
||||||
|
@ -341,7 +341,7 @@ class TakeOfferDataModel extends OfferDataModel {
|
|||||||
long getMaxTradeLimit() {
|
long getMaxTradeLimit() {
|
||||||
if (paymentAccount != null) {
|
if (paymentAccount != null) {
|
||||||
return accountAgeWitnessService.getMyTradeLimit(paymentAccount, getCurrencyCode(),
|
return accountAgeWitnessService.getMyTradeLimit(paymentAccount, getCurrencyCode(),
|
||||||
offer.getMirroredDirection());
|
offer.getMirroredDirection(), offer.hasBuyerAsTakerWithoutDeposit());
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -123,6 +123,8 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||||||
|
|
||||||
private ScrollPane scrollPane;
|
private ScrollPane scrollPane;
|
||||||
private GridPane gridPane;
|
private GridPane gridPane;
|
||||||
|
private TitledGroupBg noFundingRequiredTitledGroupBg;
|
||||||
|
private Label noFundingRequiredLabel;
|
||||||
private TitledGroupBg payFundsTitledGroupBg;
|
private TitledGroupBg payFundsTitledGroupBg;
|
||||||
private TitledGroupBg advancedOptionsGroup;
|
private TitledGroupBg advancedOptionsGroup;
|
||||||
private VBox priceAsPercentageInputBox, amountRangeBox;
|
private VBox priceAsPercentageInputBox, amountRangeBox;
|
||||||
@ -452,7 +454,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||||||
|
|
||||||
balanceTextField.setTargetAmount(model.dataModel.getTotalToPay().get());
|
balanceTextField.setTargetAmount(model.dataModel.getTotalToPay().get());
|
||||||
|
|
||||||
if (!DevEnv.isDevMode()) {
|
if (!DevEnv.isDevMode() && model.dataModel.hasTotalToPay()) {
|
||||||
String tradeAmountText = model.isSeller() ? Res.get("takeOffer.takeOfferFundWalletInfo.tradeAmount", model.getTradeAmount()) : "";
|
String tradeAmountText = model.isSeller() ? Res.get("takeOffer.takeOfferFundWalletInfo.tradeAmount", model.getTradeAmount()) : "";
|
||||||
String message = Res.get("takeOffer.takeOfferFundWalletInfo.msg",
|
String message = Res.get("takeOffer.takeOfferFundWalletInfo.msg",
|
||||||
model.getTotalToPayInfo(),
|
model.getTotalToPayInfo(),
|
||||||
@ -472,17 +474,22 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||||||
// temporarily disabled due to high CPU usage (per issue #4649)
|
// temporarily disabled due to high CPU usage (per issue #4649)
|
||||||
//waitingForFundsBusyAnimation.play();
|
//waitingForFundsBusyAnimation.play();
|
||||||
|
|
||||||
|
if (model.getOffer().hasBuyerAsTakerWithoutDeposit()) {
|
||||||
|
noFundingRequiredTitledGroupBg.setVisible(true);
|
||||||
|
noFundingRequiredLabel.setVisible(true);
|
||||||
|
} else {
|
||||||
payFundsTitledGroupBg.setVisible(true);
|
payFundsTitledGroupBg.setVisible(true);
|
||||||
totalToPayTextField.setVisible(true);
|
totalToPayTextField.setVisible(true);
|
||||||
addressTextField.setVisible(true);
|
addressTextField.setVisible(true);
|
||||||
qrCodeImageView.setVisible(true);
|
qrCodeImageView.setVisible(true);
|
||||||
balanceTextField.setVisible(true);
|
balanceTextField.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
totalToPayTextField.setFundsStructure(Res.get("takeOffer.fundsBox.fundsStructure",
|
totalToPayTextField.setFundsStructure(Res.get("takeOffer.fundsBox.fundsStructure",
|
||||||
model.getSecurityDepositWithCode(), model.getTakerFeePercentage()));
|
model.getSecurityDepositWithCode(), model.getTakerFeePercentage()));
|
||||||
totalToPayTextField.setContentForInfoPopOver(createInfoPopover());
|
totalToPayTextField.setContentForInfoPopOver(createInfoPopover());
|
||||||
|
|
||||||
if (model.dataModel.getIsXmrWalletFunded().get()) {
|
if (model.dataModel.getIsXmrWalletFunded().get() && model.dataModel.hasTotalToPay()) {
|
||||||
if (walletFundedNotification == null) {
|
if (walletFundedNotification == null) {
|
||||||
walletFundedNotification = new Notification()
|
walletFundedNotification = new Notification()
|
||||||
.headLine(Res.get("notification.walletUpdate.headline"))
|
.headLine(Res.get("notification.walletUpdate.headline"))
|
||||||
@ -847,7 +854,24 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void addFundingGroup() {
|
private void addFundingGroup() {
|
||||||
// don't increase gridRow as we removed button when this gets visible
|
|
||||||
|
// no funding required title
|
||||||
|
noFundingRequiredTitledGroupBg = addTitledGroupBg(gridPane, gridRow, 3,
|
||||||
|
Res.get("takeOffer.fundsBox.noFundingRequiredTitle"), Layout.COMPACT_GROUP_DISTANCE);
|
||||||
|
noFundingRequiredTitledGroupBg.getStyleClass().add("last");
|
||||||
|
GridPane.setColumnSpan(noFundingRequiredTitledGroupBg, 2);
|
||||||
|
noFundingRequiredTitledGroupBg.setVisible(false);
|
||||||
|
|
||||||
|
// no funding required description
|
||||||
|
noFundingRequiredLabel = new AutoTooltipLabel(Res.get("takeOffer.fundsBox.noFundingRequiredDescription"));
|
||||||
|
noFundingRequiredLabel.setVisible(false);
|
||||||
|
//GridPane.setRowSpan(noFundingRequiredLabel, 1);
|
||||||
|
GridPane.setRowIndex(noFundingRequiredLabel, gridRow);
|
||||||
|
noFundingRequiredLabel.setPadding(new Insets(Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE, 0, 0, 0));
|
||||||
|
GridPane.setHalignment(noFundingRequiredLabel, HPos.LEFT);
|
||||||
|
gridPane.getChildren().add(noFundingRequiredLabel);
|
||||||
|
|
||||||
|
// funding title
|
||||||
payFundsTitledGroupBg = addTitledGroupBg(gridPane, gridRow, 3,
|
payFundsTitledGroupBg = addTitledGroupBg(gridPane, gridRow, 3,
|
||||||
Res.get("takeOffer.fundsBox.title"), Layout.COMPACT_GROUP_DISTANCE);
|
Res.get("takeOffer.fundsBox.title"), Layout.COMPACT_GROUP_DISTANCE);
|
||||||
payFundsTitledGroupBg.getStyleClass().add("last");
|
payFundsTitledGroupBg.getStyleClass().add("last");
|
||||||
@ -937,7 +961,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||||||
|
|
||||||
cancelButton2.setOnAction(e -> {
|
cancelButton2.setOnAction(e -> {
|
||||||
String key = "CreateOfferCancelAndFunded";
|
String key = "CreateOfferCancelAndFunded";
|
||||||
if (model.dataModel.getIsXmrWalletFunded().get() &&
|
if (model.dataModel.getIsXmrWalletFunded().get() && model.dataModel.hasTotalToPay() &&
|
||||||
model.dataModel.preferences.showAgain(key)) {
|
model.dataModel.preferences.showAgain(key)) {
|
||||||
new Popup().backgroundInfo(Res.get("takeOffer.alreadyFunded.askCancel"))
|
new Popup().backgroundInfo(Res.get("takeOffer.alreadyFunded.askCancel"))
|
||||||
.closeButtonText(Res.get("shared.no"))
|
.closeButtonText(Res.get("shared.no"))
|
||||||
|
@ -0,0 +1,245 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Haveno.
|
||||||
|
*
|
||||||
|
* Haveno 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.
|
||||||
|
*
|
||||||
|
* Haveno 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 Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package haveno.desktop.main.overlays.editor;
|
||||||
|
|
||||||
|
import haveno.common.util.Utilities;
|
||||||
|
import haveno.core.locale.GlobalSettings;
|
||||||
|
import haveno.desktop.components.InputTextField;
|
||||||
|
import haveno.desktop.main.overlays.Overlay;
|
||||||
|
import haveno.desktop.util.FormBuilder;
|
||||||
|
import javafx.animation.Interpolator;
|
||||||
|
import javafx.animation.KeyFrame;
|
||||||
|
import javafx.animation.KeyValue;
|
||||||
|
import javafx.animation.Timeline;
|
||||||
|
import javafx.beans.value.ChangeListener;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
|
import javafx.geometry.HPos;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.Camera;
|
||||||
|
import javafx.scene.PerspectiveCamera;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.input.KeyEvent;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.transform.Rotate;
|
||||||
|
import javafx.stage.Modality;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||||
|
|
||||||
|
import static haveno.desktop.util.FormBuilder.addInputTextField;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class PasswordPopup extends Overlay<PasswordPopup> {
|
||||||
|
private InputTextField inputTextField;
|
||||||
|
private static PasswordPopup INSTANCE;
|
||||||
|
private Consumer<String> actionHandler;
|
||||||
|
private ChangeListener<Boolean> focusListener;
|
||||||
|
private EventHandler<KeyEvent> keyEventEventHandler;
|
||||||
|
|
||||||
|
public PasswordPopup() {
|
||||||
|
width = 600;
|
||||||
|
type = Type.Confirmation;
|
||||||
|
if (INSTANCE != null)
|
||||||
|
INSTANCE.hide();
|
||||||
|
INSTANCE = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PasswordPopup onAction(Consumer<String> confirmHandler) {
|
||||||
|
this.actionHandler = confirmHandler;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void show() {
|
||||||
|
actionButtonText("CONFIRM");
|
||||||
|
createGridPane();
|
||||||
|
addHeadLine();
|
||||||
|
addContent();
|
||||||
|
addButtons();
|
||||||
|
applyStyles();
|
||||||
|
onShow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onShow() {
|
||||||
|
super.display();
|
||||||
|
|
||||||
|
if (stage != null) {
|
||||||
|
focusListener = (observable, oldValue, newValue) -> {
|
||||||
|
if (!newValue)
|
||||||
|
hide();
|
||||||
|
};
|
||||||
|
stage.focusedProperty().addListener(focusListener);
|
||||||
|
|
||||||
|
Scene scene = stage.getScene();
|
||||||
|
if (scene != null)
|
||||||
|
scene.addEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void hide() {
|
||||||
|
animateHide();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onHidden() {
|
||||||
|
INSTANCE = null;
|
||||||
|
|
||||||
|
if (stage != null) {
|
||||||
|
if (focusListener != null)
|
||||||
|
stage.focusedProperty().removeListener(focusListener);
|
||||||
|
|
||||||
|
Scene scene = stage.getScene();
|
||||||
|
if (scene != null)
|
||||||
|
scene.removeEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addContent() {
|
||||||
|
gridPane.setPadding(new Insets(64));
|
||||||
|
|
||||||
|
inputTextField = addInputTextField(gridPane, ++rowIndex, null, -10d);
|
||||||
|
GridPane.setColumnSpan(inputTextField, 2);
|
||||||
|
inputTextField.requestFocus();
|
||||||
|
|
||||||
|
keyEventEventHandler = event -> {
|
||||||
|
if (Utilities.isAltOrCtrlPressed(KeyCode.R, event)) {
|
||||||
|
doClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addHeadLine() {
|
||||||
|
super.addHeadLine();
|
||||||
|
GridPane.setHalignment(headLineLabel, HPos.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setupKeyHandler(Scene scene) {
|
||||||
|
scene.setOnKeyPressed(e -> {
|
||||||
|
if (e.getCode() == KeyCode.ESCAPE) {
|
||||||
|
e.consume();
|
||||||
|
doClose();
|
||||||
|
}
|
||||||
|
if (e.getCode() == KeyCode.ENTER) {
|
||||||
|
e.consume();
|
||||||
|
apply();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void animateHide(Runnable onFinishedHandler) {
|
||||||
|
if (GlobalSettings.getUseAnimations()) {
|
||||||
|
double duration = getDuration(300);
|
||||||
|
Interpolator interpolator = Interpolator.SPLINE(0.25, 0.1, 0.25, 1);
|
||||||
|
|
||||||
|
gridPane.setRotationAxis(Rotate.X_AXIS);
|
||||||
|
Camera camera = gridPane.getScene().getCamera();
|
||||||
|
gridPane.getScene().setCamera(new PerspectiveCamera());
|
||||||
|
|
||||||
|
Timeline timeline = new Timeline();
|
||||||
|
ObservableList<KeyFrame> keyFrames = timeline.getKeyFrames();
|
||||||
|
keyFrames.add(new KeyFrame(Duration.millis(0),
|
||||||
|
new KeyValue(gridPane.rotateProperty(), 0, interpolator),
|
||||||
|
new KeyValue(gridPane.opacityProperty(), 1, interpolator)
|
||||||
|
));
|
||||||
|
keyFrames.add(new KeyFrame(Duration.millis(duration),
|
||||||
|
new KeyValue(gridPane.rotateProperty(), -90, interpolator),
|
||||||
|
new KeyValue(gridPane.opacityProperty(), 0, interpolator)
|
||||||
|
));
|
||||||
|
timeline.setOnFinished(event -> {
|
||||||
|
gridPane.setRotate(0);
|
||||||
|
gridPane.setRotationAxis(Rotate.Z_AXIS);
|
||||||
|
gridPane.getScene().setCamera(camera);
|
||||||
|
onFinishedHandler.run();
|
||||||
|
});
|
||||||
|
timeline.play();
|
||||||
|
} else {
|
||||||
|
onFinishedHandler.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void animateDisplay() {
|
||||||
|
if (GlobalSettings.getUseAnimations()) {
|
||||||
|
double startY = -160;
|
||||||
|
double duration = getDuration(400);
|
||||||
|
Interpolator interpolator = Interpolator.SPLINE(0.25, 0.1, 0.25, 1);
|
||||||
|
Timeline timeline = new Timeline();
|
||||||
|
ObservableList<KeyFrame> keyFrames = timeline.getKeyFrames();
|
||||||
|
keyFrames.add(new KeyFrame(Duration.millis(0),
|
||||||
|
new KeyValue(gridPane.opacityProperty(), 0, interpolator),
|
||||||
|
new KeyValue(gridPane.translateYProperty(), startY, interpolator)
|
||||||
|
));
|
||||||
|
|
||||||
|
keyFrames.add(new KeyFrame(Duration.millis(duration),
|
||||||
|
new KeyValue(gridPane.opacityProperty(), 1, interpolator),
|
||||||
|
new KeyValue(gridPane.translateYProperty(), 0, interpolator)
|
||||||
|
));
|
||||||
|
|
||||||
|
timeline.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void createGridPane() {
|
||||||
|
super.createGridPane();
|
||||||
|
gridPane.setPadding(new Insets(15, 15, 30, 30));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addButtons() {
|
||||||
|
buttonDistance = 10;
|
||||||
|
super.addButtons();
|
||||||
|
|
||||||
|
actionButton.setOnAction(event -> apply());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void apply() {
|
||||||
|
hide();
|
||||||
|
if (actionHandler != null && inputTextField != null)
|
||||||
|
actionHandler.accept(inputTextField.getText());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void applyStyles() {
|
||||||
|
super.applyStyles();
|
||||||
|
FormBuilder.getIconForLabel(AwesomeIcon.LOCK, headlineIcon, "1.5em");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setModality() {
|
||||||
|
stage.initOwner(owner.getScene().getWindow());
|
||||||
|
stage.initModality(Modality.NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addEffectToBackground() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void removeEffectFromBackground() {
|
||||||
|
}
|
||||||
|
}
|
@ -141,7 +141,7 @@ public class ContractWindow extends Overlay<ContractWindow> {
|
|||||||
DisplayUtils.formatDateTime(offer.getDate()) + " / " + DisplayUtils.formatDateTime(dispute.getTradeDate()));
|
DisplayUtils.formatDateTime(offer.getDate()) + " / " + DisplayUtils.formatDateTime(dispute.getTradeDate()));
|
||||||
String currencyCode = offer.getCurrencyCode();
|
String currencyCode = offer.getCurrencyCode();
|
||||||
addConfirmationLabelTextField(gridPane, ++rowIndex, Res.get("shared.offerType"),
|
addConfirmationLabelTextField(gridPane, ++rowIndex, Res.get("shared.offerType"),
|
||||||
DisplayUtils.getDirectionBothSides(offer.getDirection()));
|
DisplayUtils.getDirectionBothSides(offer.getDirection(), offer.isPrivateOffer()));
|
||||||
addConfirmationLabelTextField(gridPane, ++rowIndex, Res.get("shared.tradePrice"),
|
addConfirmationLabelTextField(gridPane, ++rowIndex, Res.get("shared.tradePrice"),
|
||||||
FormattingUtils.formatPrice(contract.getPrice()));
|
FormattingUtils.formatPrice(contract.getPrice()));
|
||||||
addConfirmationLabelTextField(gridPane, ++rowIndex, Res.get("shared.tradeAmount"),
|
addConfirmationLabelTextField(gridPane, ++rowIndex, Res.get("shared.tradeAmount"),
|
||||||
|
@ -99,6 +99,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||||||
reasonWasOtherRadioButton, reasonWasBankRadioButton, reasonWasOptionTradeRadioButton,
|
reasonWasOtherRadioButton, reasonWasBankRadioButton, reasonWasOptionTradeRadioButton,
|
||||||
reasonWasSellerNotRespondingRadioButton, reasonWasWrongSenderAccountRadioButton,
|
reasonWasSellerNotRespondingRadioButton, reasonWasWrongSenderAccountRadioButton,
|
||||||
reasonWasPeerWasLateRadioButton, reasonWasTradeAlreadySettledRadioButton;
|
reasonWasPeerWasLateRadioButton, reasonWasTradeAlreadySettledRadioButton;
|
||||||
|
private CoreDisputesService.PayoutSuggestion payoutSuggestion;
|
||||||
|
|
||||||
// Dispute object of other trade peer. The dispute field is the one from which we opened the close dispute window.
|
// Dispute object of other trade peer. The dispute field is the one from which we opened the close dispute window.
|
||||||
private Optional<Dispute> peersDisputeOptional;
|
private Optional<Dispute> peersDisputeOptional;
|
||||||
@ -700,33 +701,28 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void applyPayoutAmountsToDisputeResult(Toggle selectedTradeAmountToggle) {
|
private void applyPayoutAmountsToDisputeResult(Toggle selectedTradeAmountToggle) {
|
||||||
CoreDisputesService.DisputePayout payout;
|
|
||||||
if (selectedTradeAmountToggle == buyerGetsTradeAmountRadioButton) {
|
if (selectedTradeAmountToggle == buyerGetsTradeAmountRadioButton) {
|
||||||
payout = CoreDisputesService.DisputePayout.BUYER_GETS_TRADE_AMOUNT;
|
payoutSuggestion = CoreDisputesService.PayoutSuggestion.BUYER_GETS_TRADE_AMOUNT;
|
||||||
disputeResult.setWinner(DisputeResult.Winner.BUYER);
|
disputeResult.setWinner(DisputeResult.Winner.BUYER);
|
||||||
} else if (selectedTradeAmountToggle == buyerGetsAllRadioButton) {
|
} else if (selectedTradeAmountToggle == buyerGetsAllRadioButton) {
|
||||||
payout = CoreDisputesService.DisputePayout.BUYER_GETS_ALL;
|
payoutSuggestion = CoreDisputesService.PayoutSuggestion.BUYER_GETS_ALL;
|
||||||
disputeResult.setWinner(DisputeResult.Winner.BUYER);
|
disputeResult.setWinner(DisputeResult.Winner.BUYER);
|
||||||
} else if (selectedTradeAmountToggle == sellerGetsTradeAmountRadioButton) {
|
} else if (selectedTradeAmountToggle == sellerGetsTradeAmountRadioButton) {
|
||||||
payout = CoreDisputesService.DisputePayout.SELLER_GETS_TRADE_AMOUNT;
|
payoutSuggestion = CoreDisputesService.PayoutSuggestion.SELLER_GETS_TRADE_AMOUNT;
|
||||||
disputeResult.setWinner(DisputeResult.Winner.SELLER);
|
disputeResult.setWinner(DisputeResult.Winner.SELLER);
|
||||||
} else if (selectedTradeAmountToggle == sellerGetsAllRadioButton) {
|
} else if (selectedTradeAmountToggle == sellerGetsAllRadioButton) {
|
||||||
payout = CoreDisputesService.DisputePayout.SELLER_GETS_ALL;
|
payoutSuggestion = CoreDisputesService.PayoutSuggestion.SELLER_GETS_ALL;
|
||||||
disputeResult.setWinner(DisputeResult.Winner.SELLER);
|
disputeResult.setWinner(DisputeResult.Winner.SELLER);
|
||||||
} else {
|
} else {
|
||||||
// should not happen
|
// should not happen
|
||||||
throw new IllegalStateException("Unknown radio button");
|
throw new IllegalStateException("Unknown radio button");
|
||||||
}
|
}
|
||||||
disputesService.applyPayoutAmountsToDisputeResult(payout, dispute, disputeResult, -1);
|
disputesService.applyPayoutAmountsToDisputeResult(payoutSuggestion, dispute, disputeResult, -1);
|
||||||
buyerPayoutAmountInputTextField.setText(HavenoUtils.formatXmr(disputeResult.getBuyerPayoutAmountBeforeCost()));
|
buyerPayoutAmountInputTextField.setText(HavenoUtils.formatXmr(disputeResult.getBuyerPayoutAmountBeforeCost()));
|
||||||
sellerPayoutAmountInputTextField.setText(HavenoUtils.formatXmr(disputeResult.getSellerPayoutAmountBeforeCost()));
|
sellerPayoutAmountInputTextField.setText(HavenoUtils.formatXmr(disputeResult.getSellerPayoutAmountBeforeCost()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyTradeAmountRadioButtonStates() {
|
private void applyTradeAmountRadioButtonStates() {
|
||||||
Contract contract = dispute.getContract();
|
|
||||||
BigInteger buyerSecurityDeposit = trade.getBuyer().getSecurityDeposit();
|
|
||||||
BigInteger sellerSecurityDeposit = trade.getSeller().getSecurityDeposit();
|
|
||||||
BigInteger tradeAmount = contract.getTradeAmount();
|
|
||||||
|
|
||||||
BigInteger buyerPayoutAmount = disputeResult.getBuyerPayoutAmountBeforeCost();
|
BigInteger buyerPayoutAmount = disputeResult.getBuyerPayoutAmountBeforeCost();
|
||||||
BigInteger sellerPayoutAmount = disputeResult.getSellerPayoutAmountBeforeCost();
|
BigInteger sellerPayoutAmount = disputeResult.getSellerPayoutAmountBeforeCost();
|
||||||
@ -734,20 +730,22 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||||||
buyerPayoutAmountInputTextField.setText(HavenoUtils.formatXmr(buyerPayoutAmount));
|
buyerPayoutAmountInputTextField.setText(HavenoUtils.formatXmr(buyerPayoutAmount));
|
||||||
sellerPayoutAmountInputTextField.setText(HavenoUtils.formatXmr(sellerPayoutAmount));
|
sellerPayoutAmountInputTextField.setText(HavenoUtils.formatXmr(sellerPayoutAmount));
|
||||||
|
|
||||||
if (buyerPayoutAmount.equals(tradeAmount.add(buyerSecurityDeposit)) &&
|
switch (payoutSuggestion) {
|
||||||
sellerPayoutAmount.equals(sellerSecurityDeposit)) {
|
case BUYER_GETS_TRADE_AMOUNT:
|
||||||
buyerGetsTradeAmountRadioButton.setSelected(true);
|
buyerGetsTradeAmountRadioButton.setSelected(true);
|
||||||
} else if (buyerPayoutAmount.equals(tradeAmount.add(buyerSecurityDeposit).add(sellerSecurityDeposit)) &&
|
break;
|
||||||
sellerPayoutAmount.equals(BigInteger.ZERO)) {
|
case BUYER_GETS_ALL:
|
||||||
buyerGetsAllRadioButton.setSelected(true);
|
buyerGetsAllRadioButton.setSelected(true);
|
||||||
} else if (sellerPayoutAmount.equals(tradeAmount.add(sellerSecurityDeposit))
|
break;
|
||||||
&& buyerPayoutAmount.equals(buyerSecurityDeposit)) {
|
case SELLER_GETS_TRADE_AMOUNT:
|
||||||
sellerGetsTradeAmountRadioButton.setSelected(true);
|
sellerGetsTradeAmountRadioButton.setSelected(true);
|
||||||
} else if (sellerPayoutAmount.equals(tradeAmount.add(buyerSecurityDeposit).add(sellerSecurityDeposit))
|
break;
|
||||||
&& buyerPayoutAmount.equals(BigInteger.ZERO)) {
|
case SELLER_GETS_ALL:
|
||||||
sellerGetsAllRadioButton.setSelected(true);
|
sellerGetsAllRadioButton.setSelected(true);
|
||||||
} else {
|
break;
|
||||||
|
case CUSTOM:
|
||||||
customRadioButton.setSelected(true);
|
customRadioButton.setSelected(true);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import haveno.core.locale.Res;
|
|||||||
import haveno.core.monetary.Price;
|
import haveno.core.monetary.Price;
|
||||||
import haveno.core.offer.Offer;
|
import haveno.core.offer.Offer;
|
||||||
import haveno.core.offer.OfferDirection;
|
import haveno.core.offer.OfferDirection;
|
||||||
|
import haveno.core.offer.OpenOffer;
|
||||||
import haveno.core.payment.PaymentAccount;
|
import haveno.core.payment.PaymentAccount;
|
||||||
import haveno.core.payment.payload.PaymentMethod;
|
import haveno.core.payment.payload.PaymentMethod;
|
||||||
import haveno.core.trade.HavenoUtils;
|
import haveno.core.trade.HavenoUtils;
|
||||||
@ -42,6 +43,8 @@ import haveno.desktop.Navigation;
|
|||||||
import haveno.desktop.components.AutoTooltipButton;
|
import haveno.desktop.components.AutoTooltipButton;
|
||||||
import haveno.desktop.components.BusyAnimation;
|
import haveno.desktop.components.BusyAnimation;
|
||||||
import haveno.desktop.main.overlays.Overlay;
|
import haveno.desktop.main.overlays.Overlay;
|
||||||
|
import haveno.desktop.main.overlays.editor.PasswordPopup;
|
||||||
|
import haveno.desktop.main.overlays.popups.Popup;
|
||||||
import haveno.desktop.util.CssTheme;
|
import haveno.desktop.util.CssTheme;
|
||||||
import haveno.desktop.util.DisplayUtils;
|
import haveno.desktop.util.DisplayUtils;
|
||||||
import static haveno.desktop.util.FormBuilder.addButtonAfterGroup;
|
import static haveno.desktop.util.FormBuilder.addButtonAfterGroup;
|
||||||
@ -195,7 +198,7 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
|
|||||||
rows++;
|
rows++;
|
||||||
}
|
}
|
||||||
|
|
||||||
addTitledGroupBg(gridPane, ++rowIndex, rows, Res.get("shared.Offer"));
|
addTitledGroupBg(gridPane, ++rowIndex, rows, Res.get(offer.isPrivateOffer() ? "shared.Offer" : "shared.Offer"));
|
||||||
|
|
||||||
String counterCurrencyDirectionInfo = "";
|
String counterCurrencyDirectionInfo = "";
|
||||||
String xmrDirectionInfo = "";
|
String xmrDirectionInfo = "";
|
||||||
@ -217,7 +220,7 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
|
|||||||
xmrDirectionInfo = direction == OfferDirection.BUY ? toReceive : toSpend;
|
xmrDirectionInfo = direction == OfferDirection.BUY ? toReceive : toSpend;
|
||||||
} else {
|
} else {
|
||||||
addConfirmationLabelLabel(gridPane, rowIndex, offerTypeLabel,
|
addConfirmationLabelLabel(gridPane, rowIndex, offerTypeLabel,
|
||||||
DisplayUtils.getDirectionBothSides(direction), firstRowDistance);
|
DisplayUtils.getDirectionBothSides(direction, offer.isPrivateOffer()), firstRowDistance);
|
||||||
}
|
}
|
||||||
String amount = Res.get("shared.xmrAmount");
|
String amount = Res.get("shared.xmrAmount");
|
||||||
if (takeOfferHandlerOptional.isPresent()) {
|
if (takeOfferHandlerOptional.isPresent()) {
|
||||||
@ -342,6 +345,10 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
|
|||||||
// get amount reserved for the offer
|
// get amount reserved for the offer
|
||||||
BigInteger reservedAmount = isMyOffer ? offer.getReservedAmount() : null;
|
BigInteger reservedAmount = isMyOffer ? offer.getReservedAmount() : null;
|
||||||
|
|
||||||
|
// get offer challenge
|
||||||
|
OpenOffer myOpenOffer = HavenoUtils.openOfferManager.getOpenOfferById(offer.getId()).orElse(null);
|
||||||
|
String offerChallenge = myOpenOffer == null ? null : myOpenOffer.getChallenge();
|
||||||
|
|
||||||
rows = 3;
|
rows = 3;
|
||||||
if (countryCode != null)
|
if (countryCode != null)
|
||||||
rows++;
|
rows++;
|
||||||
@ -349,6 +356,8 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
|
|||||||
rows++;
|
rows++;
|
||||||
if (reservedAmount != null)
|
if (reservedAmount != null)
|
||||||
rows++;
|
rows++;
|
||||||
|
if (offerChallenge != null)
|
||||||
|
rows++;
|
||||||
|
|
||||||
addTitledGroupBg(gridPane, ++rowIndex, rows, Res.get("shared.details"), Layout.GROUP_DISTANCE);
|
addTitledGroupBg(gridPane, ++rowIndex, rows, Res.get("shared.details"), Layout.GROUP_DISTANCE);
|
||||||
addConfirmationLabelTextFieldWithCopyIcon(gridPane, rowIndex, Res.get("shared.offerId"), offer.getId(),
|
addConfirmationLabelTextFieldWithCopyIcon(gridPane, rowIndex, Res.get("shared.offerId"), offer.getId(),
|
||||||
@ -365,6 +374,7 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
|
|||||||
" " +
|
" " +
|
||||||
HavenoUtils.formatXmr(offer.getOfferPayload().getMaxSellerSecurityDeposit(), true);
|
HavenoUtils.formatXmr(offer.getOfferPayload().getMaxSellerSecurityDeposit(), true);
|
||||||
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.securityDeposit"), value);
|
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.securityDeposit"), value);
|
||||||
|
|
||||||
if (reservedAmount != null) {
|
if (reservedAmount != null) {
|
||||||
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.reservedAmount"), HavenoUtils.formatXmr(reservedAmount, true));
|
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.reservedAmount"), HavenoUtils.formatXmr(reservedAmount, true));
|
||||||
}
|
}
|
||||||
@ -373,6 +383,9 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
|
|||||||
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("offerDetailsWindow.countryBank"),
|
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("offerDetailsWindow.countryBank"),
|
||||||
CountryUtil.getNameAndCode(countryCode));
|
CountryUtil.getNameAndCode(countryCode));
|
||||||
|
|
||||||
|
if (offerChallenge != null)
|
||||||
|
addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, Res.get("offerDetailsWindow.challenge"), offerChallenge);
|
||||||
|
|
||||||
if (placeOfferHandlerOptional.isPresent()) {
|
if (placeOfferHandlerOptional.isPresent()) {
|
||||||
addTitledGroupBg(gridPane, ++rowIndex, 1, Res.get("offerDetailsWindow.commitment"), Layout.GROUP_DISTANCE);
|
addTitledGroupBg(gridPane, ++rowIndex, 1, Res.get("offerDetailsWindow.commitment"), Layout.GROUP_DISTANCE);
|
||||||
final Tuple2<Label, Label> labelLabelTuple2 = addConfirmationLabelLabel(gridPane, rowIndex, Res.get("offerDetailsWindow.agree"), Res.get("createOffer.tac"),
|
final Tuple2<Label, Label> labelLabelTuple2 = addConfirmationLabelLabel(gridPane, rowIndex, Res.get("offerDetailsWindow.agree"), Res.get("createOffer.tac"),
|
||||||
@ -416,13 +429,13 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
|
|||||||
++rowIndex, 1,
|
++rowIndex, 1,
|
||||||
isPlaceOffer ? placeOfferButtonText : takeOfferButtonText);
|
isPlaceOffer ? placeOfferButtonText : takeOfferButtonText);
|
||||||
|
|
||||||
AutoTooltipButton button = (AutoTooltipButton) placeOfferTuple.first;
|
AutoTooltipButton confirmButton = (AutoTooltipButton) placeOfferTuple.first;
|
||||||
button.setMinHeight(40);
|
confirmButton.setMinHeight(40);
|
||||||
button.setPadding(new Insets(0, 20, 0, 20));
|
confirmButton.setPadding(new Insets(0, 20, 0, 20));
|
||||||
button.setGraphic(iconView);
|
confirmButton.setGraphic(iconView);
|
||||||
button.setGraphicTextGap(10);
|
confirmButton.setGraphicTextGap(10);
|
||||||
button.setId(isBuyerRole ? "buy-button-big" : "sell-button-big");
|
confirmButton.setId(isBuyerRole ? "buy-button-big" : "sell-button-big");
|
||||||
button.updateText(isPlaceOffer ? placeOfferButtonText : takeOfferButtonText);
|
confirmButton.updateText(isPlaceOffer ? placeOfferButtonText : takeOfferButtonText);
|
||||||
|
|
||||||
busyAnimation = placeOfferTuple.second;
|
busyAnimation = placeOfferTuple.second;
|
||||||
Label spinnerInfoLabel = placeOfferTuple.third;
|
Label spinnerInfoLabel = placeOfferTuple.third;
|
||||||
@ -436,8 +449,29 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
|
|||||||
|
|
||||||
placeOfferTuple.fourth.getChildren().add(cancelButton);
|
placeOfferTuple.fourth.getChildren().add(cancelButton);
|
||||||
|
|
||||||
button.setOnAction(e -> {
|
confirmButton.setOnAction(e -> {
|
||||||
if (GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation)) {
|
if (GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation)) {
|
||||||
|
if (!isPlaceOffer && offer.isPrivateOffer()) {
|
||||||
|
new PasswordPopup()
|
||||||
|
.headLine(Res.get("offerbook.takeOffer.enterChallenge"))
|
||||||
|
.onAction(password -> {
|
||||||
|
if (offer.getChallengeHash().equals(HavenoUtils.getChallengeHash(password))) {
|
||||||
|
offer.setChallenge(password);
|
||||||
|
confirmTakeOfferAux(confirmButton, cancelButton, spinnerInfoLabel, isPlaceOffer);
|
||||||
|
} else {
|
||||||
|
new Popup().warning(Res.get("password.wrongPw")).show();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.closeButtonText(Res.get("shared.cancel"))
|
||||||
|
.show();
|
||||||
|
} else {
|
||||||
|
confirmTakeOfferAux(confirmButton, cancelButton, spinnerInfoLabel, isPlaceOffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void confirmTakeOfferAux(Button button, Button cancelButton, Label spinnerInfoLabel, boolean isPlaceOffer) {
|
||||||
button.setDisable(true);
|
button.setDisable(true);
|
||||||
cancelButton.setDisable(isPlaceOffer ? false : true); // TODO: enable cancel button for taking an offer until messages sent
|
cancelButton.setDisable(isPlaceOffer ? false : true); // TODO: enable cancel button for taking an offer until messages sent
|
||||||
// temporarily disabled due to high CPU usage (per issue #4649)
|
// temporarily disabled due to high CPU usage (per issue #4649)
|
||||||
@ -456,8 +490,6 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
|
|||||||
takeOfferHandlerOptional.ifPresent(Runnable::run);
|
takeOfferHandlerOptional.ifPresent(Runnable::run);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void subscribeToProgress(Label spinnerInfoLabel) {
|
private void subscribeToProgress(Label spinnerInfoLabel) {
|
||||||
Trade trade = tradeManager.getTrade(offer.getId());
|
Trade trade = tradeManager.getTrade(offer.getId());
|
||||||
|
@ -52,7 +52,7 @@ class DuplicateOfferDataModel extends MutableOfferDataModel {
|
|||||||
DuplicateOfferDataModel(CreateOfferService createOfferService,
|
DuplicateOfferDataModel(CreateOfferService createOfferService,
|
||||||
OpenOfferManager openOfferManager,
|
OpenOfferManager openOfferManager,
|
||||||
OfferUtil offerUtil,
|
OfferUtil offerUtil,
|
||||||
XmrWalletService btcWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
Preferences preferences,
|
Preferences preferences,
|
||||||
User user,
|
User user,
|
||||||
P2PService p2PService,
|
P2PService p2PService,
|
||||||
@ -65,7 +65,7 @@ class DuplicateOfferDataModel extends MutableOfferDataModel {
|
|||||||
super(createOfferService,
|
super(createOfferService,
|
||||||
openOfferManager,
|
openOfferManager,
|
||||||
offerUtil,
|
offerUtil,
|
||||||
btcWalletService,
|
xmrWalletService,
|
||||||
preferences,
|
preferences,
|
||||||
user,
|
user,
|
||||||
p2PService,
|
p2PService,
|
||||||
@ -85,20 +85,21 @@ class DuplicateOfferDataModel extends MutableOfferDataModel {
|
|||||||
setPrice(offer.getPrice());
|
setPrice(offer.getPrice());
|
||||||
setVolume(offer.getVolume());
|
setVolume(offer.getVolume());
|
||||||
setUseMarketBasedPrice(offer.isUseMarketBasedPrice());
|
setUseMarketBasedPrice(offer.isUseMarketBasedPrice());
|
||||||
|
setBuyerAsTakerWithoutDeposit(offer.hasBuyerAsTakerWithoutDeposit());
|
||||||
|
|
||||||
setBuyerSecurityDeposit(getBuyerSecurityAsPercent(offer));
|
setSecurityDepositPct(getSecurityAsPercent(offer));
|
||||||
|
|
||||||
if (offer.isUseMarketBasedPrice()) {
|
if (offer.isUseMarketBasedPrice()) {
|
||||||
setMarketPriceMarginPct(offer.getMarketPriceMarginPct());
|
setMarketPriceMarginPct(offer.getMarketPriceMarginPct());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private double getBuyerSecurityAsPercent(Offer offer) {
|
private double getSecurityAsPercent(Offer offer) {
|
||||||
BigInteger offerBuyerSecurityDeposit = getBoundedBuyerSecurityDeposit(offer.getMaxBuyerSecurityDeposit());
|
BigInteger offerSellerSecurityDeposit = getBoundedSecurityDeposit(offer.getMaxSellerSecurityDeposit());
|
||||||
double offerBuyerSecurityDepositAsPercent = CoinUtil.getAsPercentPerBtc(offerBuyerSecurityDeposit,
|
double offerSellerSecurityDepositAsPercent = CoinUtil.getAsPercentPerXmr(offerSellerSecurityDeposit,
|
||||||
offer.getAmount());
|
offer.getAmount());
|
||||||
return Math.min(offerBuyerSecurityDepositAsPercent,
|
return Math.min(offerSellerSecurityDepositAsPercent,
|
||||||
Restrictions.getMaxBuyerSecurityDepositAsPercent());
|
Restrictions.getMaxSecurityDepositAsPercent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -95,7 +95,7 @@ class EditOfferDataModel extends MutableOfferDataModel {
|
|||||||
price.set(null);
|
price.set(null);
|
||||||
volume.set(null);
|
volume.set(null);
|
||||||
minVolume.set(null);
|
minVolume.set(null);
|
||||||
buyerSecurityDepositPct.set(0);
|
securityDepositPct.set(0);
|
||||||
paymentAccounts.clear();
|
paymentAccounts.clear();
|
||||||
paymentAccount = null;
|
paymentAccount = null;
|
||||||
marketPriceMargin = 0;
|
marketPriceMargin = 0;
|
||||||
@ -127,12 +127,12 @@ class EditOfferDataModel extends MutableOfferDataModel {
|
|||||||
// If the security deposit got bounded because it was below the coin amount limit, it can be bigger
|
// If the security deposit got bounded because it was below the coin amount limit, it can be bigger
|
||||||
// by percentage than the restriction. We can't determine the percentage originally entered at offer
|
// by percentage than the restriction. We can't determine the percentage originally entered at offer
|
||||||
// creation, so just use the default value as it doesn't matter anyway.
|
// creation, so just use the default value as it doesn't matter anyway.
|
||||||
double buyerSecurityDepositPercent = CoinUtil.getAsPercentPerBtc(offer.getMaxBuyerSecurityDeposit(), offer.getAmount());
|
double securityDepositPercent = CoinUtil.getAsPercentPerXmr(offer.getMaxSellerSecurityDeposit(), offer.getAmount());
|
||||||
if (buyerSecurityDepositPercent > Restrictions.getMaxBuyerSecurityDepositAsPercent()
|
if (securityDepositPercent > Restrictions.getMaxSecurityDepositAsPercent()
|
||||||
&& offer.getMaxBuyerSecurityDeposit().equals(Restrictions.getMinBuyerSecurityDeposit()))
|
&& offer.getMaxSellerSecurityDeposit().equals(Restrictions.getMinSecurityDeposit()))
|
||||||
buyerSecurityDepositPct.set(Restrictions.getDefaultBuyerSecurityDepositAsPercent());
|
securityDepositPct.set(Restrictions.getDefaultSecurityDepositAsPercent());
|
||||||
else
|
else
|
||||||
buyerSecurityDepositPct.set(buyerSecurityDepositPercent);
|
securityDepositPct.set(securityDepositPercent);
|
||||||
|
|
||||||
allowAmountUpdate = false;
|
allowAmountUpdate = false;
|
||||||
}
|
}
|
||||||
@ -211,7 +211,7 @@ class EditOfferDataModel extends MutableOfferDataModel {
|
|||||||
offerPayload.getLowerClosePrice(),
|
offerPayload.getLowerClosePrice(),
|
||||||
offerPayload.getUpperClosePrice(),
|
offerPayload.getUpperClosePrice(),
|
||||||
offerPayload.isPrivateOffer(),
|
offerPayload.isPrivateOffer(),
|
||||||
offerPayload.getHashOfChallenge(),
|
offerPayload.getChallengeHash(),
|
||||||
offerPayload.getExtraDataMap(),
|
offerPayload.getExtraDataMap(),
|
||||||
offerPayload.getProtocolVersion(),
|
offerPayload.getProtocolVersion(),
|
||||||
offerPayload.getArbitratorSigner(),
|
offerPayload.getArbitratorSigner(),
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user