mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-08-17 19:02:32 -04:00
commit
06052ad7b4
95 changed files with 1157 additions and 642 deletions
|
@ -737,14 +737,13 @@ public class AccountAgeWitnessService {
|
|||
}
|
||||
|
||||
public Optional<SignedWitness> traderSignAndPublishPeersAccountAgeWitness(Trade trade) {
|
||||
AccountAgeWitness peersWitness = findTradePeerWitness(trade).orElse(null);
|
||||
BigInteger tradeAmount = trade.getAmount();
|
||||
checkNotNull(trade.getTradePeer().getPubKeyRing(), "Peer must have a keyring");
|
||||
PublicKey peersPubKey = trade.getTradePeer().getPubKeyRing().getSignaturePubKey();
|
||||
checkNotNull(peersWitness, "Not able to find peers witness, unable to sign for trade {}",
|
||||
trade.toString());
|
||||
checkNotNull(tradeAmount, "Trade amount must not be null");
|
||||
checkNotNull(peersPubKey, "Peers pub key must not be null");
|
||||
AccountAgeWitness peersWitness = findTradePeerWitness(trade).orElse(null);
|
||||
checkNotNull(peersWitness, "Not able to find peers witness, unable to sign for trade " + trade.toString());
|
||||
BigInteger tradeAmount = trade.getAmount();
|
||||
checkNotNull(tradeAmount, "Trade amount must not be null");
|
||||
|
||||
try {
|
||||
return signedWitnessService.signAndPublishAccountAgeWitness(tradeAmount, peersWitness, peersPubKey);
|
||||
|
|
|
@ -64,9 +64,14 @@ class CorePaymentAccountsService {
|
|||
}
|
||||
|
||||
PaymentAccount createPaymentAccount(PaymentAccountForm form) {
|
||||
validateFormFields(form);
|
||||
PaymentAccount paymentAccount = form.toPaymentAccount();
|
||||
setSelectedTradeCurrency(paymentAccount); // TODO: selected trade currency is function of offer, not payment account payload
|
||||
verifyPaymentAccountHasRequiredFields(paymentAccount);
|
||||
if (paymentAccount instanceof CryptoCurrencyAccount) {
|
||||
CryptoCurrencyAccount cryptoAccount = (CryptoCurrencyAccount) paymentAccount;
|
||||
verifyCryptoCurrencyAddress(cryptoAccount.getSingleTradeCurrency().getCode(), cryptoAccount.getAddress());
|
||||
}
|
||||
user.addPaymentAccountIfNotExists(paymentAccount);
|
||||
accountAgeWitnessService.publishMyAccountAgeWitness(paymentAccount.getPaymentAccountPayload());
|
||||
log.info("Saved payment account with id {} and payment method {}.",
|
||||
|
@ -166,6 +171,12 @@ class CorePaymentAccountsService {
|
|||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void validateFormFields(PaymentAccountForm form) {
|
||||
for (PaymentAccountFormField field : form.getFields()) {
|
||||
validateFormField(form, field.getId(), field.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
void validateFormField(PaymentAccountForm form, PaymentAccountFormField.FieldId fieldId, String value) {
|
||||
|
||||
// get payment method id
|
||||
|
|
|
@ -72,7 +72,7 @@ class CorePriceService {
|
|||
* @return Price per 1 XMR in the given currency (traditional or crypto)
|
||||
*/
|
||||
public double getMarketPrice(String currencyCode) throws ExecutionException, InterruptedException, TimeoutException, IllegalArgumentException {
|
||||
var marketPrice = priceFeedService.requestAllPrices().get(currencyCode);
|
||||
var marketPrice = priceFeedService.requestAllPrices().get(CurrencyUtil.getCurrencyCodeBase(currencyCode));
|
||||
if (marketPrice == null) {
|
||||
throw new IllegalArgumentException("Currency not found: " + currencyCode); // message sent to client
|
||||
}
|
||||
|
|
|
@ -19,10 +19,8 @@ package haveno.core.locale;
|
|||
|
||||
|
||||
import com.google.protobuf.Message;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class CryptoCurrency extends TradeCurrency {
|
||||
// http://boschista.deviantart.com/journal/Cool-ASCII-Symbols-214218618
|
||||
private final static String PREFIX = "✦ ";
|
||||
|
|
|
@ -73,14 +73,6 @@ public class CurrencyUtil {
|
|||
|
||||
private static String baseCurrencyCode = "XMR";
|
||||
|
||||
private static List<TraditionalCurrency> getTraditionalNonFiatCurrencies() {
|
||||
return Arrays.asList(
|
||||
new TraditionalCurrency("XAG", "Silver"),
|
||||
new TraditionalCurrency("XAU", "Gold"),
|
||||
new TraditionalCurrency("XGB", "Goldback")
|
||||
);
|
||||
}
|
||||
|
||||
// Calls to isTraditionalCurrency and isCryptoCurrency are very frequent so we use a cache of the results.
|
||||
// The main improvement was already achieved with using memoize for the source maps, but
|
||||
// the caching still reduces performance costs by about 20% for isCryptoCurrency (1752 ms vs 2121 ms) and about 50%
|
||||
|
@ -124,6 +116,14 @@ public class CurrencyUtil {
|
|||
return new ArrayList<>(traditionalCurrencyMapSupplier.get().values());
|
||||
}
|
||||
|
||||
public static List<TraditionalCurrency> getTraditionalNonFiatCurrencies() {
|
||||
return Arrays.asList(
|
||||
new TraditionalCurrency("XAG", "Silver"),
|
||||
new TraditionalCurrency("XAU", "Gold"),
|
||||
new TraditionalCurrency("XGB", "Goldback")
|
||||
);
|
||||
}
|
||||
|
||||
public static Collection<TraditionalCurrency> getAllSortedTraditionalCurrencies(Comparator comparator) {
|
||||
return (List<TraditionalCurrency>) getAllSortedTraditionalCurrencies().stream()
|
||||
.sorted(comparator)
|
||||
|
@ -200,6 +200,7 @@ public class CurrencyUtil {
|
|||
result.add(new CryptoCurrency("BCH", "Bitcoin Cash"));
|
||||
result.add(new CryptoCurrency("ETH", "Ether"));
|
||||
result.add(new CryptoCurrency("LTC", "Litecoin"));
|
||||
result.add(new CryptoCurrency("USDT-ERC20", "Tether USD (ERC20)"));
|
||||
result.sort(TradeCurrency::compareTo);
|
||||
return result;
|
||||
}
|
||||
|
@ -295,6 +296,9 @@ public class CurrencyUtil {
|
|||
if (currencyCode != null && isCryptoCurrencyMap.containsKey(currencyCode.toUpperCase())) {
|
||||
return isCryptoCurrencyMap.get(currencyCode.toUpperCase());
|
||||
}
|
||||
if (isCryptoCurrencyBase(currencyCode)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean isCryptoCurrency;
|
||||
if (currencyCode == null) {
|
||||
|
@ -321,6 +325,19 @@ public class CurrencyUtil {
|
|||
return isCryptoCurrency;
|
||||
}
|
||||
|
||||
private static boolean isCryptoCurrencyBase(String currencyCode) {
|
||||
if (currencyCode == null) return false;
|
||||
currencyCode = currencyCode.toUpperCase();
|
||||
return currencyCode.equals("USDT");
|
||||
}
|
||||
|
||||
public static String getCurrencyCodeBase(String currencyCode) {
|
||||
if (currencyCode == null) return null;
|
||||
currencyCode = currencyCode.toUpperCase();
|
||||
if (currencyCode.contains("USDT")) return "USDT";
|
||||
return currencyCode;
|
||||
}
|
||||
|
||||
public static Optional<CryptoCurrency> getCryptoCurrency(String currencyCode) {
|
||||
return Optional.ofNullable(cryptoCurrencyMapSupplier.get().get(currencyCode));
|
||||
}
|
||||
|
|
|
@ -19,19 +19,16 @@ package haveno.core.locale;
|
|||
|
||||
import haveno.common.proto.ProtobufferRuntimeException;
|
||||
import haveno.common.proto.persistable.PersistablePayload;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
@Getter
|
||||
@Slf4j
|
||||
public abstract class TradeCurrency implements PersistablePayload, Comparable<TradeCurrency> {
|
||||
protected final String code;
|
||||
@EqualsAndHashCode.Exclude
|
||||
protected final String name;
|
||||
|
||||
public TradeCurrency(String code, String name) {
|
||||
|
@ -82,4 +79,23 @@ public abstract class TradeCurrency implements PersistablePayload, Comparable<Tr
|
|||
return this.name.compareTo(other.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof TradeCurrency) {
|
||||
TradeCurrency other = (TradeCurrency) obj;
|
||||
return code.equals(other.code);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return code.hashCode();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,14 +36,12 @@ package haveno.core.locale;
|
|||
|
||||
|
||||
import com.google.protobuf.Message;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.Currency;
|
||||
import java.util.Locale;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString
|
||||
@Getter
|
||||
public final class TraditionalCurrency extends TradeCurrency {
|
||||
|
|
|
@ -62,6 +62,7 @@ import haveno.core.offer.messages.SignOfferRequest;
|
|||
import haveno.core.offer.messages.SignOfferResponse;
|
||||
import haveno.core.offer.placeoffer.PlaceOfferModel;
|
||||
import haveno.core.offer.placeoffer.PlaceOfferProtocol;
|
||||
import haveno.core.offer.placeoffer.tasks.ValidateOffer;
|
||||
import haveno.core.provider.price.PriceFeedService;
|
||||
import haveno.core.support.dispute.arbitration.arbitrator.Arbitrator;
|
||||
import haveno.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||
|
@ -934,6 +935,14 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
return;
|
||||
}
|
||||
|
||||
// validate offer
|
||||
try {
|
||||
ValidateOffer.validateOffer(openOffer.getOffer(), accountAgeWitnessService, user);
|
||||
} catch (Exception e) {
|
||||
errorMessageHandler.handleErrorMessage("Failed to validate offer: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
// cancel offer if scheduled txs unavailable
|
||||
if (openOffer.getScheduledTxHashes() != null) {
|
||||
boolean scheduledTxsAvailable = true;
|
||||
|
@ -1855,7 +1864,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
}
|
||||
|
||||
private boolean preventedFromPublishing(OpenOffer openOffer) {
|
||||
return openOffer.isDeactivated() || openOffer.isCanceled();
|
||||
return openOffer.isDeactivated() || openOffer.isCanceled() || openOffer.getOffer().getOfferPayload().getArbitratorSigner() == null;
|
||||
}
|
||||
|
||||
private void startPeriodicRepublishOffersTimer() {
|
||||
|
|
|
@ -19,10 +19,12 @@ package haveno.core.offer.placeoffer.tasks;
|
|||
|
||||
import haveno.common.taskrunner.Task;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.account.witness.AccountAgeWitnessService;
|
||||
import haveno.core.offer.Offer;
|
||||
import haveno.core.offer.placeoffer.PlaceOfferModel;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.trade.messages.TradeMessage;
|
||||
import haveno.core.user.User;
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
@ -41,55 +43,7 @@ public class ValidateOffer extends Task<PlaceOfferModel> {
|
|||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// Coins
|
||||
checkBINotNullOrZero(offer.getAmount(), "Amount");
|
||||
checkBINotNullOrZero(offer.getMinAmount(), "MinAmount");
|
||||
//checkCoinNotNullOrZero(offer.getTxFee(), "txFee"); // TODO: remove from data model
|
||||
checkBINotNullOrZero(offer.getMaxTradeLimit(), "MaxTradeLimit");
|
||||
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.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());
|
||||
|
||||
// We remove those checks to be more flexible with future changes.
|
||||
/*checkArgument(offer.getMakerFee().value >= FeeService.getMinMakerFee(offer.isCurrencyForMakerFeeBtc()).value,
|
||||
"createOfferFee must not be less than FeeService.MIN_CREATE_OFFER_FEE_IN_BTC. " +
|
||||
"MakerFee=" + offer.getMakerFee().toFriendlyString());*/
|
||||
/*checkArgument(offer.getBuyerSecurityDeposit().value >= ProposalConsensus.getMinBuyerSecurityDeposit().value,
|
||||
"buyerSecurityDeposit must not be less than ProposalConsensus.MIN_BUYER_SECURITY_DEPOSIT. " +
|
||||
"buyerSecurityDeposit=" + offer.getBuyerSecurityDeposit().toFriendlyString());
|
||||
checkArgument(offer.getBuyerSecurityDeposit().value <= ProposalConsensus.getMaxBuyerSecurityDeposit().value,
|
||||
"buyerSecurityDeposit must not be larger than ProposalConsensus.MAX_BUYER_SECURITY_DEPOSIT. " +
|
||||
"buyerSecurityDeposit=" + offer.getBuyerSecurityDeposit().toFriendlyString());
|
||||
checkArgument(offer.getSellerSecurityDeposit().value == ProposalConsensus.getSellerSecurityDeposit().value,
|
||||
"sellerSecurityDeposit must be equal to ProposalConsensus.SELLER_SECURITY_DEPOSIT. " +
|
||||
"sellerSecurityDeposit=" + offer.getSellerSecurityDeposit().toFriendlyString());*/
|
||||
/*checkArgument(offer.getMinAmount().compareTo(ProposalConsensus.getMinTradeAmount()) >= 0,
|
||||
"MinAmount is less than " + ProposalConsensus.getMinTradeAmount().toFriendlyString());*/
|
||||
|
||||
long maxAmount = model.getAccountAgeWitnessService().getMyTradeLimit(model.getUser().getPaymentAccount(offer.getMakerPaymentAccountId()), offer.getCurrencyCode(), offer.getDirection());
|
||||
checkArgument(offer.getAmount().longValueExact() <= maxAmount,
|
||||
"Amount is larger than " + HavenoUtils.atomicUnitsToXmr(offer.getPaymentMethod().getMaxTradeLimit(offer.getCurrencyCode())) + " XMR");
|
||||
checkArgument(offer.getAmount().compareTo(offer.getMinAmount()) >= 0, "MinAmount is larger than Amount");
|
||||
|
||||
checkNotNull(offer.getPrice(), "Price is null");
|
||||
if (!offer.isUseMarketBasedPrice()) checkArgument(offer.getPrice().isPositive(),
|
||||
"Price must be positive unless using market based price. price=" + offer.getPrice().toFriendlyString());
|
||||
|
||||
checkArgument(offer.getDate().getTime() > 0,
|
||||
"Date must not be 0. date=" + offer.getDate().toString());
|
||||
|
||||
checkNotNull(offer.getCurrencyCode(), "Currency is null");
|
||||
checkNotNull(offer.getDirection(), "Direction is null");
|
||||
checkNotNull(offer.getId(), "Id is null");
|
||||
checkNotNull(offer.getPubKeyRing(), "pubKeyRing is null");
|
||||
checkNotNull(offer.getMinAmount(), "MinAmount is null");
|
||||
checkNotNull(offer.getPrice(), "Price is null");
|
||||
checkNotNull(offer.getVersionNr(), "VersionNr is null");
|
||||
checkArgument(offer.getMaxTradePeriod() > 0,
|
||||
"maxTradePeriod must be positive. maxTradePeriod=" + offer.getMaxTradePeriod());
|
||||
// TODO check upper and lower bounds for fiat
|
||||
// TODO check rest of new parameters
|
||||
validateOffer(offer, model.getAccountAgeWitnessService(), model.getUser());
|
||||
|
||||
complete();
|
||||
} catch (Exception e) {
|
||||
|
@ -100,42 +54,95 @@ public class ValidateOffer extends Task<PlaceOfferModel> {
|
|||
}
|
||||
}
|
||||
|
||||
public static void checkBINotNullOrZero(BigInteger value, String name) {
|
||||
public static void validateOffer(Offer offer, AccountAgeWitnessService accountAgeWitnessService, User user) {
|
||||
|
||||
// Coins
|
||||
checkBINotNullOrZero(offer.getAmount(), "Amount");
|
||||
checkBINotNullOrZero(offer.getMinAmount(), "MinAmount");
|
||||
//checkCoinNotNullOrZero(offer.getTxFee(), "txFee"); // TODO: remove from data model
|
||||
checkBINotNullOrZero(offer.getMaxTradeLimit(), "MaxTradeLimit");
|
||||
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.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());
|
||||
|
||||
// We remove those checks to be more flexible with future changes.
|
||||
/*checkArgument(offer.getMakerFee().value >= FeeService.getMinMakerFee(offer.isCurrencyForMakerFeeBtc()).value,
|
||||
"createOfferFee must not be less than FeeService.MIN_CREATE_OFFER_FEE_IN_BTC. " +
|
||||
"MakerFee=" + offer.getMakerFee().toFriendlyString());*/
|
||||
/*checkArgument(offer.getBuyerSecurityDeposit().value >= ProposalConsensus.getMinBuyerSecurityDeposit().value,
|
||||
"buyerSecurityDeposit must not be less than ProposalConsensus.MIN_BUYER_SECURITY_DEPOSIT. " +
|
||||
"buyerSecurityDeposit=" + offer.getBuyerSecurityDeposit().toFriendlyString());
|
||||
checkArgument(offer.getBuyerSecurityDeposit().value <= ProposalConsensus.getMaxBuyerSecurityDeposit().value,
|
||||
"buyerSecurityDeposit must not be larger than ProposalConsensus.MAX_BUYER_SECURITY_DEPOSIT. " +
|
||||
"buyerSecurityDeposit=" + offer.getBuyerSecurityDeposit().toFriendlyString());
|
||||
checkArgument(offer.getSellerSecurityDeposit().value == ProposalConsensus.getSellerSecurityDeposit().value,
|
||||
"sellerSecurityDeposit must be equal to ProposalConsensus.SELLER_SECURITY_DEPOSIT. " +
|
||||
"sellerSecurityDeposit=" + offer.getSellerSecurityDeposit().toFriendlyString());*/
|
||||
/*checkArgument(offer.getMinAmount().compareTo(ProposalConsensus.getMinTradeAmount()) >= 0,
|
||||
"MinAmount is less than " + ProposalConsensus.getMinTradeAmount().toFriendlyString());*/
|
||||
|
||||
long maxAmount = accountAgeWitnessService.getMyTradeLimit(user.getPaymentAccount(offer.getMakerPaymentAccountId()), offer.getCurrencyCode(), offer.getDirection());
|
||||
checkArgument(offer.getAmount().longValueExact() <= maxAmount,
|
||||
"Amount is larger than " + HavenoUtils.atomicUnitsToXmr(offer.getPaymentMethod().getMaxTradeLimit(offer.getCurrencyCode())) + " XMR");
|
||||
checkArgument(offer.getAmount().compareTo(offer.getMinAmount()) >= 0, "MinAmount is larger than Amount");
|
||||
|
||||
checkNotNull(offer.getPrice(), "Price is null");
|
||||
if (!offer.isUseMarketBasedPrice()) checkArgument(offer.getPrice().isPositive(),
|
||||
"Price must be positive unless using market based price. price=" + offer.getPrice().toFriendlyString());
|
||||
|
||||
checkArgument(offer.getDate().getTime() > 0,
|
||||
"Date must not be 0. date=" + offer.getDate().toString());
|
||||
|
||||
checkNotNull(offer.getCurrencyCode(), "Currency is null");
|
||||
checkNotNull(offer.getDirection(), "Direction is null");
|
||||
checkNotNull(offer.getId(), "Id is null");
|
||||
checkNotNull(offer.getPubKeyRing(), "pubKeyRing is null");
|
||||
checkNotNull(offer.getMinAmount(), "MinAmount is null");
|
||||
checkNotNull(offer.getPrice(), "Price is null");
|
||||
checkNotNull(offer.getVersionNr(), "VersionNr is null");
|
||||
checkArgument(offer.getMaxTradePeriod() > 0,
|
||||
"maxTradePeriod must be positive. maxTradePeriod=" + offer.getMaxTradePeriod());
|
||||
// TODO check upper and lower bounds for fiat
|
||||
// TODO check rest of new parameters
|
||||
}
|
||||
|
||||
private static void checkBINotNullOrZero(BigInteger value, String name) {
|
||||
checkNotNull(value, name + " is null");
|
||||
checkArgument(value.compareTo(BigInteger.ZERO) > 0,
|
||||
name + " must be positive. " + name + "=" + value);
|
||||
}
|
||||
|
||||
public static void checkCoinNotNullOrZero(Coin value, String name) {
|
||||
private static void checkCoinNotNullOrZero(Coin value, String name) {
|
||||
checkNotNull(value, name + " is null");
|
||||
checkArgument(value.isPositive(),
|
||||
name + " must be positive. " + name + "=" + value.toFriendlyString());
|
||||
}
|
||||
|
||||
public static String nonEmptyStringOf(String value) {
|
||||
private static String nonEmptyStringOf(String value) {
|
||||
checkNotNull(value);
|
||||
checkArgument(value.length() > 0);
|
||||
return value;
|
||||
}
|
||||
|
||||
public static long nonNegativeLongOf(long value) {
|
||||
private static long nonNegativeLongOf(long value) {
|
||||
checkArgument(value >= 0);
|
||||
return value;
|
||||
}
|
||||
|
||||
public static Coin nonZeroCoinOf(Coin value) {
|
||||
private static Coin nonZeroCoinOf(Coin value) {
|
||||
checkNotNull(value);
|
||||
checkArgument(!value.isZero());
|
||||
return value;
|
||||
}
|
||||
|
||||
public static Coin positiveCoinOf(Coin value) {
|
||||
private static Coin positiveCoinOf(Coin value) {
|
||||
checkNotNull(value);
|
||||
checkArgument(value.isPositive());
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void checkTradeId(String tradeId, TradeMessage tradeMessage) {
|
||||
private static void checkTradeId(String tradeId, TradeMessage tradeMessage) {
|
||||
checkArgument(tradeId.equals(tradeMessage.getOfferId()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -139,6 +139,10 @@ public abstract class PaymentAccount implements PersistablePayload {
|
|||
return getSingleTradeCurrency() == null || CurrencyUtil.isFiatCurrency(getSingleTradeCurrency().getCode()); // TODO: check if trade currencies contain fiat
|
||||
}
|
||||
|
||||
public boolean isCryptoCurrency() {
|
||||
return getSingleTradeCurrency() != null && CurrencyUtil.isCryptoCurrency(getSingleTradeCurrency().getCode());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
|
|
|
@ -37,6 +37,7 @@ package haveno.core.provider;
|
|||
import com.google.inject.Inject;
|
||||
import com.google.inject.name.Named;
|
||||
import haveno.common.config.Config;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
@ -47,9 +48,11 @@ import lombok.extern.slf4j.Slf4j;
|
|||
|
||||
@Slf4j
|
||||
public class ProvidersRepository {
|
||||
|
||||
private static final String DEFAULT_LOCAL_NODE = "http://localhost:8078/";
|
||||
private static final List<String> DEFAULT_NODES = Arrays.asList(
|
||||
"http://elaxlgigphpicy5q7pi5wkz2ko2vgjbq4576vic7febmx4xcxvk6deqd.onion/", // Haveno
|
||||
"http://a66ulzwhhudtqy6k2efnhodj2n6wnc5mnzjs3ocqtf47lwtcuo4wxyqd.onion/" // Cake
|
||||
"http://lrrgpezvdrbpoqvkavzobmj7dr2otxc5x6wgktrw337bk6mxsvfp5yid.onion/" // Cake
|
||||
);
|
||||
|
||||
private final Config config;
|
||||
|
@ -78,19 +81,22 @@ public class ProvidersRepository {
|
|||
this.providersFromProgramArgs = providers;
|
||||
this.useLocalhostForP2P = useLocalhostForP2P;
|
||||
|
||||
Collections.shuffle(DEFAULT_NODES);
|
||||
Collections.shuffle(DEFAULT_NODES); // randomize order of default nodes
|
||||
|
||||
applyBannedNodes(config.bannedPriceRelayNodes);
|
||||
}
|
||||
|
||||
public void applyBannedNodes(@Nullable List<String> bannedNodes) {
|
||||
this.bannedNodes = bannedNodes;
|
||||
|
||||
// fill provider list
|
||||
fillProviderList();
|
||||
selectNextProviderBaseUrl();
|
||||
|
||||
// select next provider if current provider is null or banned
|
||||
if (baseUrl.isEmpty() || isBanned(baseUrl)) selectNextProviderBaseUrl();
|
||||
|
||||
if (bannedNodes != null && !bannedNodes.isEmpty()) {
|
||||
log.info("Excluded provider nodes from filter: nodes={}, selected provider baseUrl={}, providerList={}",
|
||||
bannedNodes, baseUrl, providerList);
|
||||
log.info("Excluded provider nodes from filter: nodes={}, selected provider baseUrl={}, providerList={}", bannedNodes, baseUrl, providerList);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,22 +135,30 @@ public class ProvidersRepository {
|
|||
// If we run in localhost mode we don't have the tor node running, so we need a clearnet host
|
||||
// Use localhost for using a locally running provider
|
||||
providers = List.of(
|
||||
"http://localhost:8078/",
|
||||
DEFAULT_LOCAL_NODE,
|
||||
"https://price.haveno.network/",
|
||||
"http://173.230.142.36:8078/");
|
||||
} else {
|
||||
providers = DEFAULT_NODES;
|
||||
providers = new ArrayList<String>();
|
||||
//providers.add(DEFAULT_LOCAL_NODE); // try local provider first
|
||||
providers.addAll(DEFAULT_NODES);
|
||||
}
|
||||
} else {
|
||||
providers = providersFromProgramArgs;
|
||||
}
|
||||
providerList = providers.stream()
|
||||
.filter(e -> bannedNodes == null ||
|
||||
!bannedNodes.contains(e.replace("http://", "")
|
||||
.replace("/", "")
|
||||
.replace(".onion", "")))
|
||||
.filter(e -> !isBanned(e))
|
||||
.map(e -> e.endsWith("/") ? e : e + "/")
|
||||
.map(e -> e.startsWith("http") ? e : "http://" + e)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private boolean isBanned(String provider) {
|
||||
if (bannedNodes == null) return false;
|
||||
return bannedNodes.stream()
|
||||
.anyMatch(e -> provider.replace("http://", "")
|
||||
.replace("/", "")
|
||||
.replace(".onion", "")
|
||||
.equals(e));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -292,15 +292,16 @@ public class PriceFeedService {
|
|||
@Nullable
|
||||
public MarketPrice getMarketPrice(String currencyCode) {
|
||||
synchronized (cache) {
|
||||
return cache.getOrDefault(currencyCode, null);
|
||||
return cache.getOrDefault(CurrencyUtil.getCurrencyCodeBase(currencyCode), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void setHavenoMarketPrice(String currencyCode, Price price) {
|
||||
UserThread.execute(() -> {
|
||||
String currencyCodeBase = CurrencyUtil.getCurrencyCodeBase(currencyCode);
|
||||
synchronized (cache) {
|
||||
if (!cache.containsKey(currencyCode) || !cache.get(currencyCode).isExternallyProvidedPrice()) {
|
||||
cache.put(currencyCode, new MarketPrice(currencyCode,
|
||||
if (!cache.containsKey(currencyCodeBase) || !cache.get(currencyCodeBase).isExternallyProvidedPrice()) {
|
||||
cache.put(currencyCodeBase, new MarketPrice(currencyCodeBase,
|
||||
MathUtils.scaleDownByPowerOf10(price.getValue(), CurrencyUtil.isCryptoCurrency(currencyCode) ? CryptoMoney.SMALLEST_UNIT_EXPONENT : TraditionalMoney.SMALLEST_UNIT_EXPONENT),
|
||||
0,
|
||||
false));
|
||||
|
|
|
@ -21,6 +21,7 @@ import com.google.gson.Gson;
|
|||
import com.google.gson.internal.LinkedTreeMap;
|
||||
import haveno.common.app.Version;
|
||||
import haveno.common.util.MathUtils;
|
||||
import haveno.core.locale.CurrencyUtil;
|
||||
import haveno.core.provider.HttpClientProvider;
|
||||
import haveno.network.http.HttpClient;
|
||||
import haveno.network.p2p.P2PService;
|
||||
|
@ -63,6 +64,7 @@ public class PriceProvider extends HttpClientProvider {
|
|||
String baseCurrencyCode = (String) treeMap.get("baseCurrencyCode");
|
||||
String counterCurrencyCode = (String) treeMap.get("counterCurrencyCode");
|
||||
String currencyCode = baseCurrencyCode.equals("XMR") ? counterCurrencyCode : baseCurrencyCode;
|
||||
currencyCode = CurrencyUtil.getCurrencyCodeBase(currencyCode);
|
||||
double price = (Double) treeMap.get("price");
|
||||
// json uses double for our timestampSec long value...
|
||||
long timestampSec = MathUtils.doubleToLong((Double) treeMap.get("timestampSec"));
|
||||
|
|
|
@ -466,6 +466,10 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||
return this.disputeState == State.NEW;
|
||||
}
|
||||
|
||||
public boolean isOpen() {
|
||||
return this.disputeState == State.OPEN || this.disputeState == State.REOPENED;
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return this.disputeState == State.CLOSED;
|
||||
}
|
||||
|
|
|
@ -393,8 +393,14 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||
chatMessage.setSystemMessage(true);
|
||||
dispute.addAndPersistChatMessage(chatMessage);
|
||||
|
||||
// export latest multisig hex
|
||||
try {
|
||||
trade.exportMultisigHex();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to export multisig hex", e);
|
||||
}
|
||||
|
||||
// create dispute opened message
|
||||
trade.exportMultisigHex();
|
||||
NodeAddress agentNodeAddress = getAgentNodeAddress(dispute);
|
||||
DisputeOpenedMessage disputeOpenedMessage = new DisputeOpenedMessage(dispute,
|
||||
p2PService.getAddress(),
|
||||
|
|
|
@ -70,7 +70,7 @@ public abstract class DisputeSession extends SupportSession {
|
|||
|
||||
@Override
|
||||
public boolean chatIsOpen() {
|
||||
return dispute != null && !dispute.isClosed();
|
||||
return dispute != null && dispute.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -575,14 +575,14 @@ public class HavenoUtils {
|
|||
// get original format
|
||||
AudioFormat baseFormat = audioInputStream.getFormat();
|
||||
|
||||
// set target format: PCM_SIGNED, 16-bit
|
||||
// set target format: PCM_SIGNED, 16-bit, 44100 Hz
|
||||
AudioFormat targetFormat = new AudioFormat(
|
||||
AudioFormat.Encoding.PCM_SIGNED,
|
||||
baseFormat.getSampleRate(),
|
||||
44100.0f,
|
||||
16, // 16-bit instead of 32-bit float
|
||||
baseFormat.getChannels(),
|
||||
baseFormat.getChannels() * 2, // Frame size: 2 bytes per channel (16-bit)
|
||||
baseFormat.getSampleRate(),
|
||||
44100.0f,
|
||||
false // Little-endian
|
||||
);
|
||||
|
||||
|
|
|
@ -937,6 +937,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||
if (wallet == null) throw new RuntimeException("Trade wallet to close is not open for trade " + getId());
|
||||
stopPolling();
|
||||
xmrWalletService.closeWallet(wallet, true);
|
||||
maybeBackupWallet();
|
||||
wallet = null;
|
||||
pollPeriodMs = null;
|
||||
}
|
||||
|
@ -1064,6 +1065,14 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||
}
|
||||
}
|
||||
|
||||
public void importMultisigHexIfNeeded() {
|
||||
synchronized (walletLock) {
|
||||
if (wallet.isMultisigImportNeeded()) {
|
||||
importMultisigHex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void importMultisigHex() {
|
||||
synchronized (walletLock) {
|
||||
synchronized (HavenoUtils.getDaemonLock()) { // lock on daemon because import calls full refresh
|
||||
|
@ -1076,8 +1085,10 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to import multisig hex, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||
handleWalletError(e, sourceConnection);
|
||||
doPollWallet();
|
||||
if (isPayoutPublished()) break;
|
||||
log.warn("Failed to import multisig hex, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||
}
|
||||
|
@ -1183,6 +1194,11 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||
// create payout tx
|
||||
synchronized (walletLock) {
|
||||
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||
|
||||
// import multisig hex if needed
|
||||
importMultisigHexIfNeeded();
|
||||
|
||||
// create payout tx
|
||||
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||
MoneroRpcConnection sourceConnection = xmrConnectionService.getConnection();
|
||||
try {
|
||||
|
@ -1190,8 +1206,10 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to create payout tx, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||
handleWalletError(e, sourceConnection);
|
||||
doPollWallet();
|
||||
if (isPayoutPublished()) break;
|
||||
log.warn("Failed to create payout tx, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||
}
|
||||
|
@ -1250,8 +1268,10 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||
throw e;
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage().contains("not possible")) throw new IllegalArgumentException("Loser payout is too small to cover the mining fee");
|
||||
log.warn("Failed to create dispute payout tx, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||
handleWalletError(e, sourceConnection);
|
||||
doPollWallet();
|
||||
if (isPayoutPublished()) break;
|
||||
log.warn("Failed to create dispute payout tx, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||
}
|
||||
|
@ -1279,8 +1299,10 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to process payout tx, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage(), e);
|
||||
handleWalletError(e, sourceConnection);
|
||||
doPollWallet();
|
||||
if (isPayoutPublished()) break;
|
||||
log.warn("Failed to process payout tx, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage(), e);
|
||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||
} finally {
|
||||
|
@ -1545,9 +1567,6 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||
forceCloseWallet();
|
||||
}
|
||||
|
||||
// backup trade wallet if applicable
|
||||
maybeBackupWallet();
|
||||
|
||||
// de-initialize
|
||||
if (idlePayoutSyncer != null) {
|
||||
xmrWalletService.removeWalletListener(idlePayoutSyncer);
|
||||
|
@ -2438,7 +2457,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||
}
|
||||
}
|
||||
|
||||
if (pollWallet) pollWallet();
|
||||
if (pollWallet) doPollWallet();
|
||||
} catch (Exception e) {
|
||||
ThreadUtils.execute(() -> requestSwitchToNextBestConnection(sourceConnection), getId());
|
||||
throw e;
|
||||
|
@ -2500,10 +2519,18 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||
}
|
||||
|
||||
private void doPollWallet() {
|
||||
|
||||
// skip if shut down started
|
||||
if (isShutDownStarted) return;
|
||||
|
||||
// set poll in progress
|
||||
boolean pollInProgressSet = false;
|
||||
synchronized (pollLock) {
|
||||
if (!pollInProgress) pollInProgressSet = true;
|
||||
pollInProgress = true;
|
||||
}
|
||||
|
||||
// poll wallet
|
||||
try {
|
||||
|
||||
// skip if payout unlocked
|
||||
|
@ -2628,8 +2655,10 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
|||
}
|
||||
}
|
||||
} finally {
|
||||
synchronized (pollLock) {
|
||||
pollInProgress = false;
|
||||
if (pollInProgressSet) {
|
||||
synchronized (pollLock) {
|
||||
pollInProgress = false;
|
||||
}
|
||||
}
|
||||
requestSaveWallet();
|
||||
}
|
||||
|
|
|
@ -450,6 +450,13 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
return;
|
||||
}
|
||||
|
||||
// skip if marked as failed
|
||||
if (failedTradesManager.getObservableList().contains(trade)) {
|
||||
log.warn("Skipping initialization of failed trade {} {}", trade.getClass().getSimpleName(), trade.getId());
|
||||
tradesToSkip.add(trade);
|
||||
return;
|
||||
}
|
||||
|
||||
// initialize trade
|
||||
initPersistedTrade(trade);
|
||||
|
||||
|
@ -958,6 +965,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
}
|
||||
|
||||
public void unregisterTrade(Trade trade) {
|
||||
log.warn("Unregistering {} {}", trade.getClass().getSimpleName(), trade.getId());
|
||||
removeTrade(trade, true);
|
||||
removeFailedTrade(trade);
|
||||
requestPersistence();
|
||||
|
@ -1059,7 +1067,11 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
|
||||
private void addTradeToPendingTrades(Trade trade) {
|
||||
if (!trade.isInitialized()) {
|
||||
initPersistedTrade(trade);
|
||||
try {
|
||||
initPersistedTrade(trade);
|
||||
} catch (Exception e) {
|
||||
log.warn("Error initializing {} {} on move to pending trades", trade.getClass().getSimpleName(), trade.getShortId(), e);
|
||||
}
|
||||
}
|
||||
addTrade(trade);
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ package haveno.core.trade.protocol.tasks;
|
|||
|
||||
import com.google.common.base.Preconditions;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.trade.Trade;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import monero.wallet.MoneroWallet;
|
||||
|
@ -79,15 +80,21 @@ public class BuyerPreparePaymentSentMessage extends TradeTask {
|
|||
// create payout tx if we have seller's updated multisig hex
|
||||
if (trade.getSeller().getUpdatedMultisigHex() != null) {
|
||||
|
||||
// import multisig hex
|
||||
trade.importMultisigHex();
|
||||
// synchronize on lock for wallet operations
|
||||
synchronized (trade.getWalletLock()) {
|
||||
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||
|
||||
// create payout tx
|
||||
log.info("Buyer creating unsigned payout tx for {} {} ", trade.getClass().getSimpleName(), trade.getShortId());
|
||||
MoneroTxWallet payoutTx = trade.createPayoutTx();
|
||||
trade.updatePayout(payoutTx);
|
||||
trade.getSelf().setUnsignedPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
|
||||
trade.requestPersistence();
|
||||
// import multisig hex
|
||||
trade.importMultisigHex();
|
||||
|
||||
// create payout tx
|
||||
log.info("Buyer creating unsigned payout tx for {} {} ", trade.getClass().getSimpleName(), trade.getShortId());
|
||||
MoneroTxWallet payoutTx = trade.createPayoutTx();
|
||||
trade.updatePayout(payoutTx);
|
||||
trade.getSelf().setUnsignedPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
|
||||
trade.requestPersistence();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
complete();
|
||||
|
|
|
@ -41,6 +41,7 @@ public class ProcessDepositResponse extends TradeTask {
|
|||
// throw if error
|
||||
DepositResponse message = (DepositResponse) processModel.getTradeMessage();
|
||||
if (message.getErrorMessage() != null) {
|
||||
log.warn("Unregistering trade {} {} because deposit response has error message={}", trade.getClass().getSimpleName(), trade.getShortId(), message.getErrorMessage());
|
||||
trade.setStateIfValidTransitionTo(Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED);
|
||||
processModel.getTradeManager().unregisterTrade(trade);
|
||||
throw new RuntimeException(message.getErrorMessage());
|
||||
|
|
|
@ -105,12 +105,9 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
|
|||
// advance state, arbitrator auto completes when payout published
|
||||
trade.advanceState(Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG);
|
||||
|
||||
// publish signed witness
|
||||
// buyer republishes signed witness for resilience
|
||||
SignedWitness signedWitness = message.getBuyerSignedWitness();
|
||||
if (signedWitness != null && trade instanceof BuyerTrade) {
|
||||
// We received the signedWitness from the seller and publish the data to the network.
|
||||
// The signer has published it as well but we prefer to re-do it on our side as well to achieve higher
|
||||
// resilience.
|
||||
processModel.getAccountAgeWitnessService().publishOwnSignedWitness(signedWitness);
|
||||
}
|
||||
|
||||
|
@ -146,12 +143,10 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
|
|||
// handle if payout tx not published
|
||||
if (!trade.isPayoutPublished()) {
|
||||
|
||||
// wait to sign and publish payout tx if defer flag set (seller recently saw payout tx arrive at buyer)
|
||||
boolean isSigned = message.getSignedPayoutTxHex() != null;
|
||||
boolean deferSignAndPublish = trade instanceof ArbitratorTrade && !isSigned && message.isDeferPublishPayout();
|
||||
if (deferSignAndPublish) {
|
||||
log.info("Deferring signing and publishing payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId());
|
||||
trade.pollWalletNormallyForMs(60000);
|
||||
// wait to publish payout tx if defer flag set from seller (payout is expected)
|
||||
if (message.isDeferPublishPayout()) {
|
||||
log.info("Deferring publishing payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId());
|
||||
if (trade instanceof ArbitratorTrade) trade.pollWalletNormallyForMs(60000); // stop idling arbitrator
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (trade.isPayoutPublished()) break;
|
||||
HavenoUtils.waitFor(Trade.DEFER_PUBLISH_MS / 5);
|
||||
|
@ -162,6 +157,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
|
|||
// verify and publish payout tx
|
||||
if (!trade.isPayoutPublished()) {
|
||||
try {
|
||||
boolean isSigned = message.getSignedPayoutTxHex() != null;
|
||||
if (isSigned) {
|
||||
log.info("{} {} publishing signed payout tx from seller", trade.getClass().getSimpleName(), trade.getId());
|
||||
trade.processPayoutTx(message.getSignedPayoutTxHex(), false, true);
|
||||
|
|
|
@ -19,6 +19,7 @@ package haveno.core.trade.protocol.tasks;
|
|||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.support.dispute.Dispute;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.trade.Trade;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
@ -49,34 +50,40 @@ public class SellerPreparePaymentReceivedMessage extends TradeTask {
|
|||
trade.setPayoutTxHex(null);
|
||||
}
|
||||
|
||||
// import multisig hex unless already signed
|
||||
if (trade.getPayoutTxHex() == null) {
|
||||
trade.importMultisigHex();
|
||||
}
|
||||
// synchronize on lock for wallet operations
|
||||
synchronized (trade.getWalletLock()) {
|
||||
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||
|
||||
// verify, sign, and publish payout tx if given
|
||||
if (trade.getBuyer().getPaymentSentMessage().getPayoutTxHex() != null) {
|
||||
try {
|
||||
// import multisig hex unless already signed
|
||||
if (trade.getPayoutTxHex() == null) {
|
||||
log.info("Seller verifying, signing, and publishing payout tx for trade {}", trade.getId());
|
||||
trade.processPayoutTx(trade.getBuyer().getPaymentSentMessage().getPayoutTxHex(), true, true);
|
||||
} else {
|
||||
log.warn("Seller publishing previously signed payout tx for trade {}", trade.getId());
|
||||
trade.processPayoutTx(trade.getPayoutTxHex(), false, true);
|
||||
trade.importMultisigHex();
|
||||
}
|
||||
|
||||
// verify, sign, and publish payout tx if given
|
||||
if (trade.getBuyer().getPaymentSentMessage().getPayoutTxHex() != null) {
|
||||
try {
|
||||
if (trade.getPayoutTxHex() == null) {
|
||||
log.info("Seller verifying, signing, and publishing payout tx for trade {}", trade.getId());
|
||||
trade.processPayoutTx(trade.getBuyer().getPaymentSentMessage().getPayoutTxHex(), true, true);
|
||||
} else {
|
||||
log.warn("Seller publishing previously signed payout tx for trade {}", trade.getId());
|
||||
trade.processPayoutTx(trade.getPayoutTxHex(), false, true);
|
||||
}
|
||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
log.warn("Illegal state or argument verifying, signing, and publishing payout tx for {} {}. Creating new unsigned payout tx. error={}. ", trade.getClass().getSimpleName(), trade.getId(), e.getMessage(), e);
|
||||
createUnsignedPayoutTx();
|
||||
} catch (Exception e) {
|
||||
log.warn("Error verifying, signing, and publishing payout tx for trade {}: {}", trade.getId(), e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise create unsigned payout tx
|
||||
else if (trade.getSelf().getUnsignedPayoutTxHex() == null) {
|
||||
createUnsignedPayoutTx();
|
||||
}
|
||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
log.warn("Illegal state or argument verifying, signing, and publishing payout tx for {} {}: {}. Creating new unsigned payout tx", trade.getClass().getSimpleName(), trade.getId(), e.getMessage(), e);
|
||||
createUnsignedPayoutTx();
|
||||
} catch (Exception e) {
|
||||
log.warn("Error verifying, signing, and publishing payout tx for trade {}: {}", trade.getId(), e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise create unsigned payout tx
|
||||
else if (trade.getSelf().getUnsignedPayoutTxHex() == null) {
|
||||
createUnsignedPayoutTx();
|
||||
}
|
||||
} else if (trade.getArbitrator().getPaymentReceivedMessage().getSignedPayoutTxHex() != null && !trade.isPayoutPublished()) {
|
||||
|
||||
// republish payout tx from previous message
|
||||
|
|
|
@ -90,8 +90,12 @@ public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessag
|
|||
// sign account witness
|
||||
AccountAgeWitnessService accountAgeWitnessService = processModel.getAccountAgeWitnessService();
|
||||
if (accountAgeWitnessService.isSignWitnessTrade(trade)) {
|
||||
accountAgeWitnessService.traderSignAndPublishPeersAccountAgeWitness(trade).ifPresent(witness -> signedWitness = witness);
|
||||
log.info("{} {} signed and published peers account age witness", trade.getClass().getSimpleName(), trade.getId());
|
||||
try {
|
||||
accountAgeWitnessService.traderSignAndPublishPeersAccountAgeWitness(trade).ifPresent(witness -> signedWitness = witness);
|
||||
log.info("{} {} signed and published peers account age witness", trade.getClass().getSimpleName(), trade.getId());
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to sign and publish peer's account age witness for {} {}, error={}\n", getClass().getSimpleName(), trade.getId(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// We do not use a real unique ID here as we want to be able to re-send the exact same message in case the
|
||||
|
@ -99,6 +103,7 @@ public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessag
|
|||
// messages where only the one which gets processed by the peer would be removed we use the same uid. All
|
||||
// other data stays the same when we re-send the message at any time later.
|
||||
String deterministicId = HavenoUtils.getDeterministicId(trade, PaymentReceivedMessage.class, getReceiverNodeAddress());
|
||||
boolean deferPublishPayout = trade.isPayoutPublished() || trade.getState().ordinal() >= Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG.ordinal(); // informs receiver to expect payout so delay processing
|
||||
PaymentReceivedMessage message = new PaymentReceivedMessage(
|
||||
tradeId,
|
||||
processModel.getMyNodeAddress(),
|
||||
|
@ -106,7 +111,7 @@ public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessag
|
|||
trade.getPayoutTxHex() == null ? trade.getSelf().getUnsignedPayoutTxHex() : null, // unsigned // TODO: phase in after next update to clear old style trades
|
||||
trade.getPayoutTxHex() == null ? null : trade.getPayoutTxHex(), // signed
|
||||
trade.getSelf().getUpdatedMultisigHex(),
|
||||
trade.getState().ordinal() >= Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG.ordinal(), // informs to expect payout
|
||||
deferPublishPayout,
|
||||
trade.getTradePeer().getAccountAgeWitness(),
|
||||
signedWitness,
|
||||
getReceiver() == trade.getArbitrator() ? trade.getBuyer().getPaymentSentMessage() : null // buyer already has payment sent message
|
||||
|
|
|
@ -566,6 +566,16 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||
requestPersistence();
|
||||
}
|
||||
|
||||
public void setBuyScreenOtherCurrencyCode(String buyScreenCurrencyCode) {
|
||||
prefPayload.setBuyScreenOtherCurrencyCode(buyScreenCurrencyCode);
|
||||
requestPersistence();
|
||||
}
|
||||
|
||||
public void setSellScreenOtherCurrencyCode(String sellScreenCurrencyCode) {
|
||||
prefPayload.setSellScreenOtherCurrencyCode(sellScreenCurrencyCode);
|
||||
requestPersistence();
|
||||
}
|
||||
|
||||
public void setIgnoreTradersList(List<String> ignoreTradersList) {
|
||||
prefPayload.setIgnoreTradersList(ignoreTradersList);
|
||||
requestPersistence();
|
||||
|
|
|
@ -77,6 +77,10 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
|||
private String buyScreenCryptoCurrencyCode;
|
||||
@Nullable
|
||||
private String sellScreenCryptoCurrencyCode;
|
||||
@Nullable
|
||||
private String buyScreenOtherCurrencyCode;
|
||||
@Nullable
|
||||
private String sellScreenOtherCurrencyCode;
|
||||
private int tradeStatisticsTickUnitIndex = 3;
|
||||
private boolean resyncSpvRequested;
|
||||
private boolean sortMarketCurrenciesNumerically = true;
|
||||
|
@ -212,6 +216,8 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
|||
Optional.ofNullable(sellScreenCurrencyCode).ifPresent(builder::setSellScreenCurrencyCode);
|
||||
Optional.ofNullable(buyScreenCryptoCurrencyCode).ifPresent(builder::setBuyScreenCryptoCurrencyCode);
|
||||
Optional.ofNullable(sellScreenCryptoCurrencyCode).ifPresent(builder::setSellScreenCryptoCurrencyCode);
|
||||
Optional.ofNullable(buyScreenOtherCurrencyCode).ifPresent(builder::setBuyScreenOtherCurrencyCode);
|
||||
Optional.ofNullable(sellScreenOtherCurrencyCode).ifPresent(builder::setSellScreenOtherCurrencyCode);
|
||||
Optional.ofNullable(selectedPaymentAccountForCreateOffer).ifPresent(
|
||||
account -> builder.setSelectedPaymentAccountForCreateOffer(selectedPaymentAccountForCreateOffer.toProtoMessage()));
|
||||
Optional.ofNullable(bridgeAddresses).ifPresent(builder::addAllBridgeAddresses);
|
||||
|
@ -260,6 +266,8 @@ public final class PreferencesPayload implements PersistableEnvelope {
|
|||
ProtoUtil.stringOrNullFromProto(proto.getSellScreenCurrencyCode()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getBuyScreenCryptoCurrencyCode()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getSellScreenCryptoCurrencyCode()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getBuyScreenOtherCurrencyCode()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getSellScreenOtherCurrencyCode()),
|
||||
proto.getTradeStatisticsTickUnitIndex(),
|
||||
proto.getResyncSpvRequested(),
|
||||
proto.getSortMarketCurrenciesNumerically(),
|
||||
|
|
|
@ -121,7 +121,7 @@ public class XmrWalletService extends XmrWalletBase {
|
|||
private static final String MONERO_WALLET_NAME = "haveno_XMR";
|
||||
private static final String KEYS_FILE_POSTFIX = ".keys";
|
||||
private static final String ADDRESS_FILE_POSTFIX = ".address.txt";
|
||||
private static final int NUM_MAX_WALLET_BACKUPS = 1;
|
||||
private static final int NUM_MAX_WALLET_BACKUPS = 2;
|
||||
private static final int MAX_SYNC_ATTEMPTS = 3;
|
||||
private static final boolean PRINT_RPC_STACK_TRACE = false;
|
||||
private static final String THREAD_ID = XmrWalletService.class.getSimpleName();
|
||||
|
@ -1477,26 +1477,33 @@ public class XmrWalletService extends XmrWalletBase {
|
|||
try {
|
||||
walletFull = MoneroWalletFull.openWallet(config);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to open full wallet '{}', attempting to use backup cache, error={}", config.getPath(), e.getMessage());
|
||||
log.warn("Failed to open full wallet '{}', attempting to use backup cache files, error={}", config.getPath(), e.getMessage());
|
||||
boolean retrySuccessful = false;
|
||||
try {
|
||||
|
||||
// rename wallet cache to backup
|
||||
String cachePath = walletDir.toString() + File.separator + MONERO_WALLET_NAME;
|
||||
String cachePath = walletDir.toString() + File.separator + getWalletName(config.getPath());
|
||||
File originalCacheFile = new File(cachePath);
|
||||
if (originalCacheFile.exists()) originalCacheFile.renameTo(new File(cachePath + ".backup"));
|
||||
|
||||
// copy latest wallet cache backup to main folder
|
||||
File backupCacheFile = FileUtil.getLatestBackupFile(walletDir, MONERO_WALLET_NAME);
|
||||
if (backupCacheFile != null) FileUtil.copyFile(backupCacheFile, new File(cachePath));
|
||||
// try opening wallet with backup cache files in descending order
|
||||
List<File> backupCacheFiles = FileUtil.getBackupFiles(walletDir, getWalletName(config.getPath()));
|
||||
Collections.reverse(backupCacheFiles);
|
||||
for (File backupCacheFile : backupCacheFiles) {
|
||||
try {
|
||||
FileUtil.copyFile(backupCacheFile, new File(cachePath));
|
||||
walletFull = MoneroWalletFull.openWallet(config);
|
||||
log.warn("Successfully opened full wallet using backup cache");
|
||||
retrySuccessful = true;
|
||||
break;
|
||||
} catch (Exception e2) {
|
||||
|
||||
// retry opening wallet without original cache
|
||||
try {
|
||||
walletFull = MoneroWalletFull.openWallet(config);
|
||||
log.info("Successfully opened full wallet using backup cache");
|
||||
retrySuccessful = true;
|
||||
} catch (Exception e2) {
|
||||
// ignore
|
||||
// delete cache file if failed to open
|
||||
File cacheFile = new File(cachePath);
|
||||
if (cacheFile.exists()) cacheFile.delete();
|
||||
File unportableCacheFile = new File(cachePath + ".unportable");
|
||||
if (unportableCacheFile.exists()) unportableCacheFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
// handle success or failure
|
||||
|
@ -1505,14 +1512,30 @@ public class XmrWalletService extends XmrWalletBase {
|
|||
if (originalCacheBackup.exists()) originalCacheBackup.delete(); // delete original wallet cache backup
|
||||
} else {
|
||||
|
||||
// restore original wallet cache
|
||||
log.warn("Failed to open full wallet using backup cache, restoring original cache");
|
||||
File cacheFile = new File(cachePath);
|
||||
if (cacheFile.exists()) cacheFile.delete();
|
||||
if (originalCacheBackup.exists()) originalCacheBackup.renameTo(new File(cachePath));
|
||||
// retry opening wallet after cache deleted
|
||||
try {
|
||||
log.warn("Failed to open full wallet using backup cache files, retrying with cache deleted");
|
||||
walletFull = MoneroWalletFull.openWallet(config);
|
||||
log.warn("Successfully opened full wallet after cache deleted");
|
||||
retrySuccessful = true;
|
||||
} catch (Exception e2) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// throw exception
|
||||
throw e;
|
||||
// handle success or failure
|
||||
if (retrySuccessful) {
|
||||
if (originalCacheBackup.exists()) originalCacheBackup.delete(); // delete original wallet cache backup
|
||||
} else {
|
||||
|
||||
// restore original wallet cache
|
||||
log.warn("Failed to open full wallet after deleting cache, restoring original cache");
|
||||
File cacheFile = new File(cachePath);
|
||||
if (cacheFile.exists()) cacheFile.delete();
|
||||
if (originalCacheBackup.exists()) originalCacheBackup.renameTo(new File(cachePath));
|
||||
|
||||
// throw original exception
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} catch (Exception e2) {
|
||||
throw e; // throw original exception
|
||||
|
@ -1582,26 +1605,33 @@ public class XmrWalletService extends XmrWalletBase {
|
|||
try {
|
||||
walletRpc.openWallet(config);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to open RPC wallet '{}', attempting to use backup cache, error={}", config.getPath(), e.getMessage());
|
||||
log.warn("Failed to open RPC wallet '{}', attempting to use backup cache files, error={}", config.getPath(), e.getMessage());
|
||||
boolean retrySuccessful = false;
|
||||
try {
|
||||
|
||||
// rename wallet cache to backup
|
||||
String cachePath = walletDir.toString() + File.separator + MONERO_WALLET_NAME;
|
||||
String cachePath = walletDir.toString() + File.separator + config.getPath();
|
||||
File originalCacheFile = new File(cachePath);
|
||||
if (originalCacheFile.exists()) originalCacheFile.renameTo(new File(cachePath + ".backup"));
|
||||
|
||||
// copy latest wallet cache backup to main folder
|
||||
File backupCacheFile = FileUtil.getLatestBackupFile(walletDir, MONERO_WALLET_NAME);
|
||||
if (backupCacheFile != null) FileUtil.copyFile(backupCacheFile, new File(cachePath));
|
||||
// try opening wallet with backup cache files in descending order
|
||||
List<File> backupCacheFiles = FileUtil.getBackupFiles(walletDir, config.getPath());
|
||||
Collections.reverse(backupCacheFiles);
|
||||
for (File backupCacheFile : backupCacheFiles) {
|
||||
try {
|
||||
FileUtil.copyFile(backupCacheFile, new File(cachePath));
|
||||
walletRpc.openWallet(config);
|
||||
log.warn("Successfully opened RPC wallet using backup cache");
|
||||
retrySuccessful = true;
|
||||
break;
|
||||
} catch (Exception e2) {
|
||||
|
||||
// retry opening wallet without original cache
|
||||
try {
|
||||
walletRpc.openWallet(config);
|
||||
log.info("Successfully opened RPC wallet using backup cache");
|
||||
retrySuccessful = true;
|
||||
} catch (Exception e2) {
|
||||
// ignore
|
||||
// delete cache file if failed to open
|
||||
File cacheFile = new File(cachePath);
|
||||
if (cacheFile.exists()) cacheFile.delete();
|
||||
File unportableCacheFile = new File(cachePath + ".unportable");
|
||||
if (unportableCacheFile.exists()) unportableCacheFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
// handle success or failure
|
||||
|
@ -1610,14 +1640,30 @@ public class XmrWalletService extends XmrWalletBase {
|
|||
if (originalCacheBackup.exists()) originalCacheBackup.delete(); // delete original wallet cache backup
|
||||
} else {
|
||||
|
||||
// restore original wallet cache
|
||||
log.warn("Failed to open RPC wallet using backup cache, restoring original cache");
|
||||
File cacheFile = new File(cachePath);
|
||||
if (cacheFile.exists()) cacheFile.delete();
|
||||
if (originalCacheBackup.exists()) originalCacheBackup.renameTo(new File(cachePath));
|
||||
// retry opening wallet after cache deleted
|
||||
try {
|
||||
log.warn("Failed to open RPC wallet using backup cache files, retrying with cache deleted");
|
||||
walletRpc.openWallet(config);
|
||||
log.warn("Successfully opened RPC wallet after cache deleted");
|
||||
retrySuccessful = true;
|
||||
} catch (Exception e2) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// throw exception
|
||||
throw e;
|
||||
// handle success or failure
|
||||
if (retrySuccessful) {
|
||||
if (originalCacheBackup.exists()) originalCacheBackup.delete(); // delete original wallet cache backup
|
||||
} else {
|
||||
|
||||
// restore original wallet cache
|
||||
log.warn("Failed to open RPC wallet after deleting cache, restoring original cache");
|
||||
File cacheFile = new File(cachePath);
|
||||
if (cacheFile.exists()) cacheFile.delete();
|
||||
if (originalCacheBackup.exists()) originalCacheBackup.renameTo(new File(cachePath));
|
||||
|
||||
// throw original exception
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} catch (Exception e2) {
|
||||
throw e; // throw original exception
|
||||
|
@ -1837,10 +1883,18 @@ public class XmrWalletService extends XmrWalletBase {
|
|||
}
|
||||
|
||||
private void doPollWallet(boolean updateTxs) {
|
||||
|
||||
// skip if shut down started
|
||||
if (isShutDownStarted) return;
|
||||
|
||||
// set poll in progress
|
||||
boolean pollInProgressSet = false;
|
||||
synchronized (pollLock) {
|
||||
if (!pollInProgress) pollInProgressSet = true;
|
||||
pollInProgress = true;
|
||||
}
|
||||
if (isShutDownStarted) return;
|
||||
|
||||
// poll wallet
|
||||
try {
|
||||
|
||||
// skip if daemon not synced
|
||||
|
@ -1903,8 +1957,10 @@ public class XmrWalletService extends XmrWalletBase {
|
|||
//e.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
synchronized (pollLock) {
|
||||
pollInProgress = false;
|
||||
if (pollInProgressSet) {
|
||||
synchronized (pollLock) {
|
||||
pollInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
// cache wallet info last
|
||||
|
|
|
@ -150,6 +150,7 @@ shared.addNewAccount=Add new account
|
|||
shared.ExportAccounts=Export Accounts
|
||||
shared.importAccounts=Import Accounts
|
||||
shared.createNewAccount=Create new account
|
||||
shared.createNewAccountDescription=Your account details are stored locally on your device and shared only with your trading peer and the arbitrator if a dispute is opened.
|
||||
shared.saveNewAccount=Save new account
|
||||
shared.selectedAccount=Selected account
|
||||
shared.deleteAccount=Delete account
|
||||
|
@ -207,6 +208,7 @@ shared.crypto=Crypto
|
|||
shared.traditional=Traditional
|
||||
shared.otherAssets=other assets
|
||||
shared.other=Other
|
||||
shared.preciousMetals=Precious Metals
|
||||
shared.all=All
|
||||
shared.edit=Edit
|
||||
shared.advancedOptions=Advanced options
|
||||
|
@ -245,8 +247,8 @@ shared.taker=Taker
|
|||
####################################################################
|
||||
|
||||
mainView.menu.market=Market
|
||||
mainView.menu.buy=Buy
|
||||
mainView.menu.sell=Sell
|
||||
mainView.menu.buyXmr=Buy XMR
|
||||
mainView.menu.sellXmr=Sell XMR
|
||||
mainView.menu.portfolio=Portfolio
|
||||
mainView.menu.funds=Funds
|
||||
mainView.menu.support=Support
|
||||
|
@ -375,6 +377,8 @@ offerbook.timeSinceSigning.tooltip.checkmark.buyXmr=buy XMR from a signed accoun
|
|||
offerbook.timeSinceSigning.tooltip.checkmark.wait=wait a minimum of {0} days
|
||||
offerbook.timeSinceSigning.tooltip.learnMore=Learn more
|
||||
offerbook.xmrAutoConf=Is auto-confirm enabled
|
||||
offerbook.buyXmrWith=Buy XMR with:
|
||||
offerbook.sellXmrFor=Sell XMR for:
|
||||
|
||||
offerbook.timeSinceSigning.help=When you successfully complete a trade with a peer who has a signed payment account, your payment account is signed.\n\
|
||||
{0} days later, the initial limit of {1} is lifted and your account can sign other peers'' payment accounts.
|
||||
|
@ -389,7 +393,7 @@ offerbook.volume={0} (min - max)
|
|||
offerbook.deposit=Deposit XMR (%)
|
||||
offerbook.deposit.help=Deposit paid by each trader to guarantee the trade. Will be returned when the trade is completed.
|
||||
|
||||
offerbook.createNewOffer=Create new offer to {0} {1}
|
||||
offerbook.createNewOffer=Create offer to {0} {1}
|
||||
offerbook.createOfferDisabled.tooltip=You can only create one offer at a time
|
||||
|
||||
offerbook.takeOfferButton.tooltip=Take offer for {0}
|
||||
|
|
|
@ -139,6 +139,7 @@ shared.addNewAccount=Přidat nový účet
|
|||
shared.ExportAccounts=Exportovat účty
|
||||
shared.importAccounts=Importovat účty
|
||||
shared.createNewAccount=Vytvořit nový účet
|
||||
shared.createNewAccountDescription=Vaše údaje o účtu jsou uloženy místně na vašem zařízení a sdíleny pouze s vaším obchodním partnerem a rozhodcem, pokud dojde k otevření sporu.
|
||||
shared.saveNewAccount=Uložit nový účet
|
||||
shared.selectedAccount=Vybraný účet
|
||||
shared.deleteAccount=Smazat účet
|
||||
|
@ -192,6 +193,7 @@ shared.iConfirm=Potvrzuji
|
|||
shared.openURL=Otevřené {0}
|
||||
shared.fiat=Fiat
|
||||
shared.crypto=Krypto
|
||||
shared.preciousMetals=Drahé kovy
|
||||
shared.all=Vše
|
||||
shared.edit=Upravit
|
||||
shared.advancedOptions=Pokročilé možnosti
|
||||
|
@ -348,6 +350,8 @@ offerbook.timeSinceSigning.info.banned=účet byl zablokován
|
|||
offerbook.timeSinceSigning.daysSinceSigning={0} dní
|
||||
offerbook.timeSinceSigning.daysSinceSigning.long={0} od podpisu
|
||||
offerbook.xmrAutoConf=Je automatické potvrzení povoleno
|
||||
offerbook.buyXmrWith=Kupte XMR za:
|
||||
offerbook.sellXmrFor=Prodat XMR za:
|
||||
|
||||
offerbook.timeSinceSigning.help=Když úspěšně dokončíte obchod s uživatelem, který má podepsaný platební účet, je váš platební účet podepsán.\n{0} dní později se počáteční limit {1} zruší a váš účet může podepisovat platební účty ostatních uživatelů.
|
||||
offerbook.timeSinceSigning.notSigned=Dosud nepodepsáno
|
||||
|
@ -362,6 +366,7 @@ offerbook.nrOffers=Počet nabídek: {0}
|
|||
offerbook.volume={0} (min - max)
|
||||
offerbook.deposit=Kauce XMR (%)
|
||||
offerbook.deposit.help=Kauce zaplacená každým obchodníkem k zajištění obchodu. Bude vrácena po dokončení obchodu.
|
||||
offerbook.createNewOffer=Vytvořit nabídku pro {0} {1}
|
||||
|
||||
offerbook.createOfferToBuy=Vytvořit novou nabídku k nákupu {0}
|
||||
offerbook.createOfferToSell=Vytvořit novou nabídku k prodeji {0}
|
||||
|
|
|
@ -139,6 +139,7 @@ shared.addNewAccount=Neues Konto hinzufügen
|
|||
shared.ExportAccounts=Konten exportieren
|
||||
shared.importAccounts=Konten importieren
|
||||
shared.createNewAccount=Neues Konto erstellen
|
||||
shared.createNewAccountDescription=Ihre Kontodaten werden lokal auf Ihrem Gerät gespeichert und nur mit Ihrem Handelspartner und dem Schiedsrichter geteilt, wenn ein Streitfall eröffnet wird.
|
||||
shared.saveNewAccount=Neues Konto speichern
|
||||
shared.selectedAccount=Konto auswählen
|
||||
shared.deleteAccount=Konto löschen
|
||||
|
@ -192,6 +193,7 @@ shared.iConfirm=Ich bestätige
|
|||
shared.openURL=Öffne {0}
|
||||
shared.fiat=Fiat
|
||||
shared.crypto=Crypto
|
||||
shared.preciousMetals=Edelmetalle
|
||||
shared.all=Alle
|
||||
shared.edit=Bearbeiten
|
||||
shared.advancedOptions=Erweiterte Optionen
|
||||
|
@ -348,6 +350,8 @@ offerbook.timeSinceSigning.info.banned=Konto wurde geblockt
|
|||
offerbook.timeSinceSigning.daysSinceSigning={0} Tage
|
||||
offerbook.timeSinceSigning.daysSinceSigning.long={0} seit der Unterzeichnung
|
||||
offerbook.xmrAutoConf=Automatische Bestätigung aktiviert
|
||||
offerbook.buyXmrWith=XMR kaufen mit:
|
||||
offerbook.sellXmrFor=XMR verkaufen für:
|
||||
|
||||
offerbook.timeSinceSigning.help=Wenn Sie einen Trade mit einem Partner erfolgreich abschließen, der ein unterzeichnetes Zahlungskonto hat, wird Ihr Zahlungskonto unterzeichnet.\n{0} Tage später wird das anfängliche Limit von {1} aufgehoben und Ihr Konto kann die Zahlungskonten anderer Partner unterzeichnen.
|
||||
offerbook.timeSinceSigning.notSigned=Noch nicht unterzeichnet
|
||||
|
@ -362,6 +366,7 @@ offerbook.nrOffers=Anzahl der Angebote: {0}
|
|||
offerbook.volume={0} (min - max)
|
||||
offerbook.deposit=Kaution XMR (%)
|
||||
offerbook.deposit.help=Kaution die von beiden Handelspartnern bezahlt werden muss, um den Handel abzusichern. Wird zurückgezahlt, wenn der Handel erfolgreich abgeschlossen wurde.
|
||||
offerbook.createNewOffer=Erstelle Angebot an {0} {1}
|
||||
|
||||
offerbook.createOfferToBuy=Neues Angebot erstellen, um {0} zu kaufen
|
||||
offerbook.createOfferToSell=Neues Angebot erstellen, um {0} zu verkaufen
|
||||
|
|
|
@ -139,6 +139,7 @@ shared.addNewAccount=Añadir una nueva cuenta
|
|||
shared.ExportAccounts=Exportar cuentas
|
||||
shared.importAccounts=Importar cuentas
|
||||
shared.createNewAccount=Crear nueva cuenta
|
||||
shared.createNewAccountDescription=Los detalles de su cuenta se almacenan localmente en su dispositivo y se comparten solo con su contraparte comercial y el árbitro si se abre una disputa.
|
||||
shared.saveNewAccount=Guardar nueva cuenta
|
||||
shared.selectedAccount=Cuenta seleccionada
|
||||
shared.deleteAccount=Borrar cuenta
|
||||
|
@ -192,6 +193,7 @@ shared.iConfirm=Confirmo
|
|||
shared.openURL=Abrir {0}
|
||||
shared.fiat=Fiat
|
||||
shared.crypto=Cripto
|
||||
shared.preciousMetals=Metales Preciosos
|
||||
shared.all=Todos
|
||||
shared.edit=Editar
|
||||
shared.advancedOptions=Opciones avanzadas
|
||||
|
@ -348,6 +350,8 @@ offerbook.timeSinceSigning.info.banned=La cuenta fue bloqueada
|
|||
offerbook.timeSinceSigning.daysSinceSigning={0} días
|
||||
offerbook.timeSinceSigning.daysSinceSigning.long={0} desde el firmado
|
||||
offerbook.xmrAutoConf=¿Está habilitada la confirmación automática?
|
||||
offerbook.buyXmrWith=Compra XMR con:
|
||||
offerbook.sellXmrFor=Vender XMR por:
|
||||
|
||||
offerbook.timeSinceSigning.help=Cuando complete con éxito un intercambio con un par que tenga una cuenta de pago firmada, su cuenta de pago es firmada.\n{0} días después, el límite inicial de {1} se eleva y su cuenta puede firmar tras cuentas de pago.
|
||||
offerbook.timeSinceSigning.notSigned=No firmada aún
|
||||
|
@ -362,6 +366,7 @@ offerbook.nrOffers=Número de ofertas: {0}
|
|||
offerbook.volume={0} (min - max)
|
||||
offerbook.deposit=Depósito en XMR (%)
|
||||
offerbook.deposit.help=Depósito pagado por cada comerciante para garantizar el intercambio. Será devuelto al acabar el intercambio.
|
||||
offerbook.createNewOffer=Crear oferta a {0} {1}
|
||||
|
||||
offerbook.createOfferToBuy=Crear nueva oferta para comprar {0}
|
||||
offerbook.createOfferToSell=Crear nueva oferta para vender {0}
|
||||
|
|
|
@ -139,6 +139,7 @@ shared.addNewAccount=افزودن حساب جدید
|
|||
shared.ExportAccounts=صادر کردن حسابها
|
||||
shared.importAccounts=وارد کردن حسابها
|
||||
shared.createNewAccount=ایجاد حساب جدید
|
||||
shared.createNewAccountDescription=جزئیات حساب شما بهطور محلی بر روی دستگاه شما ذخیره شده و تنها با همتجارت شما و داور در صورت باز شدن یک اختلاف به اشتراک گذاشته میشود.
|
||||
shared.saveNewAccount=ذخیرهی حساب جدید
|
||||
shared.selectedAccount=حساب انتخاب شده
|
||||
shared.deleteAccount=حذف حساب
|
||||
|
@ -192,6 +193,7 @@ shared.iConfirm=تایید میکنم
|
|||
shared.openURL=باز {0}
|
||||
shared.fiat=فیات
|
||||
shared.crypto=کریپتو
|
||||
shared.preciousMetals=فلزات گرانبها
|
||||
shared.all=همه
|
||||
shared.edit=ویرایش
|
||||
shared.advancedOptions=گزینههای پیشرفته
|
||||
|
@ -348,6 +350,8 @@ offerbook.timeSinceSigning.info.banned=account was banned
|
|||
offerbook.timeSinceSigning.daysSinceSigning={0} روز
|
||||
offerbook.timeSinceSigning.daysSinceSigning.long={0} since signing
|
||||
offerbook.xmrAutoConf=Is auto-confirm enabled
|
||||
offerbook.buyXmrWith=با XMR خرید کنید:
|
||||
offerbook.sellXmrFor=فروش XMR برای:
|
||||
|
||||
offerbook.timeSinceSigning.help=When you successfully complete a trade with a peer who has a signed payment account, your payment account is signed.\n{0} days later, the initial limit of {1} is lifted and your account can sign other peers'' payment accounts.
|
||||
offerbook.timeSinceSigning.notSigned=Not signed yet
|
||||
|
@ -362,6 +366,7 @@ offerbook.nrOffers=تعداد پیشنهادها: {0}
|
|||
offerbook.volume={0} (حداقل - حداکثر)
|
||||
offerbook.deposit=Deposit XMR (%)
|
||||
offerbook.deposit.help=Deposit paid by each trader to guarantee the trade. Will be returned when the trade is completed.
|
||||
offerbook.createNewOffer=پیشنهاد ایجاد کنید به {0} {1}
|
||||
|
||||
offerbook.createOfferToBuy=پیشنهاد جدید برای خرید {0} ایجاد کن
|
||||
offerbook.createOfferToSell=پیشنهاد جدید برای فروش {0} ایجاد کن
|
||||
|
|
|
@ -139,6 +139,7 @@ shared.addNewAccount=Ajouter un nouveau compte
|
|||
shared.ExportAccounts=Exporter les comptes
|
||||
shared.importAccounts=Importer les comptes
|
||||
shared.createNewAccount=Créer un nouveau compte
|
||||
shared.createNewAccountDescription=Les détails de votre compte sont stockés localement sur votre appareil et partagés uniquement avec votre pair de trading et l'arbitre si un litige est ouvert.
|
||||
shared.saveNewAccount=Sauvegarder un nouveau compte
|
||||
shared.selectedAccount=Sélectionner un compte
|
||||
shared.deleteAccount=Supprimer le compte
|
||||
|
@ -192,6 +193,7 @@ shared.iConfirm=Je confirme
|
|||
shared.openURL=Ouvert {0}
|
||||
shared.fiat=Fiat
|
||||
shared.crypto=Crypto
|
||||
shared.preciousMetals=Métaux précieux
|
||||
shared.all=Tout
|
||||
shared.edit=Modifier
|
||||
shared.advancedOptions=Options avancées
|
||||
|
@ -348,6 +350,8 @@ offerbook.timeSinceSigning.info.banned=Ce compte a été banni
|
|||
offerbook.timeSinceSigning.daysSinceSigning={0} jours
|
||||
offerbook.timeSinceSigning.daysSinceSigning.long={0} depuis la signature
|
||||
offerbook.xmrAutoConf=Est-ce-que la confirmation automatique est activée
|
||||
offerbook.buyXmrWith=Acheter XMR avec :
|
||||
offerbook.sellXmrFor=Vendre XMR pour :
|
||||
|
||||
offerbook.timeSinceSigning.help=Lorsque vous effectuez avec succès une transaction avec un pair disposant d''un compte de paiement signé, votre compte de paiement est signé.\n{0} Jours plus tard, la limite initiale de {1} est levée et votre compte peut signer les comptes de paiement d''un autre pair.
|
||||
offerbook.timeSinceSigning.notSigned=Pas encore signé
|
||||
|
@ -362,6 +366,7 @@ offerbook.nrOffers=Nombre d''ordres: {0}
|
|||
offerbook.volume={0} (min - max)
|
||||
offerbook.deposit=Déposer XMR (%)
|
||||
offerbook.deposit.help=Les deux parties à la transaction ont payé un dépôt pour assurer que la transaction se déroule normalement. Ce montant sera remboursé une fois la transaction terminée.
|
||||
offerbook.createNewOffer=Créer une offre à {0} {1}
|
||||
|
||||
offerbook.createOfferToBuy=Créer un nouvel ordre d''achat pour {0}
|
||||
offerbook.createOfferToSell=Créer un nouvel ordre de vente pour {0}
|
||||
|
|
|
@ -139,6 +139,7 @@ shared.addNewAccount=Aggiungi nuovo account
|
|||
shared.ExportAccounts=Esporta Account
|
||||
shared.importAccounts=Importa Account
|
||||
shared.createNewAccount=Crea nuovo account
|
||||
shared.createNewAccountDescription=I dettagli del tuo account sono memorizzati localmente sul tuo dispositivo e condivisi solo con il tuo partner commerciale e l'arbitro se viene aperta una disputa.
|
||||
shared.saveNewAccount=Salva nuovo account
|
||||
shared.selectedAccount=Account selezionato
|
||||
shared.deleteAccount=Elimina account
|
||||
|
@ -192,6 +193,7 @@ shared.iConfirm=Confermo
|
|||
shared.openURL=Aperti {0}
|
||||
shared.fiat=Fiat
|
||||
shared.crypto=Crypto
|
||||
shared.preciousMetals=Metalli Preziosi
|
||||
shared.all=Tutti
|
||||
shared.edit=Modifica
|
||||
shared.advancedOptions=Opzioni avanzate
|
||||
|
@ -348,6 +350,8 @@ offerbook.timeSinceSigning.info.banned= \nl'account è stato bannato
|
|||
offerbook.timeSinceSigning.daysSinceSigning={0} giorni
|
||||
offerbook.timeSinceSigning.daysSinceSigning.long={0} dalla firma
|
||||
offerbook.xmrAutoConf=Is auto-confirm enabled
|
||||
offerbook.buyXmrWith=Compra XMR con:
|
||||
offerbook.sellXmrFor=Vendi XMR per:
|
||||
|
||||
offerbook.timeSinceSigning.help=Quando completi correttamente un'operazione con un peer che ha un account di pagamento firmato, il tuo account di pagamento viene firmato.\n{0} giorni dopo, il limite iniziale di {1} viene alzato e il tuo account può firmare account di pagamento di altri peer.
|
||||
offerbook.timeSinceSigning.notSigned=Non ancora firmato
|
||||
|
@ -362,6 +366,7 @@ offerbook.nrOffers=N. di offerte: {0}
|
|||
offerbook.volume={0} (min - max)
|
||||
offerbook.deposit=Deposit XMR (%)
|
||||
offerbook.deposit.help=Deposit paid by each trader to guarantee the trade. Will be returned when the trade is completed.
|
||||
offerbook.createNewOffer=Crea offerta per {0} {1}
|
||||
|
||||
offerbook.createOfferToBuy=Crea una nuova offerta per comprare {0}
|
||||
offerbook.createOfferToSell=Crea una nuova offerta per vendere {0}
|
||||
|
|
|
@ -139,6 +139,7 @@ shared.addNewAccount=アカウントを追加
|
|||
shared.ExportAccounts=アカウントをエクスポート
|
||||
shared.importAccounts=アカウントをインポート
|
||||
shared.createNewAccount=新しいアカウントを作る
|
||||
shared.createNewAccountDescription=あなたのアカウント詳細は、デバイスにローカルに保存され、取引相手および紛争が発生した場合には仲裁人とのみ共有されます。
|
||||
shared.saveNewAccount=新しいアカウントを保存する
|
||||
shared.selectedAccount=選択したアカウント
|
||||
shared.deleteAccount=アカウントを削除
|
||||
|
@ -192,6 +193,7 @@ shared.iConfirm=確認します
|
|||
shared.openURL={0} をオープン
|
||||
shared.fiat=法定通貨
|
||||
shared.crypto=暗号通貨
|
||||
shared.preciousMetals=貴金属
|
||||
shared.all=全て
|
||||
shared.edit=編集
|
||||
shared.advancedOptions=高度なオプション
|
||||
|
@ -348,6 +350,8 @@ offerbook.timeSinceSigning.info.banned=このアカウントは禁止されま
|
|||
offerbook.timeSinceSigning.daysSinceSigning={0}日
|
||||
offerbook.timeSinceSigning.daysSinceSigning.long=署名する後から {0}
|
||||
offerbook.xmrAutoConf=自動確認は有効されますか?
|
||||
offerbook.buyXmrWith=XMRを購入:
|
||||
offerbook.sellXmrFor=XMRを売る:
|
||||
|
||||
offerbook.timeSinceSigning.help=署名された支払いアカウントを持っているピアと成功にトレードすると、自身の支払いアカウントも署名されることになります。\n{0} 日後に、{1} という初期の制限は解除され、他のピアの支払いアカウントを署名できるようになります。
|
||||
offerbook.timeSinceSigning.notSigned=まだ署名されていません
|
||||
|
@ -362,6 +366,7 @@ offerbook.nrOffers=オファー数: {0}
|
|||
offerbook.volume={0} (下限 - 上限)
|
||||
offerbook.deposit=XMRの敷金(%)
|
||||
offerbook.deposit.help=トレードを保証するため、両方の取引者が支払う敷金。トレードが完了されたら、返還されます。
|
||||
offerbook.createNewOffer={0} {1}にオファーを作成する
|
||||
|
||||
offerbook.createOfferToBuy={0} を購入するオファーを作成
|
||||
offerbook.createOfferToSell={0} を売却するオファーを作成
|
||||
|
|
|
@ -139,6 +139,7 @@ shared.addNewAccount=Adicionar conta nova
|
|||
shared.ExportAccounts=Exportar Contas
|
||||
shared.importAccounts=Importar Contas
|
||||
shared.createNewAccount=Criar nova conta
|
||||
shared.createNewAccountDescription=Os detalhes da sua conta são armazenados localmente no seu dispositivo e compartilhados apenas com seu parceiro de negociação e o árbitro, caso uma disputa seja aberta.
|
||||
shared.saveNewAccount=Salvar nova conta
|
||||
shared.selectedAccount=Conta selecionada
|
||||
shared.deleteAccount=Apagar conta
|
||||
|
@ -192,6 +193,7 @@ shared.iConfirm=Eu confirmo
|
|||
shared.openURL=Aberto {0}
|
||||
shared.fiat=Fiat
|
||||
shared.crypto=Cripto
|
||||
shared.preciousMetals=Metais Preciosos
|
||||
shared.all=Todos
|
||||
shared.edit=Editar
|
||||
shared.advancedOptions=Opções avançadas
|
||||
|
@ -351,6 +353,8 @@ offerbook.timeSinceSigning.info.banned=conta foi banida
|
|||
offerbook.timeSinceSigning.daysSinceSigning={0} dias
|
||||
offerbook.timeSinceSigning.daysSinceSigning.long={0} desde a assinatura
|
||||
offerbook.xmrAutoConf=Is auto-confirm enabled
|
||||
offerbook.buyXmrWith=Compre XMR com:
|
||||
offerbook.sellXmrFor=Venda XMR por:
|
||||
|
||||
offerbook.timeSinceSigning.help=Quando você completa uma negociação bem sucedida com um par que tem uma conta de pagamento assinada, a sua conta de pagamento é assinada.\n{0} dias depois, o limite inicial de {1} é levantado e sua conta pode assinar as contas de pagamento de outros pares.
|
||||
offerbook.timeSinceSigning.notSigned=Ainda não assinada
|
||||
|
@ -365,6 +369,7 @@ offerbook.nrOffers=N.º de ofertas: {0}
|
|||
offerbook.volume={0} (mín. - máx.)
|
||||
offerbook.deposit=Deposit XMR (%)
|
||||
offerbook.deposit.help=Deposit paid by each trader to guarantee the trade. Will be returned when the trade is completed.
|
||||
offerbook.createNewOffer=Criar oferta para {0} {1}
|
||||
|
||||
offerbook.createOfferToBuy=Criar oferta para comprar {0}
|
||||
offerbook.createOfferToSell=Criar oferta para vender {0}
|
||||
|
|
|
@ -139,6 +139,7 @@ shared.addNewAccount=Adicionar uma nova conta
|
|||
shared.ExportAccounts=Exportar Contas
|
||||
shared.importAccounts=Importar Contas
|
||||
shared.createNewAccount=Criar nova conta
|
||||
shared.createNewAccountDescription=Os detalhes da sua conta são armazenados localmente no seu dispositivo e compartilhados apenas com seu parceiro de negociação e o árbitro, caso uma disputa seja aberta.
|
||||
shared.saveNewAccount=Guardar nova conta
|
||||
shared.selectedAccount=Conta selecionada
|
||||
shared.deleteAccount=Apagar conta
|
||||
|
@ -192,6 +193,7 @@ shared.iConfirm=Eu confirmo
|
|||
shared.openURL=Abrir {0}
|
||||
shared.fiat=Moeda fiduciária
|
||||
shared.crypto=Cripto
|
||||
shared.preciousMetals=TODO
|
||||
shared.all=Tudo
|
||||
shared.edit=Editar
|
||||
shared.advancedOptions=Opções avançadas
|
||||
|
@ -348,6 +350,8 @@ offerbook.timeSinceSigning.info.banned=account was banned
|
|||
offerbook.timeSinceSigning.daysSinceSigning={0} dias
|
||||
offerbook.timeSinceSigning.daysSinceSigning.long={0} desde a assinatura
|
||||
offerbook.xmrAutoConf=Is auto-confirm enabled
|
||||
offerbook.buyXmrWith=Compre XMR com:
|
||||
offerbook.sellXmrFor=Venda XMR por:
|
||||
|
||||
offerbook.timeSinceSigning.help=Quando você completa com sucesso um negócio com um par que tenha uma conta de pagamento assinada, a sua conta de pagamento é assinada .\n{0} dias depois, o limite inicial de {1} é aumentado e a sua conta pode assinar contas de pagamento de outros pares.
|
||||
offerbook.timeSinceSigning.notSigned=Ainda não assinada
|
||||
|
@ -362,6 +366,7 @@ offerbook.nrOffers=Nº de ofertas: {0}
|
|||
offerbook.volume={0} (mín - máx)
|
||||
offerbook.deposit=Deposit XMR (%)
|
||||
offerbook.deposit.help=Deposit paid by each trader to guarantee the trade. Will be returned when the trade is completed.
|
||||
offerbook.createNewOffer=Criar oferta para {0} {1}
|
||||
|
||||
offerbook.createOfferToBuy=Criar nova oferta para comprar {0}
|
||||
offerbook.createOfferToSell=Criar nova oferta para vender {0}
|
||||
|
|
|
@ -139,6 +139,7 @@ shared.addNewAccount=Добавить новый счёт
|
|||
shared.ExportAccounts=Экспортировать счета
|
||||
shared.importAccounts=Импортировать счета
|
||||
shared.createNewAccount=Создать новый счёт
|
||||
shared.createNewAccountDescription=Данные вашей учетной записи хранятся локально на вашем устройстве и передаются только вашему торговому партнеру и арбитру, если открывается спор.
|
||||
shared.saveNewAccount=Сохранить новый счёт
|
||||
shared.selectedAccount=Выбранный счёт
|
||||
shared.deleteAccount=Удалить счёт
|
||||
|
@ -192,6 +193,7 @@ shared.iConfirm=Подтверждаю
|
|||
shared.openURL=Открыть {0}
|
||||
shared.fiat=Нац. валюта
|
||||
shared.crypto=Криптовалюта
|
||||
shared.preciousMetals=Драгоценные металлы
|
||||
shared.all=Все
|
||||
shared.edit=Редактировать
|
||||
shared.advancedOptions=Дополнительные настройки
|
||||
|
@ -348,6 +350,8 @@ offerbook.timeSinceSigning.info.banned=account was banned
|
|||
offerbook.timeSinceSigning.daysSinceSigning={0} дн.
|
||||
offerbook.timeSinceSigning.daysSinceSigning.long={0} since signing
|
||||
offerbook.xmrAutoConf=Is auto-confirm enabled
|
||||
offerbook.buyXmrWith=Купить XMR с помощью:
|
||||
offerbook.sellXmrFor=Продать XMR за:
|
||||
|
||||
offerbook.timeSinceSigning.help=When you successfully complete a trade with a peer who has a signed payment account, your payment account is signed.\n{0} days later, the initial limit of {1} is lifted and your account can sign other peers'' payment accounts.
|
||||
offerbook.timeSinceSigning.notSigned=Not signed yet
|
||||
|
@ -362,6 +366,7 @@ offerbook.nrOffers=Кол-во предложений: {0}
|
|||
offerbook.volume={0} (мин. — макс.)
|
||||
offerbook.deposit=Deposit XMR (%)
|
||||
offerbook.deposit.help=Deposit paid by each trader to guarantee the trade. Will be returned when the trade is completed.
|
||||
offerbook.createNewOffer=Создать предложение для {0} {1}
|
||||
|
||||
offerbook.createOfferToBuy=Создать новое предложение на покупку {0}
|
||||
offerbook.createOfferToSell=Создать новое предложение на продажу {0}
|
||||
|
|
|
@ -139,6 +139,7 @@ shared.addNewAccount=เพิ่มบัญชีใหม่
|
|||
shared.ExportAccounts=บัญชีส่งออก
|
||||
shared.importAccounts=บัญชีนำเข้า
|
||||
shared.createNewAccount=สร้างบัญชีใหม่
|
||||
shared.createNewAccountDescription=รายละเอียดบัญชีของคุณถูกจัดเก็บไว้ในอุปกรณ์ของคุณและจะแบ่งปันเฉพาะกับคู่ค้าของคุณและผู้ตัดสินหากมีการเปิดข้อพิพาท
|
||||
shared.saveNewAccount=บันทึกบัญชีใหม่
|
||||
shared.selectedAccount=บัญชีที่เลือก
|
||||
shared.deleteAccount=ลบบัญชี
|
||||
|
@ -192,6 +193,7 @@ shared.iConfirm=ฉันยืนยัน
|
|||
shared.openURL=เปิด {0}
|
||||
shared.fiat=คำสั่ง
|
||||
shared.crypto=คริปโต
|
||||
shared.preciousMetals=โลหะมีค่า
|
||||
shared.all=ทั้งหมด
|
||||
shared.edit=แก้ไข
|
||||
shared.advancedOptions=ทางเลือกขั้นสูง
|
||||
|
@ -348,6 +350,8 @@ offerbook.timeSinceSigning.info.banned=account was banned
|
|||
offerbook.timeSinceSigning.daysSinceSigning={0} วัน
|
||||
offerbook.timeSinceSigning.daysSinceSigning.long={0} since signing
|
||||
offerbook.xmrAutoConf=Is auto-confirm enabled
|
||||
offerbook.buyXmrWith=ซื้อ XMR ด้วย:
|
||||
offerbook.sellXmrFor=ขาย XMR สำหรับ:
|
||||
|
||||
offerbook.timeSinceSigning.help=When you successfully complete a trade with a peer who has a signed payment account, your payment account is signed.\n{0} days later, the initial limit of {1} is lifted and your account can sign other peers'' payment accounts.
|
||||
offerbook.timeSinceSigning.notSigned=Not signed yet
|
||||
|
@ -362,6 +366,7 @@ offerbook.nrOffers=No. ของข้อเสนอ: {0}
|
|||
offerbook.volume={0} (ต่ำสุด - สูงสุด)
|
||||
offerbook.deposit=Deposit XMR (%)
|
||||
offerbook.deposit.help=Deposit paid by each trader to guarantee the trade. Will be returned when the trade is completed.
|
||||
offerbook.createNewOffer=สร้างข้อเสนอให้กับ {0} {1}
|
||||
|
||||
offerbook.createOfferToBuy=Create new offer to buy {0}
|
||||
offerbook.createOfferToSell=Create new offer to sell {0}
|
||||
|
|
|
@ -150,6 +150,7 @@ shared.addNewAccount=Yeni hesap ekle
|
|||
shared.ExportAccounts=Hesapları Dışa Aktar
|
||||
shared.importAccounts=Hesapları İçe Aktar
|
||||
shared.createNewAccount=Yeni hesap oluştur
|
||||
shared.createNewAccountDescription=Hesap bilgileriniz yerel olarak cihazınızda saklanır ve yalnızca ticaret ortağınızla ve bir anlaşmazlık açılırsa hakemle paylaşılır.
|
||||
shared.saveNewAccount=Yeni hesabı kaydet
|
||||
shared.selectedAccount=Seçilen hesap
|
||||
shared.deleteAccount=Hesabı sil
|
||||
|
@ -204,6 +205,7 @@ shared.iConfirm=Onaylıyorum
|
|||
shared.openURL={0}'i aç
|
||||
shared.fiat=Fiat
|
||||
shared.crypto=Kripto
|
||||
shared.preciousMetals=Değerli Madenler
|
||||
shared.traditional=Nakit
|
||||
shared.otherAssets=diğer varlıklar
|
||||
shared.other=Diğer
|
||||
|
@ -245,8 +247,8 @@ shared.taker=Alıcı
|
|||
####################################################################
|
||||
|
||||
mainView.menu.market=Piyasa
|
||||
mainView.menu.buy=Satın Al
|
||||
mainView.menu.sell=Sat
|
||||
mainView.menu.buyXmr=XMR Satın Al
|
||||
mainView.menu.sellXmr=XMR Sat
|
||||
mainView.menu.portfolio=Portföy
|
||||
mainView.menu.funds=Fonlar
|
||||
mainView.menu.support=Destek
|
||||
|
@ -372,6 +374,8 @@ offerbook.timeSinceSigning.tooltip.checkmark.buyXmr=imzalı bir hesaptan XMR al
|
|||
offerbook.timeSinceSigning.tooltip.checkmark.wait=minimal {0} gün bekleyin
|
||||
offerbook.timeSinceSigning.tooltip.learnMore=Daha fazla bilgi edin
|
||||
offerbook.xmrAutoConf=Otomatik onay etkin mi
|
||||
offerbook.buyXmrWith=XMR satın al:
|
||||
offerbook.sellXmrFor=XMR'i şunlar için satın:
|
||||
|
||||
offerbook.timeSinceSigning.help=Bir imzalı ödeme hesabı olan bir eş ile başarılı bir şekilde işlem yaptığınızda, ödeme hesabınız imzalanır.\n\
|
||||
{0} gün sonra, başlangıç limiti {1} kaldırılır ve hesabınız diğer eşlerin ödeme hesaplarını imzalayabilir.
|
||||
|
@ -386,7 +390,7 @@ offerbook.volume={0} (min - maks)
|
|||
offerbook.deposit=Mevduat XMR (%)
|
||||
offerbook.deposit.help=Her yatırımcı tarafından işlemi garanti altına almak için ödenen mevduat. İşlem tamamlandığında geri verilecektir.
|
||||
|
||||
offerbook.createNewOffer=teklif aç {0} {1}
|
||||
offerbook.createNewOffer=Teklif oluştur {0} {1}
|
||||
offerbook.createOfferDisabled.tooltip=Bir seferde sadece bir teklif oluşturabilirsiniz
|
||||
|
||||
offerbook.takeOfferButton.tooltip=Teklifi al {0}
|
||||
|
|
|
@ -139,6 +139,7 @@ shared.addNewAccount=Thêm tài khoản mới
|
|||
shared.ExportAccounts=Truy xuất tài khoản
|
||||
shared.importAccounts=Truy nhập tài khoản
|
||||
shared.createNewAccount=Tạo tài khoản mới
|
||||
shared.createNewAccountDescription=Thông tin tài khoản của bạn được lưu trữ cục bộ trên thiết bị của bạn và chỉ được chia sẻ với đối tác giao dịch của bạn và trọng tài nếu xảy ra tranh chấp.
|
||||
shared.saveNewAccount=Lưu tài khoản mới
|
||||
shared.selectedAccount=Tài khoản được chọn
|
||||
shared.deleteAccount=Xóa tài khoản
|
||||
|
@ -192,6 +193,7 @@ shared.iConfirm=Tôi xác nhận
|
|||
shared.openURL=Mở {0}
|
||||
shared.fiat=Tiền pháp định
|
||||
shared.crypto=Tiền mã hóa
|
||||
shared.preciousMetals=Kim loại quý
|
||||
shared.all=Tất cả
|
||||
shared.edit=Chỉnh sửa
|
||||
shared.advancedOptions=Tùy chọn nâng cao
|
||||
|
@ -348,6 +350,8 @@ offerbook.timeSinceSigning.info.banned=account was banned
|
|||
offerbook.timeSinceSigning.daysSinceSigning={0} ngày
|
||||
offerbook.timeSinceSigning.daysSinceSigning.long={0} since signing
|
||||
offerbook.xmrAutoConf=Is auto-confirm enabled
|
||||
offerbook.buyXmrWith=Mua XMR với:
|
||||
offerbook.sellXmrFor=Bán XMR để:
|
||||
|
||||
offerbook.timeSinceSigning.help=When you successfully complete a trade with a peer who has a signed payment account, your payment account is signed.\n{0} days later, the initial limit of {1} is lifted and your account can sign other peers'' payment accounts.
|
||||
offerbook.timeSinceSigning.notSigned=Not signed yet
|
||||
|
@ -362,6 +366,7 @@ offerbook.nrOffers=Số chào giá: {0}
|
|||
offerbook.volume={0} (min - max)
|
||||
offerbook.deposit=Deposit XMR (%)
|
||||
offerbook.deposit.help=Deposit paid by each trader to guarantee the trade. Will be returned when the trade is completed.
|
||||
offerbook.createNewOffer=Tạo ưu đãi cho {0} {1}
|
||||
|
||||
offerbook.createOfferToBuy=Tạo chào giá mua mới {0}
|
||||
offerbook.createOfferToSell=Tạo chào giá bán mới {0}
|
||||
|
|
|
@ -139,6 +139,7 @@ shared.addNewAccount=添加新的账户
|
|||
shared.ExportAccounts=导出账户
|
||||
shared.importAccounts=导入账户
|
||||
shared.createNewAccount=创建新的账户
|
||||
shared.createNewAccountDescription=您的账户详情存储在您的设备上,仅与您的交易对手和仲裁员在出现争议时共享。
|
||||
shared.saveNewAccount=保存新的账户
|
||||
shared.selectedAccount=选中的账户
|
||||
shared.deleteAccount=删除账户
|
||||
|
@ -192,6 +193,7 @@ shared.iConfirm=我确认
|
|||
shared.openURL=打开 {0}
|
||||
shared.fiat=法定货币
|
||||
shared.crypto=加密
|
||||
shared.preciousMetals=贵金属
|
||||
shared.all=全部
|
||||
shared.edit=编辑
|
||||
shared.advancedOptions=高级选项
|
||||
|
@ -348,6 +350,8 @@ offerbook.timeSinceSigning.info.banned=账户已被封禁
|
|||
offerbook.timeSinceSigning.daysSinceSigning={0} 天
|
||||
offerbook.timeSinceSigning.daysSinceSigning.long=自验证{0}
|
||||
offerbook.xmrAutoConf=是否开启自动确认
|
||||
offerbook.buyXmrWith=使用以下方式购买 XMR:
|
||||
offerbook.sellXmrFor=出售 XMR 以换取:
|
||||
|
||||
offerbook.timeSinceSigning.help=当您成功地完成与拥有已验证付款帐户的伙伴交易时,您的付款帐户已验证。\n{0} 天后,最初的 {1} 的限制解除以及你的账户可以验证其他人的付款账户。
|
||||
offerbook.timeSinceSigning.notSigned=尚未验证
|
||||
|
@ -362,6 +366,7 @@ offerbook.nrOffers=报价数量:{0}
|
|||
offerbook.volume={0}(最小 - 最大)
|
||||
offerbook.deposit=XMR 保证金(%)
|
||||
offerbook.deposit.help=交易双方均已支付保证金确保这个交易正常进行。这会在交易完成时退还。
|
||||
offerbook.createNewOffer=創建報價給 {0} {1}
|
||||
|
||||
offerbook.createOfferToBuy=创建新的报价来买入 {0}
|
||||
offerbook.createOfferToSell=创建新的报价来卖出 {0}
|
||||
|
|
|
@ -139,6 +139,7 @@ shared.addNewAccount=添加新的賬户
|
|||
shared.ExportAccounts=導出賬户
|
||||
shared.importAccounts=導入賬户
|
||||
shared.createNewAccount=創建新的賬户
|
||||
shared.createNewAccountDescription=您的帳戶詳細資料儲存在您的裝置上,僅在開啟爭議時與您的交易夥伴和仲裁者分享。
|
||||
shared.saveNewAccount=保存新的賬户
|
||||
shared.selectedAccount=選中的賬户
|
||||
shared.deleteAccount=刪除賬户
|
||||
|
@ -192,6 +193,7 @@ shared.iConfirm=我確認
|
|||
shared.openURL=打開 {0}
|
||||
shared.fiat=法定貨幣
|
||||
shared.crypto=加密
|
||||
shared.preciousMetals=貴金屬
|
||||
shared.all=全部
|
||||
shared.edit=編輯
|
||||
shared.advancedOptions=高級選項
|
||||
|
@ -348,6 +350,8 @@ offerbook.timeSinceSigning.info.banned=賬户已被封禁
|
|||
offerbook.timeSinceSigning.daysSinceSigning={0} 天
|
||||
offerbook.timeSinceSigning.daysSinceSigning.long=自驗證{0}
|
||||
offerbook.xmrAutoConf=是否開啟自動確認
|
||||
offerbook.buyXmrWith=購買 XMR 使用:
|
||||
offerbook.sellXmrFor=出售 XMR 以換取:
|
||||
|
||||
offerbook.timeSinceSigning.help=當您成功地完成與擁有已驗證付款帳户的夥伴交易時,您的付款帳户已驗證。\n{0} 天后,最初的 {1} 的限制解除以及你的賬户可以驗證其他人的付款賬户。
|
||||
offerbook.timeSinceSigning.notSigned=尚未驗證
|
||||
|
@ -362,6 +366,7 @@ offerbook.nrOffers=報價數量:{0}
|
|||
offerbook.volume={0}(最小 - 最大)
|
||||
offerbook.deposit=XMR 保證金(%)
|
||||
offerbook.deposit.help=交易雙方均已支付保證金確保這個交易正常進行。這會在交易完成時退還。
|
||||
offerbook.createNewOffer=創建報價給 {0} {1}
|
||||
|
||||
offerbook.createOfferToBuy=創建新的報價來買入 {0}
|
||||
offerbook.createOfferToSell=創建新的報價來賣出 {0}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue