mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-31 02:21:35 +00:00
Fix race condition onchain (#388)
* Fix race condition swaps * Collect new phrases * Increase random delay interval
This commit is contained in:
parent
2fd4a0123e
commit
42f208fad4
@ -1,6 +1,5 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import random
|
|
||||||
import secrets
|
import secrets
|
||||||
import time
|
import time
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
@ -129,7 +128,7 @@ class LNNode:
|
|||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pay_onchain(cls, onchainpayment, valid_code=1, on_mempool_code=2):
|
def pay_onchain(cls, onchainpayment, queue_code=5, on_mempool_code=2):
|
||||||
"""Send onchain transaction for buyer payouts"""
|
"""Send onchain transaction for buyer payouts"""
|
||||||
|
|
||||||
if config("DISABLE_ONCHAIN", cast=bool):
|
if config("DISABLE_ONCHAIN", cast=bool):
|
||||||
@ -144,9 +143,12 @@ class LNNode:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Cheap security measure to ensure there has been some non-deterministic time between request and DB check
|
# Cheap security measure to ensure there has been some non-deterministic time between request and DB check
|
||||||
time.sleep(random.uniform(0.5, 10))
|
delay = (
|
||||||
|
secrets.randbelow(2**256) / (2**256) * 10
|
||||||
|
) # Random uniform 0 to 5 secs with good entropy
|
||||||
|
time.sleep(3 + delay)
|
||||||
|
|
||||||
if onchainpayment.status == valid_code:
|
if onchainpayment.status == queue_code:
|
||||||
# Changing the state to "MEMPO" should be atomic with SendCoins.
|
# Changing the state to "MEMPO" should be atomic with SendCoins.
|
||||||
onchainpayment.status = on_mempool_code
|
onchainpayment.status = on_mempool_code
|
||||||
onchainpayment.save()
|
onchainpayment.save()
|
||||||
|
@ -481,9 +481,9 @@ class Logics:
|
|||||||
"bad_request": "Only orders in dispute accept dispute statements"
|
"bad_request": "Only orders in dispute accept dispute statements"
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(statement) > 5000:
|
if len(statement) > 10000:
|
||||||
return False, {
|
return False, {
|
||||||
"bad_statement": "The statement is longer than 5000 characters"
|
"bad_statement": "The statement is longer than 10000 characters"
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(statement) < 100:
|
if len(statement) < 100:
|
||||||
@ -554,7 +554,7 @@ class Logics:
|
|||||||
confirmed = onchain_payment.balance.onchain_confirmed
|
confirmed = onchain_payment.balance.onchain_confirmed
|
||||||
reserve = 300000 # We assume a reserve of 300K Sats (3 times higher than LND's default anchor reserve)
|
reserve = 300000 # We assume a reserve of 300K Sats (3 times higher than LND's default anchor reserve)
|
||||||
pending_txs = OnchainPayment.objects.filter(
|
pending_txs = OnchainPayment.objects.filter(
|
||||||
status=OnchainPayment.Status.VALID
|
status__in=[OnchainPayment.Status.VALID, OnchainPayment.Status.QUEUE]
|
||||||
).aggregate(Sum("num_satoshis"))["num_satoshis__sum"]
|
).aggregate(Sum("num_satoshis"))["num_satoshis__sum"]
|
||||||
|
|
||||||
if pending_txs is None:
|
if pending_txs is None:
|
||||||
@ -1049,13 +1049,6 @@ class Logics:
|
|||||||
safe_cltv_expiry_secs = cltv_expiry_secs * MAX_MINING_NETWORK_SPEEDUP_EXPECTED
|
safe_cltv_expiry_secs = cltv_expiry_secs * MAX_MINING_NETWORK_SPEEDUP_EXPECTED
|
||||||
# Convert to blocks using assummed average block time (~8 mins/block)
|
# Convert to blocks using assummed average block time (~8 mins/block)
|
||||||
cltv_expiry_blocks = int(safe_cltv_expiry_secs / (BLOCK_TIME * 60))
|
cltv_expiry_blocks = int(safe_cltv_expiry_secs / (BLOCK_TIME * 60))
|
||||||
print(
|
|
||||||
invoice_concept,
|
|
||||||
" cltv_expiry_hours:",
|
|
||||||
cltv_expiry_secs / 3600,
|
|
||||||
" cltv_expiry_blocks:",
|
|
||||||
cltv_expiry_blocks,
|
|
||||||
)
|
|
||||||
|
|
||||||
return cltv_expiry_blocks
|
return cltv_expiry_blocks
|
||||||
|
|
||||||
@ -1442,20 +1435,16 @@ class Logics:
|
|||||||
else:
|
else:
|
||||||
if not order.payout_tx.status == OnchainPayment.Status.VALID:
|
if not order.payout_tx.status == OnchainPayment.Status.VALID:
|
||||||
return False
|
return False
|
||||||
|
else:
|
||||||
valid = LNNode.pay_onchain(
|
# Add onchain payment to queue
|
||||||
order.payout_tx,
|
|
||||||
valid_code=OnchainPayment.Status.VALID,
|
|
||||||
on_mempool_code=OnchainPayment.Status.MEMPO,
|
|
||||||
)
|
|
||||||
if valid:
|
|
||||||
order.status = Order.Status.SUC
|
order.status = Order.Status.SUC
|
||||||
|
order.payout_tx.status = OnchainPayment.Status.QUEUE
|
||||||
|
order.payout_tx.save()
|
||||||
order.save()
|
order.save()
|
||||||
send_message.delay(order.id, "trade_successful")
|
send_message.delay(order.id, "trade_successful")
|
||||||
order.contract_finalization_time = timezone.now()
|
order.contract_finalization_time = timezone.now()
|
||||||
order.save()
|
order.save()
|
||||||
return True
|
return True
|
||||||
return False
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def confirm_fiat(cls, order, user):
|
def confirm_fiat(cls, order, user):
|
||||||
|
@ -8,7 +8,7 @@ from django.utils import timezone
|
|||||||
|
|
||||||
from api.lightning.node import LNNode
|
from api.lightning.node import LNNode
|
||||||
from api.logics import Logics
|
from api.logics import Logics
|
||||||
from api.models import LNPayment, Order
|
from api.models import LNPayment, OnchainPayment, Order
|
||||||
from api.tasks import follow_send_payment, send_message
|
from api.tasks import follow_send_payment, send_message
|
||||||
|
|
||||||
MACAROON = b64decode(config("LND_MACAROON_BASE64"))
|
MACAROON = b64decode(config("LND_MACAROON_BASE64"))
|
||||||
@ -16,7 +16,7 @@ MACAROON = b64decode(config("LND_MACAROON_BASE64"))
|
|||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
||||||
help = "Follows all active hold invoices"
|
help = "Follows all active hold invoices, sends out payments"
|
||||||
rest = 5 # seconds between consecutive checks for invoice updates
|
rest = 5 # seconds between consecutive checks for invoice updates
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
@ -130,6 +130,14 @@ class Command(BaseCommand):
|
|||||||
self.stdout.write(str(debug))
|
self.stdout.write(str(debug))
|
||||||
|
|
||||||
def send_payments(self):
|
def send_payments(self):
|
||||||
|
"""
|
||||||
|
Checks for invoices and onchain payments that are due to be paid.
|
||||||
|
Sends the payments.
|
||||||
|
"""
|
||||||
|
self.send_ln_payments()
|
||||||
|
self.send_onchain_payments()
|
||||||
|
|
||||||
|
def send_ln_payments(self):
|
||||||
"""
|
"""
|
||||||
Checks for invoices that are due to pay; i.e., INFLIGHT status and 0 routing_attempts.
|
Checks for invoices that are due to pay; i.e., INFLIGHT status and 0 routing_attempts.
|
||||||
Checks if any payment is due for retry, and tries to pay it.
|
Checks if any payment is due for retry, and tries to pay it.
|
||||||
@ -153,8 +161,36 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
queryset = queryset.union(queryset_retries)
|
queryset = queryset.union(queryset_retries)
|
||||||
|
|
||||||
for lnpayment in queryset:
|
if len(queryset) > 0:
|
||||||
follow_send_payment(lnpayment.payment_hash)
|
for lnpayment in queryset:
|
||||||
|
follow_send_payment.delay(lnpayment.payment_hash)
|
||||||
|
|
||||||
|
def send_onchain_payments(self):
|
||||||
|
|
||||||
|
queryset = OnchainPayment.objects.filter(
|
||||||
|
status=OnchainPayment.Status.QUEUE,
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(queryset) > 0:
|
||||||
|
for onchainpayment in queryset:
|
||||||
|
# Checks that this onchain payment is part of an order with a settled escrow
|
||||||
|
if not hasattr(onchainpayment, "order_paid_TX"):
|
||||||
|
self.stdout.write(
|
||||||
|
f"Onchain payment {str(onchainpayment)} has no parent order!"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
order = onchainpayment.order_paid_TX
|
||||||
|
if order.trade_escrow.status == LNPayment.Status.SETLED:
|
||||||
|
# Sends out onchainpayment
|
||||||
|
LNNode.pay_onchain(
|
||||||
|
onchainpayment,
|
||||||
|
OnchainPayment.Status.QUEUE,
|
||||||
|
OnchainPayment.Status.MEMPO,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.stdout.write(
|
||||||
|
f"Onchain payment {str(onchainpayment)} for order {str(order)} escrow is not settled!"
|
||||||
|
)
|
||||||
|
|
||||||
def update_order_status(self, lnpayment):
|
def update_order_status(self, lnpayment):
|
||||||
"""Background process following LND hold invoices
|
"""Background process following LND hold invoices
|
||||||
|
@ -185,10 +185,11 @@ class OnchainPayment(models.Model):
|
|||||||
|
|
||||||
class Status(models.IntegerChoices):
|
class Status(models.IntegerChoices):
|
||||||
CREAT = 0, "Created" # User was given platform fees and suggested mining fees
|
CREAT = 0, "Created" # User was given platform fees and suggested mining fees
|
||||||
VALID = 1, "Valid" # Valid onchain address submitted
|
VALID = 1, "Valid" # Valid onchain address and fee submitted
|
||||||
MEMPO = 2, "In mempool" # Tx is sent to mempool
|
MEMPO = 2, "In mempool" # Tx is sent to mempool
|
||||||
CONFI = 3, "Confirmed" # Tx is confirme +2 blocks
|
CONFI = 3, "Confirmed" # Tx is confirmed +2 blocks
|
||||||
CANCE = 4, "Cancelled" # Cancelled tx
|
CANCE = 4, "Cancelled" # Cancelled tx
|
||||||
|
QUEUE = 5, "Queued" # Payment is queued to be sent out
|
||||||
|
|
||||||
def get_balance():
|
def get_balance():
|
||||||
balance = BalanceLog.objects.create()
|
balance = BalanceLog.objects.create()
|
||||||
@ -441,10 +442,10 @@ class Order(models.Model):
|
|||||||
# in dispute
|
# in dispute
|
||||||
is_disputed = models.BooleanField(default=False, null=False)
|
is_disputed = models.BooleanField(default=False, null=False)
|
||||||
maker_statement = models.TextField(
|
maker_statement = models.TextField(
|
||||||
max_length=5000, null=True, default=None, blank=True
|
max_length=10000, null=True, default=None, blank=True
|
||||||
)
|
)
|
||||||
taker_statement = models.TextField(
|
taker_statement = models.TextField(
|
||||||
max_length=5000, null=True, default=None, blank=True
|
max_length=10000, null=True, default=None, blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# LNpayments
|
# LNpayments
|
||||||
|
@ -501,7 +501,7 @@ class UpdateOrderSerializer(serializers.Serializer):
|
|||||||
max_length=100, allow_null=True, allow_blank=True, default=None
|
max_length=100, allow_null=True, allow_blank=True, default=None
|
||||||
)
|
)
|
||||||
statement = serializers.CharField(
|
statement = serializers.CharField(
|
||||||
max_length=10000, allow_null=True, allow_blank=True, default=None
|
max_length=11000, allow_null=True, allow_blank=True, default=None
|
||||||
)
|
)
|
||||||
action = serializers.ChoiceField(
|
action = serializers.ChoiceField(
|
||||||
choices=(
|
choices=(
|
||||||
|
@ -97,6 +97,7 @@ def follow_send_payment(hash):
|
|||||||
payment_request=lnpayment.invoice,
|
payment_request=lnpayment.invoice,
|
||||||
fee_limit_sat=fee_limit_sat,
|
fee_limit_sat=fee_limit_sat,
|
||||||
timeout_seconds=timeout_seconds,
|
timeout_seconds=timeout_seconds,
|
||||||
|
allow_self_payment=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
order = lnpayment.order_paid_LN
|
order = lnpayment.order_paid_LN
|
||||||
|
@ -471,12 +471,15 @@ class OrderView(viewsets.ViewSet):
|
|||||||
if order.is_swap:
|
if order.is_swap:
|
||||||
data["num_satoshis"] = order.payout_tx.num_satoshis
|
data["num_satoshis"] = order.payout_tx.num_satoshis
|
||||||
data["sent_satoshis"] = order.payout_tx.sent_satoshis
|
data["sent_satoshis"] = order.payout_tx.sent_satoshis
|
||||||
|
data["network"] = str(config("NETWORK"))
|
||||||
if order.payout_tx.status in [
|
if order.payout_tx.status in [
|
||||||
OnchainPayment.Status.MEMPO,
|
OnchainPayment.Status.MEMPO,
|
||||||
OnchainPayment.Status.CONFI,
|
OnchainPayment.Status.CONFI,
|
||||||
]:
|
]:
|
||||||
data["txid"] = order.payout_tx.txid
|
data["txid"] = order.payout_tx.txid
|
||||||
data["network"] = str(config("NETWORK"))
|
elif order.payout_tx.status == OnchainPayment.Status.QUEUE:
|
||||||
|
data["tx_queued"] = True
|
||||||
|
data["address"] = order.payout_tx.address
|
||||||
|
|
||||||
return Response(data, status.HTTP_200_OK)
|
return Response(data, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
IconButton,
|
IconButton,
|
||||||
Button,
|
Button,
|
||||||
|
CircularProgress,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import currencies from '../../../../static/assets/currencies.json';
|
import currencies from '../../../../static/assets/currencies.json';
|
||||||
import TradeSummary from '../TradeSummary';
|
import TradeSummary from '../TradeSummary';
|
||||||
@ -150,6 +151,43 @@ export const SuccessfulPrompt = ({
|
|||||||
</Alert>
|
</Alert>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
|
||||||
|
<Collapse in={order.tx_queued && order.address != undefined}>
|
||||||
|
<Alert severity='info'>
|
||||||
|
<AlertTitle>
|
||||||
|
<CircularProgress sx={{ maxWidth: '0.8em', maxHeight: '0.8em' }} />
|
||||||
|
<a> </a>
|
||||||
|
{t('Sending coins to')}
|
||||||
|
<Tooltip disableHoverListener enterTouchDelay={0} title={t('Copied!')}>
|
||||||
|
<IconButton
|
||||||
|
color='inherit'
|
||||||
|
onClick={() => {
|
||||||
|
systemClient.copyToClipboard(order.address);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ContentCopy sx={{ width: '0.8em', height: '0.8em' }} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</AlertTitle>
|
||||||
|
<Typography
|
||||||
|
variant='body2'
|
||||||
|
align='center'
|
||||||
|
sx={{ wordWrap: 'break-word', width: '15.71em' }}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
target='_blank'
|
||||||
|
href={
|
||||||
|
'http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/' +
|
||||||
|
(order.network == 'testnet' ? 'testnet/' : '') +
|
||||||
|
'address/' +
|
||||||
|
order.address
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{order.address}
|
||||||
|
</Link>
|
||||||
|
</Typography>
|
||||||
|
</Alert>
|
||||||
|
</Collapse>
|
||||||
|
|
||||||
<Grid item container alignItems='center' justifyContent='space-evenly'>
|
<Grid item container alignItems='center' justifyContent='space-evenly'>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Button color='primary' variant='outlined' onClick={onClickStartAgain}>
|
<Button color='primary' variant='outlined' onClick={onClickStartAgain}>
|
||||||
|
@ -49,7 +49,7 @@ const statusToDelay = [
|
|||||||
100000, // 'In dispute'
|
100000, // 'In dispute'
|
||||||
999999, // 'Collaboratively cancelled'
|
999999, // 'Collaboratively cancelled'
|
||||||
10000, // 'Sending satoshis to buyer'
|
10000, // 'Sending satoshis to buyer'
|
||||||
999999, // 'Sucessful trade'
|
60000, // 'Sucessful trade'
|
||||||
30000, // 'Failed lightning network routing'
|
30000, // 'Failed lightning network routing'
|
||||||
300000, // 'Wait for dispute resolution'
|
300000, // 'Wait for dispute resolution'
|
||||||
300000, // 'Maker lost dispute'
|
300000, // 'Maker lost dispute'
|
||||||
|
@ -94,6 +94,8 @@ export interface Order {
|
|||||||
num_satoshis: number;
|
num_satoshis: number;
|
||||||
sent_satoshis: number;
|
sent_satoshis: number;
|
||||||
txid: string;
|
txid: string;
|
||||||
|
tx_queued: boolean;
|
||||||
|
address: string;
|
||||||
network: 'mainnet' | 'testnet';
|
network: 'mainnet' | 'testnet';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -486,6 +486,7 @@
|
|||||||
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats millora amb més liquiditat i usuaris. Explica-li a un amic bitcoiner sobre RoboSats!",
|
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats millora amb més liquiditat i usuaris. Explica-li a un amic bitcoiner sobre RoboSats!",
|
||||||
"Thank you for using Robosats!": "Gràcies per fer servir RoboSats!",
|
"Thank you for using Robosats!": "Gràcies per fer servir RoboSats!",
|
||||||
"Your TXID": "El teu TXID",
|
"Your TXID": "El teu TXID",
|
||||||
|
"Sending coins to": "Sending coins to",
|
||||||
"Start Again": "Començar de nou",
|
"Start Again": "Començar de nou",
|
||||||
"Renew": "Renovar",
|
"Renew": "Renovar",
|
||||||
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
||||||
|
@ -486,6 +486,7 @@
|
|||||||
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats bude lepší s větší likviditou a uživateli. Pověz svým přátelům o Robosats!",
|
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats bude lepší s větší likviditou a uživateli. Pověz svým přátelům o Robosats!",
|
||||||
"Thank you for using Robosats!": "Děkujeme, že používáš Robosats!",
|
"Thank you for using Robosats!": "Děkujeme, že používáš Robosats!",
|
||||||
"Your TXID": "Tvé TXID",
|
"Your TXID": "Tvé TXID",
|
||||||
|
"Sending coins to": "Sending coins to",
|
||||||
"Start Again": "Začít znovu",
|
"Start Again": "Začít znovu",
|
||||||
"Renew": "Renew",
|
"Renew": "Renew",
|
||||||
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
||||||
|
@ -486,6 +486,7 @@
|
|||||||
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats wird noch besser mit mehr Nutzern und Liquidität. Erzähl einem Bitcoin-Freund von uns!",
|
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats wird noch besser mit mehr Nutzern und Liquidität. Erzähl einem Bitcoin-Freund von uns!",
|
||||||
"Thank you for using Robosats!": "Danke, dass du Robosats benutzt hast!",
|
"Thank you for using Robosats!": "Danke, dass du Robosats benutzt hast!",
|
||||||
"Your TXID": "Your TXID",
|
"Your TXID": "Your TXID",
|
||||||
|
"Sending coins to": "Sending coins to",
|
||||||
"Start Again": "Nochmal",
|
"Start Again": "Nochmal",
|
||||||
"Renew": "Renew",
|
"Renew": "Renew",
|
||||||
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
||||||
|
@ -486,6 +486,7 @@
|
|||||||
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!",
|
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!",
|
||||||
"Thank you for using Robosats!": "Thank you for using Robosats!",
|
"Thank you for using Robosats!": "Thank you for using Robosats!",
|
||||||
"Your TXID": "Your TXID",
|
"Your TXID": "Your TXID",
|
||||||
|
"Sending coins to": "Sending coins to",
|
||||||
"Start Again": "Start Again",
|
"Start Again": "Start Again",
|
||||||
"Renew": "Renew",
|
"Renew": "Renew",
|
||||||
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
||||||
|
@ -486,6 +486,7 @@
|
|||||||
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats mejora con más liquidez y usuarios. ¡Cuéntale a un amigo bitcoiner sobre RoboSats!",
|
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats mejora con más liquidez y usuarios. ¡Cuéntale a un amigo bitcoiner sobre RoboSats!",
|
||||||
"Thank you for using Robosats!": "¡Gracias por usar RoboSats!",
|
"Thank you for using Robosats!": "¡Gracias por usar RoboSats!",
|
||||||
"Your TXID": "Tu TXID",
|
"Your TXID": "Tu TXID",
|
||||||
|
"Sending coins to": "Sending coins to",
|
||||||
"Start Again": "Empezar de nuevo",
|
"Start Again": "Empezar de nuevo",
|
||||||
"Renew": "Renew",
|
"Renew": "Renew",
|
||||||
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
||||||
|
@ -486,6 +486,7 @@
|
|||||||
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats hobetu egiten da likidezia eta erabiltzaile gehiagorekin. Aipatu Robosats lagun bitcoiner bati!",
|
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats hobetu egiten da likidezia eta erabiltzaile gehiagorekin. Aipatu Robosats lagun bitcoiner bati!",
|
||||||
"Thank you for using Robosats!": "Mila esker Robosats erabiltzeagatik!",
|
"Thank you for using Robosats!": "Mila esker Robosats erabiltzeagatik!",
|
||||||
"Your TXID": "Zure TXID",
|
"Your TXID": "Zure TXID",
|
||||||
|
"Sending coins to": "Sending coins to",
|
||||||
"Start Again": "Berriz Hasi",
|
"Start Again": "Berriz Hasi",
|
||||||
"Renew": "Renew",
|
"Renew": "Renew",
|
||||||
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
||||||
|
@ -486,6 +486,7 @@
|
|||||||
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats s'améliore avec plus de liquidité et d'utilisateurs. Parlez de Robosats à un ami bitcoiner!",
|
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats s'améliore avec plus de liquidité et d'utilisateurs. Parlez de Robosats à un ami bitcoiner!",
|
||||||
"Thank you for using Robosats!": "Merci d'utiliser Robosats!",
|
"Thank you for using Robosats!": "Merci d'utiliser Robosats!",
|
||||||
"Your TXID": "Your TXID",
|
"Your TXID": "Your TXID",
|
||||||
|
"Sending coins to": "Sending coins to",
|
||||||
"Start Again": "Recommencer",
|
"Start Again": "Recommencer",
|
||||||
"Renew": "Renew",
|
"Renew": "Renew",
|
||||||
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
||||||
|
@ -486,6 +486,7 @@
|
|||||||
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats migliora se ha più liquidità ed utenti. Parla di Robosats ai tuoi amici bitcoiner!",
|
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats migliora se ha più liquidità ed utenti. Parla di Robosats ai tuoi amici bitcoiner!",
|
||||||
"Thank you for using Robosats!": "Grazie per aver usato Robosats!",
|
"Thank you for using Robosats!": "Grazie per aver usato Robosats!",
|
||||||
"Your TXID": "Your TXID",
|
"Your TXID": "Your TXID",
|
||||||
|
"Sending coins to": "Sending coins to",
|
||||||
"Start Again": "Ricomincia",
|
"Start Again": "Ricomincia",
|
||||||
"Renew": "Renew",
|
"Renew": "Renew",
|
||||||
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
||||||
|
@ -486,6 +486,7 @@
|
|||||||
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats staje się lepszy dzięki większej płynności i użytkownikom. Powiedz znajomemu bitcoinerowi o Robosats!",
|
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats staje się lepszy dzięki większej płynności i użytkownikom. Powiedz znajomemu bitcoinerowi o Robosats!",
|
||||||
"Thank you for using Robosats!": "Dziękujemy za korzystanie z Robosatów!",
|
"Thank you for using Robosats!": "Dziękujemy za korzystanie z Robosatów!",
|
||||||
"Your TXID": "Your TXID",
|
"Your TXID": "Your TXID",
|
||||||
|
"Sending coins to": "Sending coins to",
|
||||||
"Start Again": "Zacznij jeszcze raz",
|
"Start Again": "Zacznij jeszcze raz",
|
||||||
"Renew": "Renew",
|
"Renew": "Renew",
|
||||||
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
||||||
|
@ -486,6 +486,7 @@
|
|||||||
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats fica melhor com mais liquidez e usuários. Conte a um amigo bitcoiner sobre Robosats!",
|
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats fica melhor com mais liquidez e usuários. Conte a um amigo bitcoiner sobre Robosats!",
|
||||||
"Thank you for using Robosats!": "Obrigado por usar Robosats!",
|
"Thank you for using Robosats!": "Obrigado por usar Robosats!",
|
||||||
"Your TXID": "Sua TXID",
|
"Your TXID": "Sua TXID",
|
||||||
|
"Sending coins to": "Sending coins to",
|
||||||
"Start Again": "Comece de novo",
|
"Start Again": "Comece de novo",
|
||||||
"Renew": "Renew",
|
"Renew": "Renew",
|
||||||
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
||||||
|
@ -486,6 +486,7 @@
|
|||||||
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats становится лучше с большей ликвидностью и пользователями. Расскажите другу-биткойнеру о Robosat!",
|
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats становится лучше с большей ликвидностью и пользователями. Расскажите другу-биткойнеру о Robosat!",
|
||||||
"Thank you for using Robosats!": "Спасибо за использование Robosats!",
|
"Thank you for using Robosats!": "Спасибо за использование Robosats!",
|
||||||
"Your TXID": "Ваш TXID",
|
"Your TXID": "Ваш TXID",
|
||||||
|
"Sending coins to": "Sending coins to",
|
||||||
"Start Again": "Начать Снова",
|
"Start Again": "Начать Снова",
|
||||||
"Renew": "Renew",
|
"Renew": "Renew",
|
||||||
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
||||||
|
@ -486,6 +486,7 @@
|
|||||||
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats blir bättre med mer likviditet och fler användare. Berätta om RoboSats för en Bitcoiner-vän!",
|
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats blir bättre med mer likviditet och fler användare. Berätta om RoboSats för en Bitcoiner-vän!",
|
||||||
"Thank you for using Robosats!": "Tack för att du använder RoboSats!",
|
"Thank you for using Robosats!": "Tack för att du använder RoboSats!",
|
||||||
"Your TXID": "Ditt TXID",
|
"Your TXID": "Ditt TXID",
|
||||||
|
"Sending coins to": "Sending coins to",
|
||||||
"Start Again": "Börja om",
|
"Start Again": "Börja om",
|
||||||
"Renew": "Renew",
|
"Renew": "Renew",
|
||||||
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
||||||
|
@ -486,6 +486,7 @@
|
|||||||
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats จะดีขึ้นเมื่อมีสภาพคล่องและผู้ใช้งานมากขึ้น ช่วยกันชวนเพื่อนของคุณมาใช้ Robosats!",
|
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats จะดีขึ้นเมื่อมีสภาพคล่องและผู้ใช้งานมากขึ้น ช่วยกันชวนเพื่อนของคุณมาใช้ Robosats!",
|
||||||
"Thank you for using Robosats!": "ขอบคุณที่ใช้งาน Robosats!",
|
"Thank you for using Robosats!": "ขอบคุณที่ใช้งาน Robosats!",
|
||||||
"Your TXID": "TXID ของคุณ",
|
"Your TXID": "TXID ของคุณ",
|
||||||
|
"Sending coins to": "Sending coins to",
|
||||||
"Start Again": "Start Again",
|
"Start Again": "Start Again",
|
||||||
"Renew": "Renew",
|
"Renew": "Renew",
|
||||||
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
||||||
|
@ -486,6 +486,7 @@
|
|||||||
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats 会随着更多的流动性和更高的用户数量而变得更好。把 RoboSats 推荐给你的比特币朋友吧!",
|
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats 会随着更多的流动性和更高的用户数量而变得更好。把 RoboSats 推荐给你的比特币朋友吧!",
|
||||||
"Thank you for using Robosats!": "感谢你使用 Robosats!",
|
"Thank you for using Robosats!": "感谢你使用 Robosats!",
|
||||||
"Your TXID": "你的 TXID",
|
"Your TXID": "你的 TXID",
|
||||||
|
"Sending coins to": "Sending coins to",
|
||||||
"Start Again": "重新开始",
|
"Start Again": "重新开始",
|
||||||
"Renew": "Renew",
|
"Renew": "Renew",
|
||||||
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
||||||
|
@ -486,6 +486,7 @@
|
|||||||
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats 會隨著更多的流動性和更高的用戶数量而變得更好。把 RoboSats 推薦給你的比特幣朋友吧!",
|
"RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!": "RoboSats 會隨著更多的流動性和更高的用戶数量而變得更好。把 RoboSats 推薦給你的比特幣朋友吧!",
|
||||||
"Thank you for using Robosats!": "感謝你使用 Robosats!",
|
"Thank you for using Robosats!": "感謝你使用 Robosats!",
|
||||||
"Your TXID": "你的 TXID",
|
"Your TXID": "你的 TXID",
|
||||||
|
"Sending coins to": "Sending coins to",
|
||||||
"Start Again": "重新開始",
|
"Start Again": "重新開始",
|
||||||
"Renew": "Renew",
|
"Renew": "Renew",
|
||||||
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
"#58": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
||||||
|
Loading…
Reference in New Issue
Block a user