Merge branch 'main' into desktopApp
11
.github/workflows/android-build.yml
vendored
@ -6,6 +6,15 @@ on:
|
|||||||
semver:
|
semver:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
secrets:
|
||||||
|
KEYSTORE:
|
||||||
|
required: true
|
||||||
|
KEY_ALIAS:
|
||||||
|
required: true
|
||||||
|
KEY_PASS:
|
||||||
|
required: true
|
||||||
|
KEY_STORE_PASS:
|
||||||
|
required: true
|
||||||
push:
|
push:
|
||||||
branches: [ "main" ]
|
branches: [ "main" ]
|
||||||
paths: [ "mobile", "frontend" ]
|
paths: [ "mobile", "frontend" ]
|
||||||
@ -23,7 +32,7 @@ jobs:
|
|||||||
|
|
||||||
- name: 'Download Android Web.bundle Artifact (built frontend)'
|
- name: 'Download Android Web.bundle Artifact (built frontend)'
|
||||||
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
|
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
|
||||||
uses: dawidd6/action-download-artifact@v3
|
uses: dawidd6/action-download-artifact@v6
|
||||||
with:
|
with:
|
||||||
workflow: frontend-build.yml
|
workflow: frontend-build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
|
4
.github/workflows/coordinator-image.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
|
|
||||||
- name: 'Download Basic main.js Artifact'
|
- name: 'Download Basic main.js Artifact'
|
||||||
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
|
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
|
||||||
uses: dawidd6/action-download-artifact@v3
|
uses: dawidd6/action-download-artifact@v6
|
||||||
with:
|
with:
|
||||||
workflow: frontend-build.yml
|
workflow: frontend-build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
@ -33,7 +33,7 @@ jobs:
|
|||||||
|
|
||||||
- name: 'Download pro.js Artifact'
|
- name: 'Download pro.js Artifact'
|
||||||
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
|
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
|
||||||
uses: dawidd6/action-download-artifact@v3
|
uses: dawidd6/action-download-artifact@v6
|
||||||
with:
|
with:
|
||||||
workflow: frontend-build.yml
|
workflow: frontend-build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
|
5
.github/workflows/integration-tests.yml
vendored
@ -22,8 +22,8 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
python-tag: ['3.12.3-slim-bookworm', '3.13-rc-slim-bookworm']
|
python-tag: ['3.12.3-slim-bookworm', '3.13-rc-slim-bookworm']
|
||||||
lnd-version: ['v0.17.4-beta']
|
lnd-version: ['v0.17.4-beta']
|
||||||
cln-version: ['v23.11.2'] #,'v24.02']
|
cln-version: ['v24.05']
|
||||||
ln-vendor: ['LND'] #, 'CLN']
|
ln-vendor: ['LND', 'CLN']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Checkout'
|
- name: 'Checkout'
|
||||||
@ -56,7 +56,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
LND_VERSION: ${{ matrix.lnd-version }}
|
LND_VERSION: ${{ matrix.lnd-version }}
|
||||||
CLN_VERSION: ${{ matrix.cln-version }}
|
CLN_VERSION: ${{ matrix.cln-version }}
|
||||||
BITCOIND_VERSION: ${{ matrix.bitcoind-version }}
|
|
||||||
ROBOSATS_ENVS_FILE: ".env-sample"
|
ROBOSATS_ENVS_FILE: ".env-sample"
|
||||||
|
|
||||||
- name: Wait for coordinator (django server)
|
- name: Wait for coordinator (django server)
|
||||||
|
1
.github/workflows/release.yml
vendored
@ -71,6 +71,7 @@ jobs:
|
|||||||
android-build:
|
android-build:
|
||||||
uses: RoboSats/robosats/.github/workflows/android-build.yml@main
|
uses: RoboSats/robosats/.github/workflows/android-build.yml@main
|
||||||
needs: [frontend-build, check-versions]
|
needs: [frontend-build, check-versions]
|
||||||
|
secrets: inherit
|
||||||
with:
|
with:
|
||||||
semver: ${{ needs.check-versions.outputs.semver }}
|
semver: ${{ needs.check-versions.outputs.semver }}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ jobs:
|
|||||||
|
|
||||||
- name: 'Download basic.selfhosted.js Artifact'
|
- name: 'Download basic.selfhosted.js Artifact'
|
||||||
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
|
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
|
||||||
uses: dawidd6/action-download-artifact@v3
|
uses: dawidd6/action-download-artifact@v6
|
||||||
with:
|
with:
|
||||||
workflow: frontend-build.yml
|
workflow: frontend-build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
@ -42,7 +42,7 @@ jobs:
|
|||||||
|
|
||||||
- name: 'Download pro.selfhosted.js Artifact'
|
- name: 'Download pro.selfhosted.js Artifact'
|
||||||
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
|
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
|
||||||
uses: dawidd6/action-download-artifact@v3
|
uses: dawidd6/action-download-artifact@v6
|
||||||
with:
|
with:
|
||||||
workflow: frontend-build.yml
|
workflow: frontend-build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
|
4
.github/workflows/web-client-image.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
|||||||
|
|
||||||
- name: 'Download main.js Artifact'
|
- name: 'Download main.js Artifact'
|
||||||
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
|
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
|
||||||
uses: dawidd6/action-download-artifact@v3
|
uses: dawidd6/action-download-artifact@v6
|
||||||
with:
|
with:
|
||||||
workflow: frontend-build.yml
|
workflow: frontend-build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
@ -42,7 +42,7 @@ jobs:
|
|||||||
|
|
||||||
- name: 'Download pro.js Artifact'
|
- name: 'Download pro.js Artifact'
|
||||||
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
|
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
|
||||||
uses: dawidd6/action-download-artifact@v3
|
uses: dawidd6/action-download-artifact@v6
|
||||||
with:
|
with:
|
||||||
workflow: frontend-build.yml
|
workflow: frontend-build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
|
17
api/admin.py
@ -21,17 +21,13 @@ admin.site.unregister(TokenProxy)
|
|||||||
class RobotInline(admin.StackedInline):
|
class RobotInline(admin.StackedInline):
|
||||||
model = Robot
|
model = Robot
|
||||||
can_delete = False
|
can_delete = False
|
||||||
fields = ("avatar_tag",)
|
|
||||||
readonly_fields = ["avatar_tag"]
|
|
||||||
show_change_link = True
|
show_change_link = True
|
||||||
|
|
||||||
|
|
||||||
# extended users with avatars
|
|
||||||
@admin.register(User)
|
@admin.register(User)
|
||||||
class EUserAdmin(AdminChangeLinksMixin, UserAdmin):
|
class EUserAdmin(AdminChangeLinksMixin, UserAdmin):
|
||||||
inlines = [RobotInline]
|
inlines = [RobotInline]
|
||||||
list_display = (
|
list_display = (
|
||||||
"avatar_tag",
|
|
||||||
"id",
|
"id",
|
||||||
"robot_link",
|
"robot_link",
|
||||||
"username",
|
"username",
|
||||||
@ -43,25 +39,18 @@ class EUserAdmin(AdminChangeLinksMixin, UserAdmin):
|
|||||||
change_links = ("robot",)
|
change_links = ("robot",)
|
||||||
ordering = ("-id",)
|
ordering = ("-id",)
|
||||||
|
|
||||||
def avatar_tag(self, obj):
|
|
||||||
return obj.robot.avatar_tag()
|
|
||||||
|
|
||||||
|
# extended tokens with raw id fields
|
||||||
# extended tokens with raw id fields and avatars
|
|
||||||
@admin.register(TokenProxy)
|
@admin.register(TokenProxy)
|
||||||
class ETokenAdmin(AdminChangeLinksMixin, TokenAdmin):
|
class ETokenAdmin(AdminChangeLinksMixin, TokenAdmin):
|
||||||
raw_id_fields = ["user"]
|
raw_id_fields = ["user"]
|
||||||
list_display = (
|
list_display = (
|
||||||
"avatar_tag",
|
|
||||||
"key",
|
"key",
|
||||||
"user_link",
|
"user_link",
|
||||||
)
|
)
|
||||||
list_display_links = ("key",)
|
list_display_links = ("key",)
|
||||||
change_links = ("user",)
|
change_links = ("user",)
|
||||||
|
|
||||||
def avatar_tag(self, obj):
|
|
||||||
return obj.user.robot.avatar_tag()
|
|
||||||
|
|
||||||
|
|
||||||
class LNPaymentInline(admin.StackedInline):
|
class LNPaymentInline(admin.StackedInline):
|
||||||
model = LNPayment
|
model = LNPayment
|
||||||
@ -510,7 +499,6 @@ class OnchainPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
|
|||||||
@admin.register(Robot)
|
@admin.register(Robot)
|
||||||
class UserRobotAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
|
class UserRobotAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
"avatar_tag",
|
|
||||||
"id",
|
"id",
|
||||||
"user_link",
|
"user_link",
|
||||||
"telegram_enabled",
|
"telegram_enabled",
|
||||||
@ -523,9 +511,8 @@ class UserRobotAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
|
|||||||
)
|
)
|
||||||
raw_id_fields = ("user",)
|
raw_id_fields = ("user",)
|
||||||
list_editable = ["earned_rewards"]
|
list_editable = ["earned_rewards"]
|
||||||
list_display_links = ("avatar_tag", "id")
|
list_display_links = ["id"]
|
||||||
change_links = ["user"]
|
change_links = ["user"]
|
||||||
readonly_fields = ["avatar_tag"]
|
|
||||||
search_fields = ["user__username", "id"]
|
search_fields = ["user__username", "id"]
|
||||||
readonly_fields = ("hash_id", "public_key", "encrypted_private_key")
|
readonly_fields = ("hash_id", "public_key", "encrypted_private_key")
|
||||||
|
|
||||||
|
@ -90,9 +90,9 @@ class CLNNode:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def decode_payreq(cls, invoice):
|
def decode_payreq(cls, invoice):
|
||||||
"""Decodes a lightning payment request (invoice)"""
|
"""Decodes a lightning payment request (invoice)"""
|
||||||
request = hold_pb2.DecodeBolt11Request(bolt11=invoice)
|
nodestub = node_pb2_grpc.NodeStub(cls.node_channel)
|
||||||
holdstub = hold_pb2_grpc.HoldStub(cls.hold_channel)
|
request = node_pb2.DecodeRequest(string=invoice)
|
||||||
response = holdstub.DecodeBolt11(request)
|
response = nodestub.Decode(request)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -236,7 +236,7 @@ class CLNNode:
|
|||||||
holdstub = hold_pb2_grpc.HoldStub(cls.hold_channel)
|
holdstub = hold_pb2_grpc.HoldStub(cls.hold_channel)
|
||||||
response = holdstub.HoldInvoiceCancel(request)
|
response = holdstub.HoldInvoiceCancel(request)
|
||||||
|
|
||||||
return response.state == hold_pb2.HoldInvoiceCancelResponse.Holdstate.CANCELED
|
return response.state == hold_pb2.Holdstate.CANCELED
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def settle_hold_invoice(cls, preimage):
|
def settle_hold_invoice(cls, preimage):
|
||||||
@ -247,7 +247,7 @@ class CLNNode:
|
|||||||
holdstub = hold_pb2_grpc.HoldStub(cls.hold_channel)
|
holdstub = hold_pb2_grpc.HoldStub(cls.hold_channel)
|
||||||
response = holdstub.HoldInvoiceSettle(request)
|
response = holdstub.HoldInvoiceSettle(request)
|
||||||
|
|
||||||
return response.state == hold_pb2.HoldInvoiceSettleResponse.Holdstate.SETTLED
|
return response.state == hold_pb2.Holdstate.SETTLED
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def gen_hold_invoice(
|
def gen_hold_invoice(
|
||||||
@ -272,7 +272,7 @@ class CLNNode:
|
|||||||
|
|
||||||
request = hold_pb2.HoldInvoiceRequest(
|
request = hold_pb2.HoldInvoiceRequest(
|
||||||
description=description,
|
description=description,
|
||||||
amount_msat=primitives__pb2.Amount(msat=num_satoshis * 1_000),
|
amount_msat=hold_pb2.Amount(msat=num_satoshis * 1_000),
|
||||||
label=f"Order:{order_id}-{lnpayment_concept}-{time}",
|
label=f"Order:{order_id}-{lnpayment_concept}-{time}",
|
||||||
expiry=invoice_expiry,
|
expiry=invoice_expiry,
|
||||||
cltv=cltv_expiry_blocks,
|
cltv=cltv_expiry_blocks,
|
||||||
@ -286,7 +286,7 @@ class CLNNode:
|
|||||||
hold_payment["preimage"] = preimage.hex()
|
hold_payment["preimage"] = preimage.hex()
|
||||||
hold_payment["payment_hash"] = response.payment_hash.hex()
|
hold_payment["payment_hash"] = response.payment_hash.hex()
|
||||||
hold_payment["created_at"] = timezone.make_aware(
|
hold_payment["created_at"] = timezone.make_aware(
|
||||||
datetime.fromtimestamp(payreq_decoded.timestamp)
|
datetime.fromtimestamp(payreq_decoded.created_at)
|
||||||
)
|
)
|
||||||
hold_payment["expires_at"] = timezone.make_aware(
|
hold_payment["expires_at"] = timezone.make_aware(
|
||||||
datetime.fromtimestamp(response.expires_at)
|
datetime.fromtimestamp(response.expires_at)
|
||||||
@ -309,13 +309,13 @@ class CLNNode:
|
|||||||
# Will fail if 'unable to locate invoice'. Happens if invoice expiry
|
# Will fail if 'unable to locate invoice'. Happens if invoice expiry
|
||||||
# time has passed (but these are 15% padded at the moment). Should catch it
|
# time has passed (but these are 15% padded at the moment). Should catch it
|
||||||
# and report back that the invoice has expired (better robustness)
|
# and report back that the invoice has expired (better robustness)
|
||||||
if response.state == hold_pb2.HoldInvoiceLookupResponse.Holdstate.OPEN:
|
if response.state == hold_pb2.Holdstate.OPEN:
|
||||||
pass
|
pass
|
||||||
if response.state == hold_pb2.HoldInvoiceLookupResponse.Holdstate.SETTLED:
|
if response.state == hold_pb2.Holdstate.SETTLED:
|
||||||
pass
|
pass
|
||||||
if response.state == hold_pb2.HoldInvoiceLookupResponse.Holdstate.CANCELED:
|
if response.state == hold_pb2.Holdstate.CANCELED:
|
||||||
pass
|
pass
|
||||||
if response.state == hold_pb2.HoldInvoiceLookupResponse.Holdstate.ACCEPTED:
|
if response.state == hold_pb2.Holdstate.ACCEPTED:
|
||||||
lnpayment.expiry_height = response.htlc_expiry
|
lnpayment.expiry_height = response.htlc_expiry
|
||||||
lnpayment.status = LNPayment.Status.LOCKED
|
lnpayment.status = LNPayment.Status.LOCKED
|
||||||
lnpayment.save(update_fields=["expiry_height", "status"])
|
lnpayment.save(update_fields=["expiry_height", "status"])
|
||||||
@ -359,7 +359,7 @@ class CLNNode:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
# If it fails at finding the invoice: it has been expired for more than an hour (and could be paid or just expired).
|
# If it fails at finding the invoice: it has been expired for more than an hour (and could be paid or just expired).
|
||||||
# In RoboSats DB we make a distinction between cancelled and returned
|
# In RoboSats DB we make a distinction between cancelled and returned
|
||||||
# (cln-grpc-hodl has separate state for hodl-invoices, which it forgets after an invoice expired more than an hour ago)
|
# (holdinvoice plugin has separate state for hodl-invoices, which it forgets after an invoice expired more than an hour ago)
|
||||||
if "empty result for listdatastore_state" in str(e):
|
if "empty result for listdatastore_state" in str(e):
|
||||||
print(str(e))
|
print(str(e))
|
||||||
request2 = node_pb2.ListinvoicesRequest(
|
request2 = node_pb2.ListinvoicesRequest(
|
||||||
@ -418,7 +418,7 @@ class CLNNode:
|
|||||||
|
|
||||||
# Some wallet providers (e.g. Muun) force routing through a private channel with high fees >1500ppm
|
# Some wallet providers (e.g. Muun) force routing through a private channel with high fees >1500ppm
|
||||||
# These payments will fail. So it is best to let the user know in advance this invoice is not valid.
|
# These payments will fail. So it is best to let the user know in advance this invoice is not valid.
|
||||||
route_hints = payreq_decoded.route_hints.hints
|
route_hints = payreq_decoded.routes.hints
|
||||||
|
|
||||||
# Max amount RoboSats will pay for routing
|
# Max amount RoboSats will pay for routing
|
||||||
if routing_budget_ppm == 0:
|
if routing_budget_ppm == 0:
|
||||||
@ -438,8 +438,10 @@ class CLNNode:
|
|||||||
route_cost = 0
|
route_cost = 0
|
||||||
# ...add up the cost of every hinted hop...
|
# ...add up the cost of every hinted hop...
|
||||||
for hop_hint in hinted_route.hops:
|
for hop_hint in hinted_route.hops:
|
||||||
route_cost += hop_hint.feebase.msat / 1_000
|
route_cost += hop_hint.fee_base_msat.msat / 1_000
|
||||||
route_cost += hop_hint.feeprop * num_satoshis / 1_000_000
|
route_cost += (
|
||||||
|
hop_hint.fee_proportional_millionths * num_satoshis / 1_000_000
|
||||||
|
)
|
||||||
|
|
||||||
# ...and store the cost of the route to the array
|
# ...and store the cost of the route to the array
|
||||||
routes_cost.append(route_cost)
|
routes_cost.append(route_cost)
|
||||||
@ -466,7 +468,7 @@ class CLNNode:
|
|||||||
return payout
|
return payout
|
||||||
|
|
||||||
payout["created_at"] = timezone.make_aware(
|
payout["created_at"] = timezone.make_aware(
|
||||||
datetime.fromtimestamp(payreq_decoded.timestamp)
|
datetime.fromtimestamp(payreq_decoded.created_at)
|
||||||
)
|
)
|
||||||
payout["expires_at"] = payout["created_at"] + timedelta(
|
payout["expires_at"] = payout["created_at"] + timedelta(
|
||||||
seconds=payreq_decoded.expiry
|
seconds=payreq_decoded.expiry
|
||||||
@ -869,4 +871,4 @@ class CLNNode:
|
|||||||
else:
|
else:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
return response.state == hold_pb2.HoldInvoiceLookupResponse.Holdstate.SETTLED
|
return response.state == hold_pb2.Holdstate.SETTLED
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
@ -18,7 +19,7 @@ class Currency(models.Model):
|
|||||||
decimal_places=4,
|
decimal_places=4,
|
||||||
default=None,
|
default=None,
|
||||||
null=True,
|
null=True,
|
||||||
validators=[MinValueValidator(0)],
|
validators=[MinValueValidator(Decimal(0))],
|
||||||
)
|
)
|
||||||
timestamp = models.DateTimeField(default=timezone.now)
|
timestamp = models.DateTimeField(default=timezone.now)
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
from decouple import config
|
from decouple import config
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
@ -27,21 +28,24 @@ class MarketTick(models.Model):
|
|||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
default=None,
|
default=None,
|
||||||
null=True,
|
null=True,
|
||||||
validators=[MinValueValidator(0)],
|
validators=[MinValueValidator(Decimal(0))],
|
||||||
)
|
)
|
||||||
volume = models.DecimalField(
|
volume = models.DecimalField(
|
||||||
max_digits=8,
|
max_digits=8,
|
||||||
decimal_places=8,
|
decimal_places=8,
|
||||||
default=None,
|
default=None,
|
||||||
null=True,
|
null=True,
|
||||||
validators=[MinValueValidator(0)],
|
validators=[MinValueValidator(Decimal(0))],
|
||||||
)
|
)
|
||||||
premium = models.DecimalField(
|
premium = models.DecimalField(
|
||||||
max_digits=5,
|
max_digits=5,
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
default=None,
|
default=None,
|
||||||
null=True,
|
null=True,
|
||||||
validators=[MinValueValidator(-100), MaxValueValidator(999)],
|
validators=[
|
||||||
|
MinValueValidator(Decimal(-100)),
|
||||||
|
MaxValueValidator(Decimal(999))
|
||||||
|
],
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
currency = models.ForeignKey("api.Currency", null=True, on_delete=models.SET_NULL)
|
currency = models.ForeignKey("api.Currency", null=True, on_delete=models.SET_NULL)
|
||||||
@ -52,7 +56,10 @@ class MarketTick(models.Model):
|
|||||||
max_digits=4,
|
max_digits=4,
|
||||||
decimal_places=4,
|
decimal_places=4,
|
||||||
default=0,
|
default=0,
|
||||||
validators=[MinValueValidator(0), MaxValueValidator(1)],
|
validators=[
|
||||||
|
MinValueValidator(Decimal(0)),
|
||||||
|
MaxValueValidator(Decimal(1))
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def log_a_tick(order):
|
def log_a_tick(order):
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from decimal import Decimal
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
@ -58,7 +59,10 @@ class OnchainPayment(models.Model):
|
|||||||
default=2.05,
|
default=2.05,
|
||||||
null=False,
|
null=False,
|
||||||
blank=False,
|
blank=False,
|
||||||
validators=[MinValueValidator(1), MaxValueValidator(999)],
|
validators=[
|
||||||
|
MinValueValidator(Decimal(1)),
|
||||||
|
MaxValueValidator(Decimal(999))
|
||||||
|
],
|
||||||
)
|
)
|
||||||
mining_fee_rate = models.DecimalField(
|
mining_fee_rate = models.DecimalField(
|
||||||
max_digits=6,
|
max_digits=6,
|
||||||
@ -66,7 +70,10 @@ class OnchainPayment(models.Model):
|
|||||||
default=2.05,
|
default=2.05,
|
||||||
null=False,
|
null=False,
|
||||||
blank=False,
|
blank=False,
|
||||||
validators=[MinValueValidator(1), MaxValueValidator(999)],
|
validators=[
|
||||||
|
MinValueValidator(Decimal(1)),
|
||||||
|
MaxValueValidator(Decimal(999))
|
||||||
|
],
|
||||||
)
|
)
|
||||||
mining_fee_sats = models.PositiveBigIntegerField(default=0, null=False, blank=False)
|
mining_fee_sats = models.PositiveBigIntegerField(default=0, null=False, blank=False)
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# We use custom seeded UUID generation during testing
|
# We use custom seeded UUID generation during testing
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
from decouple import config
|
from decouple import config
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
@ -90,7 +91,10 @@ class Order(models.Model):
|
|||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
default=0,
|
default=0,
|
||||||
null=True,
|
null=True,
|
||||||
validators=[MinValueValidator(-100), MaxValueValidator(999)],
|
validators=[
|
||||||
|
MinValueValidator(Decimal(-100)),
|
||||||
|
MaxValueValidator(Decimal(999))
|
||||||
|
],
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
# explicit
|
# explicit
|
||||||
@ -135,8 +139,8 @@ class Order(models.Model):
|
|||||||
default=settings.DEFAULT_BOND_SIZE,
|
default=settings.DEFAULT_BOND_SIZE,
|
||||||
null=False,
|
null=False,
|
||||||
validators=[
|
validators=[
|
||||||
MinValueValidator(settings.MIN_BOND_SIZE), # 2 %
|
MinValueValidator(Decimal(settings.MIN_BOND_SIZE)), # 2 %
|
||||||
MaxValueValidator(settings.MAX_BOND_SIZE), # 15 %
|
MaxValueValidator(Decimal(settings.MAX_BOND_SIZE)), # 15 %
|
||||||
],
|
],
|
||||||
blank=False,
|
blank=False,
|
||||||
)
|
)
|
||||||
@ -147,8 +151,8 @@ class Order(models.Model):
|
|||||||
decimal_places=6,
|
decimal_places=6,
|
||||||
null=True,
|
null=True,
|
||||||
validators=[
|
validators=[
|
||||||
MinValueValidator(-90),
|
MinValueValidator(Decimal(-90)),
|
||||||
MaxValueValidator(90),
|
MaxValueValidator(Decimal(90)),
|
||||||
],
|
],
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
@ -157,8 +161,8 @@ class Order(models.Model):
|
|||||||
decimal_places=6,
|
decimal_places=6,
|
||||||
null=True,
|
null=True,
|
||||||
validators=[
|
validators=[
|
||||||
MinValueValidator(-180),
|
MinValueValidator(Decimal(-180)),
|
||||||
MaxValueValidator(180),
|
MaxValueValidator(Decimal(180)),
|
||||||
],
|
],
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
@ -494,7 +494,8 @@ class OrderPublicSerializer(serializers.ModelSerializer):
|
|||||||
maker_nick = serializers.CharField(required=False)
|
maker_nick = serializers.CharField(required=False)
|
||||||
maker_hash_id = serializers.CharField(required=False)
|
maker_hash_id = serializers.CharField(required=False)
|
||||||
maker_status = serializers.CharField(
|
maker_status = serializers.CharField(
|
||||||
help_text='Status of the nick - "Active" or "Inactive"', required=False
|
help_text='Status of the nick - "Active", "Seen Recently" or "Inactive"',
|
||||||
|
required=False,
|
||||||
)
|
)
|
||||||
price = serializers.FloatField(
|
price = serializers.FloatField(
|
||||||
help_text="Price in order's fiat currency", required=False
|
help_text="Price in order's fiat currency", required=False
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from decouple import config
|
from decouple import config
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -55,9 +54,6 @@ from control.models import AccountingDay, BalanceLog
|
|||||||
EXP_MAKER_BOND_INVOICE = int(config("EXP_MAKER_BOND_INVOICE"))
|
EXP_MAKER_BOND_INVOICE = int(config("EXP_MAKER_BOND_INVOICE"))
|
||||||
RETRY_TIME = int(config("RETRY_TIME"))
|
RETRY_TIME = int(config("RETRY_TIME"))
|
||||||
|
|
||||||
avatar_path = Path(settings.AVATAR_ROOT)
|
|
||||||
avatar_path.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
|
|
||||||
class MakerView(CreateAPIView):
|
class MakerView(CreateAPIView):
|
||||||
serializer_class = MakeOrderSerializer
|
serializer_class = MakeOrderSerializer
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
# Some useful handy commands that hopefully are never needed
|
# Some useful handy commands that hopefully are never needed
|
||||||
|
|
||||||
|
# docker-compose -f docker-tests.yml --env-file tests/compose.env down --volumes
|
||||||
|
|
||||||
# docker exec -it btc bitcoin-cli -chain=regtest -rpcpassword=test -rpcuser=test createwallet default
|
# docker exec -it btc bitcoin-cli -chain=regtest -rpcpassword=test -rpcuser=test createwallet default
|
||||||
# docker exec -it btc bitcoin-cli -chain=regtest -rpcpassword=test -rpcuser=test -generate 101
|
# docker exec -it btc bitcoin-cli -chain=regtest -rpcpassword=test -rpcuser=test -generate 101
|
||||||
|
|
||||||
@ -27,6 +29,8 @@ services:
|
|||||||
- "6379:6379"
|
- "6379:6379"
|
||||||
volumes:
|
volumes:
|
||||||
- bitcoin:/bitcoin/.bitcoin/
|
- bitcoin:/bitcoin/.bitcoin/
|
||||||
|
- ./tests/bitcoind/entrypoint.sh:/entrypoint.sh
|
||||||
|
entrypoint: ["/entrypoint.sh"]
|
||||||
command:
|
command:
|
||||||
--txindex=1
|
--txindex=1
|
||||||
--printtoconsole
|
--printtoconsole
|
||||||
@ -77,16 +81,16 @@ services:
|
|||||||
network_mode: service:bitcoind
|
network_mode: service:bitcoind
|
||||||
|
|
||||||
coordinator-CLN:
|
coordinator-CLN:
|
||||||
image: elementsproject/lightningd:${CLN_VERSION:-v23.08.1}
|
image: elementsproject/lightningd:${CLN_VERSION:-v24.05}
|
||||||
restart: always
|
restart: always
|
||||||
container_name: coordinator-CLN
|
container_name: coordinator-CLN
|
||||||
environment:
|
environment:
|
||||||
LIGHTNINGD_NETWORK: 'regtest'
|
LIGHTNINGD_NETWORK: 'regtest'
|
||||||
volumes:
|
volumes:
|
||||||
- cln:/root/.lightning
|
- cln:/root/.lightning
|
||||||
- ./docker/cln/plugins/cln-grpc-hold:/root/.lightning/plugins/cln-grpc-hold
|
- ./docker/cln/plugins/holdinvoice:/root/.lightning/plugins/holdinvoice
|
||||||
- bitcoin:/root/.bitcoin
|
- bitcoin:/root/.bitcoin
|
||||||
command: --regtest --wumbo --bitcoin-rpcuser=test --bitcoin-rpcpassword=test --log-level=debug --rest-host=0.0.0.0 --rest-port=3010 --bind-addr=127.0.0.1:9737 --max-concurrent-htlcs=483 --grpc-port=9999 --grpc-hold-port=9998 --important-plugin=/root/.lightning/plugins/cln-grpc-hold --database-upgrade=true
|
command: --regtest --bitcoin-rpcuser=test --bitcoin-rpcpassword=test --developer --dev-bitcoind-poll=1 --dev-fast-gossip --log-level=debug --bind-addr=127.0.0.1:9737 --max-concurrent-htlcs=483 --grpc-port=9999 --grpc-hold-port=9998 --important-plugin=/root/.lightning/plugins/holdinvoice --database-upgrade=true
|
||||||
depends_on:
|
depends_on:
|
||||||
- bitcoind
|
- bitcoind
|
||||||
network_mode: service:bitcoind
|
network_mode: service:bitcoind
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
FROM debian:bullseye-slim as builder
|
FROM debian:bullseye-slim as builder
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
ARG LIGHTNINGD_VERSION=v23.08
|
ARG LIGHTNINGD_VERSION=v24.05
|
||||||
RUN apt-get update -qq && \
|
RUN apt-get update -qq && \
|
||||||
apt-get install -qq -y --no-install-recommends \
|
apt-get install -qq -y --no-install-recommends \
|
||||||
autoconf \
|
autoconf \
|
||||||
@ -18,13 +18,13 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
|||||||
RUN rustup toolchain install stable --component rustfmt --allow-downgrade
|
RUN rustup toolchain install stable --component rustfmt --allow-downgrade
|
||||||
|
|
||||||
WORKDIR /opt/lightningd
|
WORKDIR /opt/lightningd
|
||||||
RUN git clone --recursive --branch cln-grpc-hold https://github.com/daywalker90/lightning.git /tmp/cln-grpc-hold
|
RUN git clone https://github.com/daywalker90/holdinvoice.git /tmp/holdinvoice
|
||||||
RUN cd /tmp/cln-grpc-hold \
|
RUN cd /tmp/holdinvoice \
|
||||||
&& cargo build --release
|
&& cargo build --release
|
||||||
|
|
||||||
FROM elementsproject/lightningd:v23.08 as final
|
FROM elementsproject/lightningd:v24.05 as final
|
||||||
|
|
||||||
COPY --from=builder /tmp/cln-grpc-hold/target/release/cln-grpc-hold /tmp/cln-grpc-hold
|
COPY --from=builder /tmp/holdinvoice/target/release/holdinvoice /tmp/holdinvoice
|
||||||
COPY config /tmp/config
|
COPY config /tmp/config
|
||||||
COPY entrypoint.sh entrypoint.sh
|
COPY entrypoint.sh entrypoint.sh
|
||||||
RUN chmod +x entrypoint.sh
|
RUN chmod +x entrypoint.sh
|
||||||
|
@ -5,6 +5,6 @@ addr=statictor:127.0.0.1:9051
|
|||||||
grpc-port=9999
|
grpc-port=9999
|
||||||
grpc-hold-port=9998
|
grpc-hold-port=9998
|
||||||
always-use-proxy=true
|
always-use-proxy=true
|
||||||
important-plugin=/root/.lightning/plugins/cln-grpc-hold
|
important-plugin=/root/.lightning/plugins/holdinvoice
|
||||||
# wallet=postgres://user:pass@localhost:5433/cln
|
# wallet=postgres://user:pass@localhost:5433/cln
|
||||||
# bookkeeper-db=postgres://user:pass@localhost:5433/cln
|
# bookkeeper-db=postgres://user:pass@localhost:5433/cln
|
@ -17,9 +17,9 @@ if [ "$EXPOSE_TCP" == "true" ]; then
|
|||||||
socat "TCP4-listen:$LIGHTNINGD_RPC_PORT,fork,reuseaddr" "UNIX-CONNECT:${networkdatadir}/lightning-rpc" &
|
socat "TCP4-listen:$LIGHTNINGD_RPC_PORT,fork,reuseaddr" "UNIX-CONNECT:${networkdatadir}/lightning-rpc" &
|
||||||
fg %-
|
fg %-
|
||||||
else
|
else
|
||||||
# Always copy the cln-grpc-hodl plugin into the plugins directory on start up
|
# Always copy the holdinvoice plugin into the plugins directory on start up
|
||||||
mkdir -p /root/.lightning/plugins
|
mkdir -p /root/.lightning/plugins
|
||||||
cp /tmp/cln-grpc-hold /root/.lightning/plugins/cln-grpc-hold
|
cp /tmp/holdinvoice /root/.lightning/plugins/holdinvoice
|
||||||
if [ ! -f /root/.lightning/config ]; then
|
if [ ! -f /root/.lightning/config ]; then
|
||||||
cp /tmp/config /root/.lightning/config
|
cp /tmp/config /root/.lightning/config
|
||||||
fi
|
fi
|
||||||
|
BIN
docker/cln/plugins/holdinvoice
Executable file
@ -28,15 +28,17 @@ This is a non-exhaustive compilation based on past experience of users. We have
|
|||||||
| Wallet | Version | Device | UX<sup>1</sup> | Bonds<sup>2</sup> | Payout<sup>3</sup> | Comp<sup>4</sup> | Total<sup>5</sup> |
|
| Wallet | Version | Device | UX<sup>1</sup> | Bonds<sup>2</sup> | Payout<sup>3</sup> | Comp<sup>4</sup> | Total<sup>5</sup> |
|
||||||
|:---|:---|:--:|:--:|:--:|:--:|:--:|:--:|
|
|:---|:---|:--:|:--:|:--:|:--:|:--:|:--:|
|
||||||
|[Alby](#alby-browser-extension)|[v1.14.2](https://github.com/getAlby/lightning-browser-extension)|{{page.laptop}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}} |{{page.thumbsup}}|
|
|[Alby](#alby-browser-extension)|[v1.14.2](https://github.com/getAlby/lightning-browser-extension)|{{page.laptop}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}} |{{page.thumbsup}}|
|
||||||
|
|[Aqua](#aqua-mobile)|[v0.1.55](https://aquawallet.io/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.unclear}}|{{page.good}} |{{page.thumbsup}}|
|
||||||
|[Blink](#blink-mobile-former-bitcoin-beach-wallet)|[2.2.73](https://www.blink.sv/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}} |{{page.thumbsup}}|
|
|[Blink](#blink-mobile-former-bitcoin-beach-wallet)|[2.2.73](https://www.blink.sv/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}} |{{page.thumbsup}}|
|
||||||
|[Blixt](#blixt-androidios-lnd-light-backend-on-device)|[v0.4.1](https://github.com/hsjoberg/blixt-wallet)|{{page.phone}}|{{page.soso}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}|
|
|[Blixt](#blixt-androidios-lnd-light-backend-on-device)|[v0.4.1](https://github.com/hsjoberg/blixt-wallet)|{{page.phone}}|{{page.soso}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}|
|
||||||
|[Blue](#bluewallet-mobile)|[1.4.4](https://bluewallet.io/)|{{page.phone}}|{{page.good}}|{{page.unclear}}|{{page.unclear}}|{{page.good}}|{{page.unclear}}|
|
|[Blue](#bluewallet-mobile)|[1.4.4](https://bluewallet.io/)|{{page.phone}}|{{page.good}}|{{page.unclear}}|{{page.unclear}}|{{page.good}}|{{page.unclear}}|
|
||||||
|[Breez](#breez-mobile)|[0.16](https://breez.technology/mobile/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}|
|
|[Breez](#breez-mobile)|[0.16](https://breez.technology/mobile/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}|
|
||||||
|[Cash App](#cash-app-mobile)|[4.7](https://cash.app/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}} |{{page.thumbsup}}|
|
|[Cash App](#cash-app-mobile)|[4.7](https://cash.app/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}} |{{page.thumbsup}}|
|
||||||
|[Core Lightning](#core-lightning--cln-cli-interface)|[v0.11.1](https://github.com/ElementsProject/lightning)|{{page.cli}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}|
|
|[Core Lightning](#core-lightning--cln-cli-interface)|[v0.11.1](https://github.com/ElementsProject/lightning)|{{page.cli}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}|
|
||||||
|[Electrum](#electrum-desktop)|[4.1.4](https://github.com/spesmilo/electrum)|{{page.laptop}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}||
|
|[Electrum](#electrum-mobile--desktop)|[4.1.4](https://github.com/spesmilo/electrum)|{{page.laptop}}{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}||
|
||||||
|[LND](#lnd-cli-interface)|[v0.14.2](https://github.com/LightningNetwork/lnd)|{{page.cli}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}|
|
|[LND](#lnd-cli-interface)|[v0.14.2](https://github.com/LightningNetwork/lnd)|{{page.cli}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}|
|
||||||
|[Mash](https://app.mash.com/wallet)|[Beta](https://mash.com/consumer-experience/)|{{page.laptop}}{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}} | {{page.thumbsup}}|
|
|[Mash](https://app.mash.com/wallet)|[Beta](https://mash.com/consumer-experience/)|{{page.laptop}}{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}} | {{page.thumbsup}}|
|
||||||
|
|[Mutiny](#mutiny-mobile--web-browser-wallet)|[1.7.1](https://www.mutinywallet.com/)|{{page.laptop}}{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsdown}}||
|
||||||
|[Muun](#muun-mobile)|[47.3](https://muun.com/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.bad}}|{{page.bad}}|{{page.thumbsdown}}|
|
|[Muun](#muun-mobile)|[47.3](https://muun.com/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.bad}}|{{page.bad}}|{{page.thumbsdown}}|
|
||||||
|[Phoenix](#phoenix-mobile)|[35-1.4.20](https://phoenix.acinq.co/)|{{page.phone}}|{{page.good}}|{{page.soso}}|{{page.soso}}|{{page.soso}}|{{page.unclear}}|
|
|[Phoenix](#phoenix-mobile)|[35-1.4.20](https://phoenix.acinq.co/)|{{page.phone}}|{{page.good}}|{{page.soso}}|{{page.soso}}|{{page.soso}}|{{page.unclear}}|
|
||||||
|[SBW](https://github.com/RoboSats/robosats/issues/44#issue-1135544303)|[2.4.27](https://github.com/btcontract/wallet/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}|
|
|[SBW](https://github.com/RoboSats/robosats/issues/44#issue-1135544303)|[2.4.27](https://github.com/btcontract/wallet/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}|
|
||||||
@ -57,6 +59,12 @@ Instructions to install Alby in Tor Browser:
|
|||||||
1. Install the Alby extension from the [Firefox add-ons store](https://addons.mozilla.org/en-US/firefox/addon/alby/)
|
1. Install the Alby extension from the [Firefox add-ons store](https://addons.mozilla.org/en-US/firefox/addon/alby/)
|
||||||
2. Click on the Alby extension and follow the prompts to setup your wallet.
|
2. Click on the Alby extension and follow the prompts to setup your wallet.
|
||||||
|
|
||||||
|
### Aqua (Mobile)
|
||||||
|
Overall the wallet works as expected. Hold invoices work reliably.
|
||||||
|
What is inconvenient:
|
||||||
|
- Lightning payments are encapsulated into Liquid Bitcoin so there is a small additional fee for conversion in/out
|
||||||
|
- Bond refund is locked for 3 days
|
||||||
|
|
||||||
### Blink (Mobile, former Bitcoin Beach Wallet)
|
### Blink (Mobile, former Bitcoin Beach Wallet)
|
||||||
Works well with RoboSats. Hodl invoices (Bonds) show as "Pending" in the transaction history. Payouts to the Blink wallet function as intended. Custodial wallet by Galoy which originated from the Bitcoin Beach project in El Salvador (formerly known as "Bitcoin Beach Wallet").
|
Works well with RoboSats. Hodl invoices (Bonds) show as "Pending" in the transaction history. Payouts to the Blink wallet function as intended. Custodial wallet by Galoy which originated from the Bitcoin Beach project in El Salvador (formerly known as "Bitcoin Beach Wallet").
|
||||||
|
|
||||||
@ -75,8 +83,11 @@ Works well with RoboSats. Hodl invoices (Bonds) show as "Pending" in the transac
|
|||||||
### Core Lightning / CLN (CLI Interface)
|
### Core Lightning / CLN (CLI Interface)
|
||||||
Works as expected. The `lightning-cli pay <invoice>` command does not conclude while the payment is pending, but can use `lightning-cli paystatus <invoice>` to monitor the state.
|
Works as expected. The `lightning-cli pay <invoice>` command does not conclude while the payment is pending, but can use `lightning-cli paystatus <invoice>` to monitor the state.
|
||||||
|
|
||||||
### Electrum (Desktop)
|
### Electrum (Mobile & Desktop)
|
||||||
Works as expected. Some payments and locks may fail depending on the Lightning node the channel is created to. Channels to ASINQ work fine.
|
Overall the wallet works as expected. The interface is precise and clear.
|
||||||
|
What is inconvenient:
|
||||||
|
- all your Lightning channels have to be created to the node: ACINQ
|
||||||
|
|
||||||
|
|
||||||
### LND (CLI Interface)
|
### LND (CLI Interface)
|
||||||
Raw; it shows exactly what is happening and what it knows "IN_FLIGHT". It is not user friendly and therefore not recommended to interact with RoboSats by beginners. However, everything works just fine. If you are using LNCLI regularly, then you will find no issue using it with RoboSats.
|
Raw; it shows exactly what is happening and what it knows "IN_FLIGHT". It is not user friendly and therefore not recommended to interact with RoboSats by beginners. However, everything works just fine. If you are using LNCLI regularly, then you will find no issue using it with RoboSats.
|
||||||
@ -85,6 +96,13 @@ Raw; it shows exactly what is happening and what it knows "IN_FLIGHT". It is not
|
|||||||
### Mash Wallet App (Mobile PWA & Desktop Web-Wallet)
|
### Mash Wallet App (Mobile PWA & Desktop Web-Wallet)
|
||||||
Overall the [Mash](https://mash.com/consumer-experience/) wallet works end2end with Robosats on both selling & buying over lightning. Majority of relevant invoice details in the mash wallet are shown and clear to users throughout the process. When the transactions are complete, they open in the mobile app on both sender/receiver sides to highlight that the transactions are completed.The one UX hick-up is that the pending invoices list doesn't explicitly show HOLD invoices and there is a "spinning" screen on first HOLD invoice payment. The team has a bug open to fix this issue shortly (this note is from Aug 21st 2023).
|
Overall the [Mash](https://mash.com/consumer-experience/) wallet works end2end with Robosats on both selling & buying over lightning. Majority of relevant invoice details in the mash wallet are shown and clear to users throughout the process. When the transactions are complete, they open in the mobile app on both sender/receiver sides to highlight that the transactions are completed.The one UX hick-up is that the pending invoices list doesn't explicitly show HOLD invoices and there is a "spinning" screen on first HOLD invoice payment. The team has a bug open to fix this issue shortly (this note is from Aug 21st 2023).
|
||||||
|
|
||||||
|
### Mutiny (Mobile & Web Browser Wallet)
|
||||||
|
The wallet should work as expected, but the interface, transaction states, and the structure of the funds can sometimes be very confusing in the current release version.
|
||||||
|
Use the default free Fedimint(Chaumian eCash) account, with the possibility to use zero fee Lightning transfers.
|
||||||
|
What is inconvenient:
|
||||||
|
- occasionally wallet restart is needed
|
||||||
|
- more than two pending hold invoices at the same time may cause a rejection of the new transaction
|
||||||
|
|
||||||
### Muun (Mobile)
|
### Muun (Mobile)
|
||||||
Similar to Blixt or LND, Muun plays nicely with hold invoices. You can be a seller in RoboSats using Muun and the user experience will be great. However, in order to be a buyer when using Muun, you need to submit an on-chain address for the payout as a Lightning invoice won't work. Muun is _fee siphoning attacking_ any sender to Muun wallet. There is a mandatory hop through a private channel with a fee of +1500ppm. RoboSats will strictly not route a buyer payout for a net loss. Given that RoboSats trading fees are {{site.robosats.total_fee}}% and it needs to cover the routing fees, **RoboSats will never find a suitable route to a Muun wallet user**. At the moment, RoboSats will scan your invoice for routing hints that can potentially encode a _fee siphoning attack_. If this trick is found, then the invoice will be rejected: submit an on-chain address instead for an on-the-fly swap. Refer to [Understand > On-Chain Payouts](/docs/on-chain-payouts/) for more information about on-the-fly swaps. Important to note that Muun has issues during times of high on chain fee spikes. Regardless, the workaround to receive to Muun is: either submit an on chain address or choose a higher routing budget after enabling the "Advanced Options" switch.
|
Similar to Blixt or LND, Muun plays nicely with hold invoices. You can be a seller in RoboSats using Muun and the user experience will be great. However, in order to be a buyer when using Muun, you need to submit an on-chain address for the payout as a Lightning invoice won't work. Muun is _fee siphoning attacking_ any sender to Muun wallet. There is a mandatory hop through a private channel with a fee of +1500ppm. RoboSats will strictly not route a buyer payout for a net loss. Given that RoboSats trading fees are {{site.robosats.total_fee}}% and it needs to cover the routing fees, **RoboSats will never find a suitable route to a Muun wallet user**. At the moment, RoboSats will scan your invoice for routing hints that can potentially encode a _fee siphoning attack_. If this trick is found, then the invoice will be rejected: submit an on-chain address instead for an on-the-fly swap. Refer to [Understand > On-Chain Payouts](/docs/on-chain-payouts/) for more information about on-the-fly swaps. Important to note that Muun has issues during times of high on chain fee spikes. Regardless, the workaround to receive to Muun is: either submit an on chain address or choose a higher routing budget after enabling the "Advanced Options" switch.
|
||||||
|
|
||||||
@ -93,6 +111,7 @@ One of the simplest and one of the best. The hodl invoice shows as "on fly", it
|
|||||||
*Update 26-10-23: At this moment it has no development or support
|
*Update 26-10-23: At this moment it has no development or support
|
||||||
|
|
||||||
### Phoenix (Mobile)
|
### Phoenix (Mobile)
|
||||||
|
DEV team stated that they do not want to support hold invoices.
|
||||||
*Update 21-10-23. Phoenix used to work as described here, but many things changed to worse with the last update of the wallet.
|
*Update 21-10-23. Phoenix used to work as described here, but many things changed to worse with the last update of the wallet.
|
||||||
Phoenix works very well as an order taker. Phoenix will also work well as an order maker as long as the order settings `public duration` + `deposit duration` are lower than 10 hours. Otherwise, you might have problems locking the maker bond. If the total duraton of bonds/escrow invoices exceeds 450 blocks, then Phoenix will not allow users to lock the bond (`Cannot add htlc (...) reason=expiry too big`).
|
Phoenix works very well as an order taker. Phoenix will also work well as an order maker as long as the order settings `public duration` + `deposit duration` are lower than 10 hours. Otherwise, you might have problems locking the maker bond. If the total duraton of bonds/escrow invoices exceeds 450 blocks, then Phoenix will not allow users to lock the bond (`Cannot add htlc (...) reason=expiry too big`).
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
openapi: 3.0.3
|
openapi: 3.0.3
|
||||||
info:
|
info:
|
||||||
title: RoboSats REST API
|
title: RoboSats REST API
|
||||||
version: 0.6.0
|
version: 0.6.2
|
||||||
x-logo:
|
x-logo:
|
||||||
url: https://raw.githubusercontent.com/Reckless-Satoshi/robosats/main/frontend/static/assets/images/robosats-0.1.1-banner.png
|
url: https://raw.githubusercontent.com/Reckless-Satoshi/robosats/main/frontend/static/assets/images/robosats-0.1.1-banner.png
|
||||||
backgroundColor: '#FFFFFF'
|
backgroundColor: '#FFFFFF'
|
||||||
@ -1638,7 +1638,7 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
maker_status:
|
maker_status:
|
||||||
type: string
|
type: string
|
||||||
description: Status of the nick - "Active" or "Inactive"
|
description: Status of the nick - "Active", "Seen Recently" or "Inactive"
|
||||||
price:
|
price:
|
||||||
type: number
|
type: number
|
||||||
format: double
|
format: double
|
||||||
|
@ -1539,7 +1539,7 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
maker_status:
|
maker_status:
|
||||||
type: string
|
type: string
|
||||||
description: Status of the nick - "Active" or "Inactive"
|
description: Status of the nick - "Active", "Seen Recently" or "Inactive"
|
||||||
price:
|
price:
|
||||||
type: integer
|
type: integer
|
||||||
description: Price in order's fiat currency
|
description: Price in order's fiat currency
|
||||||
|
@ -1712,7 +1712,7 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
maker_status:
|
maker_status:
|
||||||
type: string
|
type: string
|
||||||
description: Status of the nick - "Active" or "Inactive"
|
description: Status of the nick - "Active", "Seen Recently" or "Inactive"
|
||||||
price:
|
price:
|
||||||
type: number
|
type: number
|
||||||
format: double
|
format: double
|
||||||
|
12
fastlane/metadata/en-US/full_description.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<p>RoboSats is a simple and private app to exchange bitcoin for national currencies. Robosats simplifies the P2P user experience and uses lightning hold invoices to minimize custody and trust requirements. The deterministically generated robot avatars help users stick to best privacy practices.</p>
|
||||||
|
<p><br><b>Features:</b></p><ul>
|
||||||
|
<li>Privacy focused: your robot avatar is deterministically generated, no need for registration.</li>
|
||||||
|
<li>More than 10 languages available and over 60 fiat currencies</li>
|
||||||
|
<li>Safe: simply lock a lightning hodl invoice and show you are real and committed.</li>
|
||||||
|
<li>No data collection. Your communication with your peer is PGP encrypted, only you can read it.</li>
|
||||||
|
<li>Lightning fast: the average sovereign trade finishes in ~ 8 minutes. Faster than a single block confirmation!</li>
|
||||||
|
<li>Fully collateralized escrow: your peer is always committed and cannot run away with the funds.</li>
|
||||||
|
<li>Strong incentives system: attempts of cheating are penalized with the slashing of the Sats in the fidelity bond.</li>
|
||||||
|
<li>Guides and video tutorials available at https://learn.robosats.com/watch/en</li>
|
||||||
|
</ul>
|
||||||
|
<p>You can join other cool Robots and get community support at <a href="https://t.me/robosats">our Telegram group</a>.</p>
|
BIN
fastlane/metadata/en-US/images/icon.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
fastlane/metadata/en-US/images/phoneScreenshots/01.jpg
Normal file
After Width: | Height: | Size: 89 KiB |
BIN
fastlane/metadata/en-US/images/phoneScreenshots/02.jpg
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
fastlane/metadata/en-US/images/phoneScreenshots/03.jpg
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
fastlane/metadata/en-US/images/phoneScreenshots/04.jpg
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
fastlane/metadata/en-US/images/phoneScreenshots/05.jpg
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
fastlane/metadata/en-US/images/phoneScreenshots/06.jpg
Normal file
After Width: | Height: | Size: 56 KiB |
1
fastlane/metadata/en-US/short_description.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Simple and private app to exchange bitcoin for national currencies.
|
2545
frontend/package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.6.0",
|
"version": "0.6.2",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -41,7 +41,7 @@
|
|||||||
"eslint-plugin-react": "^7.34.0",
|
"eslint-plugin-react": "^7.34.0",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"jest": "^29.6.1",
|
"jest": "^29.6.1",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.3.2",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.4.2",
|
"typescript": "^5.4.2",
|
||||||
"webpack": "^5.89.0",
|
"webpack": "^5.89.0",
|
||||||
@ -57,10 +57,10 @@
|
|||||||
"@mui/lab": "^5.0.0-alpha.136",
|
"@mui/lab": "^5.0.0-alpha.136",
|
||||||
"@mui/material": "^5.15.14",
|
"@mui/material": "^5.15.14",
|
||||||
"@mui/system": "^5.15.11",
|
"@mui/system": "^5.15.11",
|
||||||
"@mui/x-data-grid": "^7.3.0",
|
"@mui/x-data-grid": "^7.6.0",
|
||||||
"@mui/x-date-pickers": "^7.2.0",
|
"@mui/x-date-pickers": "^7.2.0",
|
||||||
"@nivo/core": "^0.85.1",
|
"@nivo/core": "^0.86.0",
|
||||||
"@nivo/line": "^0.85.1",
|
"@nivo/line": "^0.86.0",
|
||||||
"base-ex": "^0.8.1",
|
"base-ex": "^0.8.1",
|
||||||
"country-flag-icons": "^1.5.11",
|
"country-flag-icons": "^1.5.11",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
|
@ -67,9 +67,11 @@ const RobotProfile = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleChangeSlot = (e: SelectChangeEvent<number | 'loading'>): void => {
|
const handleChangeSlot = (e: SelectChangeEvent<number | 'loading'>): void => {
|
||||||
garage.currentSlot = e.target.value;
|
if (e?.target?.value) {
|
||||||
setInputToken(garage.getSlot()?.token ?? '');
|
garage.setCurrentSlot(e.target.value as string);
|
||||||
setLoading(true);
|
setInputToken(garage.getSlot()?.token ?? '');
|
||||||
|
setLoading(true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const slot = garage.getSlot();
|
const slot = garage.getSlot();
|
||||||
|
@ -44,7 +44,7 @@ const RobotPage = (): JSX.Element => {
|
|||||||
const token = urlToken ?? garage.currentSlot;
|
const token = urlToken ?? garage.currentSlot;
|
||||||
if (token !== undefined && token !== null && page === 'robot') {
|
if (token !== undefined && token !== null && page === 'robot') {
|
||||||
setInputToken(token);
|
setInputToken(token);
|
||||||
if (window.NativeRobosats === undefined || torStatus === 'ON') {
|
if (window.NativeRobosats === undefined || torStatus === 'ON' || !settings.useProxy) {
|
||||||
getGenerateRobot(token);
|
getGenerateRobot(token);
|
||||||
setView('profile');
|
setView('profile');
|
||||||
}
|
}
|
||||||
@ -71,7 +71,7 @@ const RobotPage = (): JSX.Element => {
|
|||||||
encPrivKey: key.encryptedPrivateKeyArmored,
|
encPrivKey: key.encryptedPrivateKeyArmored,
|
||||||
});
|
});
|
||||||
void federation.fetchRobot(garage, token);
|
void federation.fetchRobot(garage, token);
|
||||||
garage.currentSlot = token;
|
garage.setCurrentSlot(token);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
@ -83,7 +83,7 @@ const RobotPage = (): JSX.Element => {
|
|||||||
garage.deleteSlot();
|
garage.deleteSlot();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!(window.NativeRobosats === undefined) && !(torStatus === 'ON')) {
|
if (settings.useProxy && !(window.NativeRobosats === undefined) && !(torStatus === 'ON')) {
|
||||||
return (
|
return (
|
||||||
<Paper
|
<Paper
|
||||||
elevation={12}
|
elevation={12}
|
||||||
|
@ -90,12 +90,6 @@ const MakerForm = ({
|
|||||||
const minRangeAmountMultiple = 1.6;
|
const minRangeAmountMultiple = 1.6;
|
||||||
const amountSafeThresholds = [1.03, 0.98];
|
const amountSafeThresholds = [1.03, 0.98];
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Why?
|
|
||||||
// const slot = garage.getSlot();
|
|
||||||
// if (slot?.token) void federation.fetchRobot(garage, slot?.token);
|
|
||||||
}, [garage.currentSlot]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrencyCode(currencyDict[fav.currency === 0 ? 1 : fav.currency]);
|
setCurrencyCode(currencyDict[fav.currency === 0 ? 1 : fav.currency]);
|
||||||
}, [coordinatorUpdatedAt]);
|
}, [coordinatorUpdatedAt]);
|
||||||
|
@ -178,7 +178,7 @@ const OrderDetails = ({
|
|||||||
: coordinator.info?.taker_fee ?? 0;
|
: coordinator.info?.taker_fee ?? 0;
|
||||||
const defaultRoutingBudget = 0.001;
|
const defaultRoutingBudget = 0.001;
|
||||||
const btc_now = order.satoshis_now / 100000000;
|
const btc_now = order.satoshis_now / 100000000;
|
||||||
const rate = order.amount > 0 ? order.amount / btc_now : Number(order.max_amount) / btc_now;
|
const rate = Number(order.max_amount ?? order.amount) / btc_now;
|
||||||
|
|
||||||
if (isBuyer) {
|
if (isBuyer) {
|
||||||
if (order.amount > 0) {
|
if (order.amount > 0) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { type UseAppStoreType, AppContext } from '../../contexts/AppContext';
|
import { type UseAppStoreType, AppContext } from '../../contexts/AppContext';
|
||||||
import {
|
import {
|
||||||
@ -28,17 +28,19 @@ import {
|
|||||||
QrCode,
|
QrCode,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import { systemClient } from '../../services/System';
|
import { systemClient } from '../../services/System';
|
||||||
|
import { TorIcon } from '../Icons';
|
||||||
import SwapCalls from '@mui/icons-material/SwapCalls';
|
import SwapCalls from '@mui/icons-material/SwapCalls';
|
||||||
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
|
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
|
||||||
|
import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
|
|
||||||
interface SettingsFormProps {
|
interface SettingsFormProps {
|
||||||
dense?: boolean;
|
dense?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
|
const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
|
||||||
const { fav, setFav, origin, hostUrl, settings, setSettings } =
|
const { fav, setFav, settings, setSettings } = useContext<UseAppStoreType>(AppContext);
|
||||||
useContext<UseAppStoreType>(AppContext);
|
|
||||||
const { federation } = useContext<UseFederationStoreType>(FederationContext);
|
const { federation } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
|
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const fontSizes = [
|
const fontSizes = [
|
||||||
@ -237,6 +239,29 @@ const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
|
|||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
</ToggleButtonGroup>
|
</ToggleButtonGroup>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
|
{window.NativeRobosats !== undefined && (
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<TorIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ToggleButtonGroup
|
||||||
|
exclusive={true}
|
||||||
|
value={settings.useProxy}
|
||||||
|
onChange={(_e, useProxy) => {
|
||||||
|
setSettings({ ...settings, useProxy });
|
||||||
|
systemClient.setItem('settings_use_proxy', String(useProxy));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ToggleButton value={true} color='primary'>
|
||||||
|
{t('Build-in')}
|
||||||
|
</ToggleButton>
|
||||||
|
<ToggleButton value={false} color='secondary'>
|
||||||
|
{t('Disabled')}
|
||||||
|
</ToggleButton>
|
||||||
|
</ToggleButtonGroup>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
</List>
|
</List>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -55,10 +55,10 @@ const TorIndicator = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TorConnectionBadge = (): JSX.Element => {
|
const TorConnectionBadge = (): JSX.Element => {
|
||||||
const { torStatus } = useContext<UseAppStoreType>(AppContext);
|
const { torStatus, settings } = useContext<UseAppStoreType>(AppContext);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (window?.NativeRobosats == null) {
|
if (window?.NativeRobosats == null || !settings.useProxy) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,6 +222,7 @@ export const SuccessfulPrompt = ({
|
|||||||
takerSummary={order.taker_summary}
|
takerSummary={order.taker_summary}
|
||||||
platformSummary={order.platform_summary}
|
platformSummary={order.platform_summary}
|
||||||
orderId={order.id}
|
orderId={order.id}
|
||||||
|
coordinatorLongAlias={federation.getCoordinator(order.shortAlias)?.longAlias}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
) : (
|
) : (
|
||||||
|
@ -43,6 +43,7 @@ interface Props {
|
|||||||
makerHashId: string;
|
makerHashId: string;
|
||||||
takerHashId: string;
|
takerHashId: string;
|
||||||
currencyCode: string;
|
currencyCode: string;
|
||||||
|
coordinatorLongAlias: string;
|
||||||
makerSummary: TradeRobotSummary;
|
makerSummary: TradeRobotSummary;
|
||||||
takerSummary: TradeRobotSummary;
|
takerSummary: TradeRobotSummary;
|
||||||
platformSummary: TradeCoordinatorSummary;
|
platformSummary: TradeCoordinatorSummary;
|
||||||
@ -54,6 +55,7 @@ const TradeSummary = ({
|
|||||||
makerHashId,
|
makerHashId,
|
||||||
takerHashId,
|
takerHashId,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
|
coordinatorLongAlias,
|
||||||
makerSummary,
|
makerSummary,
|
||||||
takerSummary,
|
takerSummary,
|
||||||
platformSummary,
|
platformSummary,
|
||||||
@ -72,6 +74,7 @@ const TradeSummary = ({
|
|||||||
|
|
||||||
const onClickExport = function (): void {
|
const onClickExport = function (): void {
|
||||||
const summary = {
|
const summary = {
|
||||||
|
coordinator: coordinatorLongAlias,
|
||||||
order_id: orderId,
|
order_id: orderId,
|
||||||
currency: currencyCode,
|
currency: currencyCode,
|
||||||
maker: makerSummary,
|
maker: makerSummary,
|
||||||
|
@ -111,12 +111,14 @@ export const FederationContextProvider = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// On bitcoin network change we reset book, limits and federation info and fetch everything again
|
if (window.NativeRobosats === undefined || torStatus === 'ON' || !settings.useProxy) {
|
||||||
if (window.NativeRobosats === undefined || torStatus === 'ON') {
|
|
||||||
void federation.updateUrl(origin, settings, hostUrl);
|
void federation.updateUrl(origin, settings, hostUrl);
|
||||||
void federation.update();
|
void federation.update();
|
||||||
|
|
||||||
|
const token = garage.getSlot()?.getRobot()?.token;
|
||||||
|
if (token) void federation.fetchRobot(garage, token);
|
||||||
}
|
}
|
||||||
}, [settings.network, torStatus]);
|
}, [settings.network, settings.useProxy, torStatus]);
|
||||||
|
|
||||||
const onOrderReceived = (order: Order): void => {
|
const onOrderReceived = (order: Order): void => {
|
||||||
let newDelay = defaultDelay;
|
let newDelay = defaultDelay;
|
||||||
@ -178,15 +180,6 @@ export const FederationContextProvider = ({
|
|||||||
if (page === 'offers') void federation.updateBook();
|
if (page === 'offers') void federation.updateBook();
|
||||||
}, [page]);
|
}, [page]);
|
||||||
|
|
||||||
// use effects to fetchRobots on app start and network change
|
|
||||||
useEffect(() => {
|
|
||||||
const slot = garage.getSlot();
|
|
||||||
const robot = slot?.getRobot();
|
|
||||||
|
|
||||||
if (robot && garage.currentSlot && slot?.token && robot.encPrivKey && robot.pubKey) {
|
|
||||||
void federation.fetchRobot(garage, slot.token);
|
|
||||||
}
|
|
||||||
}, [settings.network]);
|
|
||||||
// use effects to fetchRobots on Profile open
|
// use effects to fetchRobots on Profile open
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const slot = garage.getSlot();
|
const slot = garage.getSlot();
|
||||||
|
@ -145,7 +145,7 @@ export class Coordinator {
|
|||||||
public loadingInfo: boolean = false;
|
public loadingInfo: boolean = false;
|
||||||
public limits: LimitList = {};
|
public limits: LimitList = {};
|
||||||
public loadingLimits: boolean = false;
|
public loadingLimits: boolean = false;
|
||||||
public loadingRobot: boolean = true;
|
public loadingRobot: string | null;
|
||||||
|
|
||||||
updateUrl = (origin: Origin, settings: Settings, hostUrl: string): void => {
|
updateUrl = (origin: Origin, settings: Settings, hostUrl: string): void => {
|
||||||
if (settings.selfhostedClient && this.shortAlias !== 'local') {
|
if (settings.selfhostedClient && this.shortAlias !== 'local') {
|
||||||
@ -185,6 +185,7 @@ export class Coordinator {
|
|||||||
if (this.loadingBook) return;
|
if (this.loadingBook) return;
|
||||||
|
|
||||||
this.loadingBook = true;
|
this.loadingBook = true;
|
||||||
|
this.book = [];
|
||||||
|
|
||||||
apiClient
|
apiClient
|
||||||
.get(this.url, `${this.basePath}/api/book/`)
|
.get(this.url, `${this.basePath}/api/book/`)
|
||||||
@ -297,7 +298,7 @@ export class Coordinator {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fetchRobot = async (garage: Garage, token: string): Promise<Robot | null> => {
|
fetchRobot = async (garage: Garage, token: string): Promise<Robot | null> => {
|
||||||
if (!this.enabled || !token) return null;
|
if (!this.enabled || !token || this.loadingRobot === token) return null;
|
||||||
|
|
||||||
const robot = garage?.getSlot(token)?.getRobot() ?? null;
|
const robot = garage?.getSlot(token)?.getRobot() ?? null;
|
||||||
const authHeaders = robot?.getAuthHeaders();
|
const authHeaders = robot?.getAuthHeaders();
|
||||||
@ -308,6 +309,8 @@ export class Coordinator {
|
|||||||
|
|
||||||
if (!hasEnoughEntropy) return null;
|
if (!hasEnoughEntropy) return null;
|
||||||
|
|
||||||
|
this.loadingRobot = token;
|
||||||
|
|
||||||
garage.updateRobot(token, this.shortAlias, { loading: true });
|
garage.updateRobot(token, this.shortAlias, { loading: true });
|
||||||
|
|
||||||
const newAttributes = await apiClient
|
const newAttributes = await apiClient
|
||||||
@ -330,7 +333,8 @@ export class Coordinator {
|
|||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
});
|
})
|
||||||
|
.finally(() => (this.loadingRobot = null));
|
||||||
|
|
||||||
garage.updateRobot(token, this.shortAlias, {
|
garage.updateRobot(token, this.shortAlias, {
|
||||||
...newAttributes,
|
...newAttributes,
|
||||||
|
@ -101,7 +101,7 @@ export class Federation {
|
|||||||
this.exchange.loadingCoordinators = Object.keys(this.coordinators).length;
|
this.exchange.loadingCoordinators = Object.keys(this.coordinators).length;
|
||||||
this.updateEnabledCoordinators();
|
this.updateEnabledCoordinators();
|
||||||
for (const coor of Object.values(this.coordinators)) {
|
for (const coor of Object.values(this.coordinators)) {
|
||||||
await coor.update(() => {
|
coor.update(() => {
|
||||||
this.exchange.onlineCoordinators = this.exchange.onlineCoordinators + 1;
|
this.exchange.onlineCoordinators = this.exchange.onlineCoordinators + 1;
|
||||||
this.onCoordinatorSaved();
|
this.onCoordinatorSaved();
|
||||||
});
|
});
|
||||||
@ -110,10 +110,11 @@ export class Federation {
|
|||||||
|
|
||||||
updateBook = async (): Promise<void> => {
|
updateBook = async (): Promise<void> => {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
this.book = [];
|
||||||
this.triggerHook('onCoordinatorUpdate');
|
this.triggerHook('onCoordinatorUpdate');
|
||||||
this.exchange.loadingCoordinators = Object.keys(this.coordinators).length;
|
this.exchange.loadingCoordinators = Object.keys(this.coordinators).length;
|
||||||
for (const coor of Object.values(this.coordinators)) {
|
for (const coor of Object.values(this.coordinators)) {
|
||||||
await coor.updateBook(() => {
|
coor.updateBook(() => {
|
||||||
this.onCoordinatorSaved();
|
this.onCoordinatorSaved();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -106,6 +106,11 @@ class Garage {
|
|||||||
return slot;
|
return slot;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setCurrentSlot: (currentSlot: string) => void = (currentSlot) => {
|
||||||
|
this.currentSlot = currentSlot;
|
||||||
|
this.triggerHook('onRobotUpdate');
|
||||||
|
};
|
||||||
|
|
||||||
// Robots
|
// Robots
|
||||||
createRobot: (token: string, shortAliases: string[], attributes: Record<any, any>) => void = (
|
createRobot: (token: string, shortAliases: string[], attributes: Record<any, any>) => void = (
|
||||||
token,
|
token,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import i18n from '../i18n/Web';
|
import i18n from '../i18n/Web';
|
||||||
import { systemClient } from '../services/System';
|
import { systemClient } from '../services/System';
|
||||||
|
import { apiClient } from '../services/api';
|
||||||
import { getHost } from '../utils';
|
import { getHost } from '../utils';
|
||||||
|
|
||||||
export type Language =
|
export type Language =
|
||||||
@ -42,8 +43,13 @@ class BaseSettings {
|
|||||||
: i18n.resolvedLanguage.substring(0, 2);
|
: i18n.resolvedLanguage.substring(0, 2);
|
||||||
|
|
||||||
const networkCookie = systemClient.getItem('settings_network');
|
const networkCookie = systemClient.getItem('settings_network');
|
||||||
this.network = networkCookie !== '' ? networkCookie : 'mainnet';
|
this.network = networkCookie && networkCookie !== '' ? networkCookie : 'mainnet';
|
||||||
this.host = getHost();
|
this.host = getHost();
|
||||||
|
|
||||||
|
const useProxy = systemClient.getItem('settings_use_proxy');
|
||||||
|
this.useProxy = window.NativeRobosats !== undefined && useProxy !== 'false';
|
||||||
|
|
||||||
|
apiClient.useProxy = this.useProxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public frontend: 'basic' | 'pro' = 'basic';
|
public frontend: 'basic' | 'pro' = 'basic';
|
||||||
@ -56,6 +62,7 @@ class BaseSettings {
|
|||||||
public host?: string;
|
public host?: string;
|
||||||
public unsafeClient: boolean = false;
|
public unsafeClient: boolean = false;
|
||||||
public selfhostedClient: boolean = false;
|
public selfhostedClient: boolean = false;
|
||||||
|
public useProxy: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BaseSettings;
|
export default BaseSettings;
|
||||||
|
@ -28,7 +28,7 @@ class SystemNativeClient implements SystemClient {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public setCookie: (key: string, value: string) => void = (key, value) => {
|
public setCookie: (key: string, value: string) => void = (key, value) => {
|
||||||
delete window.NativeRobosats?.cookies[key];
|
window.NativeRobosats?.loadCookie({ key, value });
|
||||||
void window.NativeRobosats?.postMessage({
|
void window.NativeRobosats?.postMessage({
|
||||||
category: 'system',
|
category: 'system',
|
||||||
type: 'setCookie',
|
type: 'setCookie',
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
import { type ApiClient, type Auth } from '..';
|
import { type ApiClient, type Auth } from '..';
|
||||||
import { systemClient } from '../../System';
|
import { systemClient } from '../../System';
|
||||||
|
import ApiWebClient from '../ApiWebClient';
|
||||||
|
|
||||||
class ApiNativeClient implements ApiClient {
|
class ApiNativeClient implements ApiClient {
|
||||||
private readonly assetsCache: Record<string, string> = {};
|
public useProxy = true;
|
||||||
|
|
||||||
|
private webClient: ApiClient = new ApiWebClient();
|
||||||
|
|
||||||
private readonly assetsPromises = new Map<string, Promise<string | undefined>>();
|
private readonly assetsPromises = new Map<string, Promise<string | undefined>>();
|
||||||
|
|
||||||
private readonly getHeaders: (auth?: Auth) => HeadersInit = (auth) => {
|
private readonly getHeaders: (auth?: Auth) => HeadersInit = (auth) => {
|
||||||
@ -51,6 +55,7 @@ class ApiNativeClient implements ApiClient {
|
|||||||
|
|
||||||
public delete: (baseUrl: string, path: string, auth?: Auth) => Promise<object | undefined> =
|
public delete: (baseUrl: string, path: string, auth?: Auth) => Promise<object | undefined> =
|
||||||
async (baseUrl, path, auth) => {
|
async (baseUrl, path, auth) => {
|
||||||
|
if (!this.proxy) this.webClient.delete(baseUrl, path, auth);
|
||||||
return await window.NativeRobosats?.postMessage({
|
return await window.NativeRobosats?.postMessage({
|
||||||
category: 'http',
|
category: 'http',
|
||||||
type: 'delete',
|
type: 'delete',
|
||||||
@ -66,6 +71,7 @@ class ApiNativeClient implements ApiClient {
|
|||||||
body: object,
|
body: object,
|
||||||
auth?: Auth,
|
auth?: Auth,
|
||||||
) => Promise<object | undefined> = async (baseUrl, path, body, auth) => {
|
) => Promise<object | undefined> = async (baseUrl, path, body, auth) => {
|
||||||
|
if (!this.proxy) this.webClient.post(baseUrl, path, body, auth);
|
||||||
return await window.NativeRobosats?.postMessage({
|
return await window.NativeRobosats?.postMessage({
|
||||||
category: 'http',
|
category: 'http',
|
||||||
type: 'post',
|
type: 'post',
|
||||||
@ -81,6 +87,7 @@ class ApiNativeClient implements ApiClient {
|
|||||||
path,
|
path,
|
||||||
auth,
|
auth,
|
||||||
) => {
|
) => {
|
||||||
|
if (!this.proxy) this.webClient.get(baseUrl, path, auth);
|
||||||
return await window.NativeRobosats?.postMessage({
|
return await window.NativeRobosats?.postMessage({
|
||||||
category: 'http',
|
category: 'http',
|
||||||
type: 'get',
|
type: 'get',
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { type ApiClient, type Auth } from '..';
|
import { type ApiClient, type Auth } from '..';
|
||||||
|
|
||||||
class ApiWebClient implements ApiClient {
|
class ApiWebClient implements ApiClient {
|
||||||
|
public useProxy = false;
|
||||||
|
|
||||||
private readonly getHeaders: (auth?: Auth) => HeadersInit = (auth) => {
|
private readonly getHeaders: (auth?: Auth) => HeadersInit = (auth) => {
|
||||||
let headers = {
|
let headers = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
@ -7,6 +7,7 @@ export interface Auth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiClient {
|
export interface ApiClient {
|
||||||
|
useProxy: boolean;
|
||||||
post: (baseUrl: string, path: string, body: object, auth?: Auth) => Promise<object | undefined>;
|
post: (baseUrl: string, path: string, body: object, auth?: Auth) => Promise<object | undefined>;
|
||||||
put: (baseUrl: string, path: string, body: object, auth?: Auth) => Promise<object | undefined>;
|
put: (baseUrl: string, path: string, body: object, auth?: Auth) => Promise<object | undefined>;
|
||||||
get: (baseUrl: string, path: string, auth?: Auth) => Promise<object | undefined>;
|
get: (baseUrl: string, path: string, auth?: Auth) => Promise<object | undefined>;
|
||||||
|
@ -45,7 +45,6 @@ export default function federationLottery(federation: Federation): string[] {
|
|||||||
// federation[shortAlias] = { badges:{ donatesToDevFund }};
|
// federation[shortAlias] = { badges:{ donatesToDevFund }};
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// console.log(federation)
|
|
||||||
// return federation;
|
// return federation;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@ -58,5 +57,4 @@ export default function federationLottery(federation: Federation): string[] {
|
|||||||
// results.push(rankedCoordinators);
|
// results.push(rankedCoordinators);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// console.log(results)
|
|
||||||
// }
|
// }
|
||||||
|
@ -464,8 +464,8 @@
|
|||||||
"The order has expired": "L'ordre ha expirat",
|
"The order has expired": "L'ordre ha expirat",
|
||||||
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
||||||
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Encara no pots prendre cap ordre! Espera {{timeMin}}m {{timeSec}}s",
|
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Encara no pots prendre cap ordre! Espera {{timeMin}}m {{timeSec}}s",
|
||||||
"You receive {{amount}} Sats (Approx)": "Tu reps via Lightning {{amount}} Sats (Approx)",
|
|
||||||
"You receive via {{method}} {{amount}}": "Reps via {{method}} {{amount}}",
|
"You receive via {{method}} {{amount}}": "Reps via {{method}} {{amount}}",
|
||||||
|
"You receive {{amount}} Sats (Approx)": "Tu reps via Lightning {{amount}} Sats (Approx)",
|
||||||
"You send via Lightning {{amount}} Sats (Approx)": "Tu envies via Lightning {{amount}} Sats (Approx)",
|
"You send via Lightning {{amount}} Sats (Approx)": "Tu envies via Lightning {{amount}} Sats (Approx)",
|
||||||
"You send via {{method}} {{amount}}": "Envies via {{method}} {{amount}}",
|
"You send via {{method}} {{amount}}": "Envies via {{method}} {{amount}}",
|
||||||
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prima: {{premium}}%",
|
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prima: {{premium}}%",
|
||||||
@ -489,7 +489,9 @@
|
|||||||
"Your last order #{{orderID}}": "La teva última ordre #{{orderID}}",
|
"Your last order #{{orderID}}": "La teva última ordre #{{orderID}}",
|
||||||
"finished order": "finished order",
|
"finished order": "finished order",
|
||||||
"#43": "Phrases in components/SettingsForm/index.tsx",
|
"#43": "Phrases in components/SettingsForm/index.tsx",
|
||||||
|
"Build-in": "Build-in",
|
||||||
"Dark": "Fosc",
|
"Dark": "Fosc",
|
||||||
|
"Disabled": "Disabled",
|
||||||
"Fiat": "Fiat",
|
"Fiat": "Fiat",
|
||||||
"Light": "Clar",
|
"Light": "Clar",
|
||||||
"Mainnet": "Mainnet",
|
"Mainnet": "Mainnet",
|
||||||
|
@ -464,8 +464,8 @@
|
|||||||
"The order has expired": "Nabídka vypršela",
|
"The order has expired": "Nabídka vypršela",
|
||||||
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
||||||
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Nabídku nemůžeš zatím příjmout! Počkej {{timeMin}}m {{timeSec}}s",
|
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Nabídku nemůžeš zatím příjmout! Počkej {{timeMin}}m {{timeSec}}s",
|
||||||
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
|
||||||
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
|
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
|
||||||
|
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
||||||
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
||||||
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
|
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
|
||||||
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Přirážka: {{premium}}%",
|
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Přirážka: {{premium}}%",
|
||||||
@ -489,7 +489,9 @@
|
|||||||
"Your last order #{{orderID}}": "Tvá poslední nabídka #{{orderID}}",
|
"Your last order #{{orderID}}": "Tvá poslední nabídka #{{orderID}}",
|
||||||
"finished order": "finished order",
|
"finished order": "finished order",
|
||||||
"#43": "Phrases in components/SettingsForm/index.tsx",
|
"#43": "Phrases in components/SettingsForm/index.tsx",
|
||||||
|
"Build-in": "Build-in",
|
||||||
"Dark": "Dark",
|
"Dark": "Dark",
|
||||||
|
"Disabled": "Disabled",
|
||||||
"Fiat": "Fiat",
|
"Fiat": "Fiat",
|
||||||
"Light": "Light",
|
"Light": "Light",
|
||||||
"Mainnet": "Mainnet",
|
"Mainnet": "Mainnet",
|
||||||
|
@ -464,8 +464,8 @@
|
|||||||
"The order has expired": "Die Order ist abgelaufen",
|
"The order has expired": "Die Order ist abgelaufen",
|
||||||
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
||||||
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Du kannst noch keine Order annehmen! Warte {{timeMin}}m {{timeSec}}s",
|
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Du kannst noch keine Order annehmen! Warte {{timeMin}}m {{timeSec}}s",
|
||||||
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
|
||||||
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
|
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
|
||||||
|
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
||||||
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
||||||
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
|
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
|
||||||
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Aufschlag: {{premium}}%",
|
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Aufschlag: {{premium}}%",
|
||||||
@ -489,7 +489,9 @@
|
|||||||
"Your last order #{{orderID}}": "Deine letzte Order #{{orderID}}",
|
"Your last order #{{orderID}}": "Deine letzte Order #{{orderID}}",
|
||||||
"finished order": "finished order",
|
"finished order": "finished order",
|
||||||
"#43": "Phrases in components/SettingsForm/index.tsx",
|
"#43": "Phrases in components/SettingsForm/index.tsx",
|
||||||
|
"Build-in": "Build-in",
|
||||||
"Dark": "Dark",
|
"Dark": "Dark",
|
||||||
|
"Disabled": "Disabled",
|
||||||
"Fiat": "Fiat",
|
"Fiat": "Fiat",
|
||||||
"Light": "Light",
|
"Light": "Light",
|
||||||
"Mainnet": "Mainnet",
|
"Mainnet": "Mainnet",
|
||||||
|
@ -464,8 +464,8 @@
|
|||||||
"The order has expired": "The order has expired",
|
"The order has expired": "The order has expired",
|
||||||
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
||||||
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s",
|
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s",
|
||||||
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
|
||||||
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
|
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
|
||||||
|
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
||||||
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
||||||
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
|
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
|
||||||
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%",
|
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%",
|
||||||
@ -489,7 +489,9 @@
|
|||||||
"Your last order #{{orderID}}": "Your last order #{{orderID}}",
|
"Your last order #{{orderID}}": "Your last order #{{orderID}}",
|
||||||
"finished order": "finished order",
|
"finished order": "finished order",
|
||||||
"#43": "Phrases in components/SettingsForm/index.tsx",
|
"#43": "Phrases in components/SettingsForm/index.tsx",
|
||||||
|
"Build-in": "Build-in",
|
||||||
"Dark": "Dark",
|
"Dark": "Dark",
|
||||||
|
"Disabled": "Disabled",
|
||||||
"Fiat": "Fiat",
|
"Fiat": "Fiat",
|
||||||
"Light": "Light",
|
"Light": "Light",
|
||||||
"Mainnet": "Mainnet",
|
"Mainnet": "Mainnet",
|
||||||
|
@ -464,8 +464,8 @@
|
|||||||
"The order has expired": "La orden ha expirado",
|
"The order has expired": "La orden ha expirado",
|
||||||
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
||||||
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "¡No puedes tomar una orden aún! Espera {{timeMin}}m {{timeSec}}s",
|
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "¡No puedes tomar una orden aún! Espera {{timeMin}}m {{timeSec}}s",
|
||||||
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
|
||||||
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
|
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
|
||||||
|
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
||||||
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
||||||
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
|
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
|
||||||
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prima: {{premium}}%",
|
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prima: {{premium}}%",
|
||||||
@ -489,7 +489,9 @@
|
|||||||
"Your last order #{{orderID}}": "Tu última orden #{{orderID}}",
|
"Your last order #{{orderID}}": "Tu última orden #{{orderID}}",
|
||||||
"finished order": "finished order",
|
"finished order": "finished order",
|
||||||
"#43": "Phrases in components/SettingsForm/index.tsx",
|
"#43": "Phrases in components/SettingsForm/index.tsx",
|
||||||
|
"Build-in": "Build-in",
|
||||||
"Dark": "Oscuro",
|
"Dark": "Oscuro",
|
||||||
|
"Disabled": "Disabled",
|
||||||
"Fiat": "Fiat",
|
"Fiat": "Fiat",
|
||||||
"Light": "Claro",
|
"Light": "Claro",
|
||||||
"Mainnet": "Mainnet",
|
"Mainnet": "Mainnet",
|
||||||
|
@ -464,8 +464,8 @@
|
|||||||
"The order has expired": "Eskaera iraungi da",
|
"The order has expired": "Eskaera iraungi da",
|
||||||
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
||||||
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Oraindik ezin duzu eskaerarik hartu! Itxaron{{timeMin}}m {{timeSec}}s",
|
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Oraindik ezin duzu eskaerarik hartu! Itxaron{{timeMin}}m {{timeSec}}s",
|
||||||
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
|
||||||
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
|
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
|
||||||
|
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
||||||
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
||||||
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
|
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
|
||||||
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prima: %{{premium}}",
|
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prima: %{{premium}}",
|
||||||
@ -489,7 +489,9 @@
|
|||||||
"Your last order #{{orderID}}": "Zure azken eskaera #{{orderID}}",
|
"Your last order #{{orderID}}": "Zure azken eskaera #{{orderID}}",
|
||||||
"finished order": "finished order",
|
"finished order": "finished order",
|
||||||
"#43": "Phrases in components/SettingsForm/index.tsx",
|
"#43": "Phrases in components/SettingsForm/index.tsx",
|
||||||
|
"Build-in": "Build-in",
|
||||||
"Dark": "Dark",
|
"Dark": "Dark",
|
||||||
|
"Disabled": "Disabled",
|
||||||
"Fiat": "Fiat",
|
"Fiat": "Fiat",
|
||||||
"Light": "Light",
|
"Light": "Light",
|
||||||
"Mainnet": "Mainnet",
|
"Mainnet": "Mainnet",
|
||||||
|
@ -464,8 +464,8 @@
|
|||||||
"The order has expired": "L'ordre a expiré",
|
"The order has expired": "L'ordre a expiré",
|
||||||
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
||||||
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Vous ne pouvez pas encore prendre un ordre! Attendez {{timeMin}}m {{timeSec}}s",
|
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Vous ne pouvez pas encore prendre un ordre! Attendez {{timeMin}}m {{timeSec}}s",
|
||||||
"You receive {{amount}} Sats (Approx)": "Vous recevez via Lightning {{amount}} Sats (environ)",
|
|
||||||
"You receive via {{method}} {{amount}}": "Vous recevez via {{méthode}} {{montant}}",
|
"You receive via {{method}} {{amount}}": "Vous recevez via {{méthode}} {{montant}}",
|
||||||
|
"You receive {{amount}} Sats (Approx)": "Vous recevez via Lightning {{amount}} Sats (environ)",
|
||||||
"You send via Lightning {{amount}} Sats (Approx)": "Vous envoyez via Lightning {{amount}} Sats (environ)",
|
"You send via Lightning {{amount}} Sats (Approx)": "Vous envoyez via Lightning {{amount}} Sats (environ)",
|
||||||
"You send via {{method}} {{amount}}": "Vous envoyez via {{method}} {{amount}}",
|
"You send via {{method}} {{amount}}": "Vous envoyez via {{method}} {{amount}}",
|
||||||
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prime: {{premium}}%",
|
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prime: {{premium}}%",
|
||||||
@ -489,7 +489,9 @@
|
|||||||
"Your last order #{{orderID}}": "Votre dernière commande #{{orderID}}",
|
"Your last order #{{orderID}}": "Votre dernière commande #{{orderID}}",
|
||||||
"finished order": "finished order",
|
"finished order": "finished order",
|
||||||
"#43": "Phrases in components/SettingsForm/index.tsx",
|
"#43": "Phrases in components/SettingsForm/index.tsx",
|
||||||
|
"Build-in": "Build-in",
|
||||||
"Dark": "Sombre",
|
"Dark": "Sombre",
|
||||||
|
"Disabled": "Disabled",
|
||||||
"Fiat": "Fiat",
|
"Fiat": "Fiat",
|
||||||
"Light": "Light",
|
"Light": "Light",
|
||||||
"Mainnet": "Mainnet",
|
"Mainnet": "Mainnet",
|
||||||
|
@ -464,8 +464,8 @@
|
|||||||
"The order has expired": "L'ordine è scaduto",
|
"The order has expired": "L'ordine è scaduto",
|
||||||
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "La posizione appuntata è approssimativa. La posizione esatta del luogo dell'incontro deve essere indicata nella chat crittografata.",
|
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "La posizione appuntata è approssimativa. La posizione esatta del luogo dell'incontro deve essere indicata nella chat crittografata.",
|
||||||
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Non puoi ancora accettare un ordine! Aspetta {{timeMin}}m {{timeSec}}s",
|
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Non puoi ancora accettare un ordine! Aspetta {{timeMin}}m {{timeSec}}s",
|
||||||
"You receive {{amount}} Sats (Approx)": "Ricevi {{amount}} Sats via Lightning (approssimativo)",
|
|
||||||
"You receive via {{method}} {{amount}}": "Ricevi {{amount}} via {{method}}",
|
"You receive via {{method}} {{amount}}": "Ricevi {{amount}} via {{method}}",
|
||||||
|
"You receive {{amount}} Sats (Approx)": "Ricevi {{amount}} Sats via Lightning (approssimativo)",
|
||||||
"You send via Lightning {{amount}} Sats (Approx)": "Invii {{amount}} Sats via Lightning (approssimativo)",
|
"You send via Lightning {{amount}} Sats (Approx)": "Invii {{amount}} Sats via Lightning (approssimativo)",
|
||||||
"You send via {{method}} {{amount}}": "Invii {{amount}} via {{method}}",
|
"You send via {{method}} {{amount}}": "Invii {{amount}} via {{method}}",
|
||||||
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premio: {{premium}}%",
|
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premio: {{premium}}%",
|
||||||
@ -489,7 +489,9 @@
|
|||||||
"Your last order #{{orderID}}": "Il tuo ultimo ordine #{{orderID}}",
|
"Your last order #{{orderID}}": "Il tuo ultimo ordine #{{orderID}}",
|
||||||
"finished order": "finished order",
|
"finished order": "finished order",
|
||||||
"#43": "Phrases in components/SettingsForm/index.tsx",
|
"#43": "Phrases in components/SettingsForm/index.tsx",
|
||||||
|
"Build-in": "Build-in",
|
||||||
"Dark": "Scuro",
|
"Dark": "Scuro",
|
||||||
|
"Disabled": "Disabled",
|
||||||
"Fiat": "Fiat",
|
"Fiat": "Fiat",
|
||||||
"Light": "Chiaro",
|
"Light": "Chiaro",
|
||||||
"Mainnet": "Mainnet",
|
"Mainnet": "Mainnet",
|
||||||
|
@ -464,8 +464,8 @@
|
|||||||
"The order has expired": "注文は期限切れになりました",
|
"The order has expired": "注文は期限切れになりました",
|
||||||
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
||||||
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "まだ注文を受け取ることはできません!{{timeMin}}分{{timeSec}}秒待ってください",
|
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "まだ注文を受け取ることはできません!{{timeMin}}分{{timeSec}}秒待ってください",
|
||||||
"You receive {{amount}} Sats (Approx)": "ライトニングで{{amount}} Sats(約)を受け取ります",
|
|
||||||
"You receive via {{method}} {{amount}}": "{{method}}で{{amount}}を受け取ります",
|
"You receive via {{method}} {{amount}}": "{{method}}で{{amount}}を受け取ります",
|
||||||
|
"You receive {{amount}} Sats (Approx)": "ライトニングで{{amount}} Sats(約)を受け取ります",
|
||||||
"You send via Lightning {{amount}} Sats (Approx)": "ライトニングで{{amount}} Sats(約)を送信します",
|
"You send via Lightning {{amount}} Sats (Approx)": "ライトニングで{{amount}} Sats(約)を送信します",
|
||||||
"You send via {{method}} {{amount}}": "{{method}}で{{amount}}を送信します",
|
"You send via {{method}} {{amount}}": "{{method}}で{{amount}}を送信します",
|
||||||
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - プレミアム: {{premium}}%",
|
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - プレミアム: {{premium}}%",
|
||||||
@ -489,7 +489,9 @@
|
|||||||
"Your last order #{{orderID}}": "前回のオーダー #{{orderID}}",
|
"Your last order #{{orderID}}": "前回のオーダー #{{orderID}}",
|
||||||
"finished order": "finished order",
|
"finished order": "finished order",
|
||||||
"#43": "Phrases in components/SettingsForm/index.tsx",
|
"#43": "Phrases in components/SettingsForm/index.tsx",
|
||||||
|
"Build-in": "Build-in",
|
||||||
"Dark": "ダーク",
|
"Dark": "ダーク",
|
||||||
|
"Disabled": "Disabled",
|
||||||
"Fiat": "フィアット",
|
"Fiat": "フィアット",
|
||||||
"Light": "ライト",
|
"Light": "ライト",
|
||||||
"Mainnet": "メインネット",
|
"Mainnet": "メインネット",
|
||||||
|
@ -464,8 +464,8 @@
|
|||||||
"The order has expired": "Zamówienie wygasło",
|
"The order has expired": "Zamówienie wygasło",
|
||||||
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
||||||
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Nie możesz jeszcze przyjąć zamówienia! Czekać {{timeMin}}m {{timeSec}}s",
|
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Nie możesz jeszcze przyjąć zamówienia! Czekać {{timeMin}}m {{timeSec}}s",
|
||||||
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
|
||||||
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
|
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
|
||||||
|
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
||||||
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
||||||
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
|
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
|
||||||
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premia: {{premium}}%",
|
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premia: {{premium}}%",
|
||||||
@ -489,7 +489,9 @@
|
|||||||
"Your last order #{{orderID}}": "Your last order #{{orderID}}",
|
"Your last order #{{orderID}}": "Your last order #{{orderID}}",
|
||||||
"finished order": "finished order",
|
"finished order": "finished order",
|
||||||
"#43": "Phrases in components/SettingsForm/index.tsx",
|
"#43": "Phrases in components/SettingsForm/index.tsx",
|
||||||
|
"Build-in": "Build-in",
|
||||||
"Dark": "Dark",
|
"Dark": "Dark",
|
||||||
|
"Disabled": "Disabled",
|
||||||
"Fiat": "Fiat",
|
"Fiat": "Fiat",
|
||||||
"Light": "Light",
|
"Light": "Light",
|
||||||
"Mainnet": "Mainnet",
|
"Mainnet": "Mainnet",
|
||||||
|
@ -464,8 +464,8 @@
|
|||||||
"The order has expired": "A ordem expirou",
|
"The order has expired": "A ordem expirou",
|
||||||
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
||||||
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Você ainda não pode fazer um pedido! Espere {{timeMin}}m {{timeSec}}s",
|
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Você ainda não pode fazer um pedido! Espere {{timeMin}}m {{timeSec}}s",
|
||||||
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
|
||||||
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
|
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
|
||||||
|
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
||||||
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
||||||
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
|
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
|
||||||
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prêmio: {{premium}}%",
|
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prêmio: {{premium}}%",
|
||||||
@ -489,7 +489,9 @@
|
|||||||
"Your last order #{{orderID}}": "Sua última ordem #{{orderID}}",
|
"Your last order #{{orderID}}": "Sua última ordem #{{orderID}}",
|
||||||
"finished order": "ordem finalizada",
|
"finished order": "ordem finalizada",
|
||||||
"#43": "Phrases in components/SettingsForm/index.tsx",
|
"#43": "Phrases in components/SettingsForm/index.tsx",
|
||||||
|
"Build-in": "Build-in",
|
||||||
"Dark": "Dark",
|
"Dark": "Dark",
|
||||||
|
"Disabled": "Disabled",
|
||||||
"Fiat": "Fiat",
|
"Fiat": "Fiat",
|
||||||
"Light": "Light",
|
"Light": "Light",
|
||||||
"Mainnet": "Mainnet",
|
"Mainnet": "Mainnet",
|
||||||
|
@ -464,8 +464,8 @@
|
|||||||
"The order has expired": "Срок действия ордера истёк",
|
"The order has expired": "Срок действия ордера истёк",
|
||||||
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "Закрепленное местоположение является приблизительным. Точное местоположение места встречи необходимо сообщить в зашифрованном чате.",
|
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "Закрепленное местоположение является приблизительным. Точное местоположение места встречи необходимо сообщить в зашифрованном чате.",
|
||||||
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Вы ещё не можете взять ордер! Подождите {{timeMin}}м {{timeSec}}с",
|
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Вы ещё не можете взять ордер! Подождите {{timeMin}}м {{timeSec}}с",
|
||||||
"You receive {{amount}} Sats (Approx)": "Вы получаете через Lightning {{amount}} Сатоши (приблизительно)",
|
|
||||||
"You receive via {{method}} {{amount}}": "Вы получаете через {{method}} {{amount}}",
|
"You receive via {{method}} {{amount}}": "Вы получаете через {{method}} {{amount}}",
|
||||||
|
"You receive {{amount}} Sats (Approx)": "Вы получаете через Lightning {{amount}} Сатоши (приблизительно)",
|
||||||
"You send via Lightning {{amount}} Sats (Approx)": "Вы отправляете через Lightning {{amount}} Сатоши (приблизительно)",
|
"You send via Lightning {{amount}} Sats (Approx)": "Вы отправляете через Lightning {{amount}} Сатоши (приблизительно)",
|
||||||
"You send via {{method}} {{amount}}": "Вы отправляете через {{method}} {{amount}}",
|
"You send via {{method}} {{amount}}": "Вы отправляете через {{method}} {{amount}}",
|
||||||
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Наценка: {{premium}}%",
|
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Наценка: {{premium}}%",
|
||||||
@ -489,7 +489,9 @@
|
|||||||
"Your last order #{{orderID}}": "Ваш последний ордер #{{orderID}}",
|
"Your last order #{{orderID}}": "Ваш последний ордер #{{orderID}}",
|
||||||
"finished order": "finished order",
|
"finished order": "finished order",
|
||||||
"#43": "Phrases in components/SettingsForm/index.tsx",
|
"#43": "Phrases in components/SettingsForm/index.tsx",
|
||||||
|
"Build-in": "Build-in",
|
||||||
"Dark": "Темный",
|
"Dark": "Темный",
|
||||||
|
"Disabled": "Disabled",
|
||||||
"Fiat": "Фиат",
|
"Fiat": "Фиат",
|
||||||
"Light": "Светлый",
|
"Light": "Светлый",
|
||||||
"Mainnet": "Основная сеть",
|
"Mainnet": "Основная сеть",
|
||||||
|
@ -464,8 +464,8 @@
|
|||||||
"The order has expired": "Ordern har förfallit",
|
"The order has expired": "Ordern har förfallit",
|
||||||
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
||||||
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Du kan inte ta en order ännu! Vänta {{timeMin}}m {{timeSec}}s",
|
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Du kan inte ta en order ännu! Vänta {{timeMin}}m {{timeSec}}s",
|
||||||
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
|
||||||
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
|
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
|
||||||
|
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
||||||
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
||||||
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
|
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
|
||||||
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%",
|
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%",
|
||||||
@ -489,7 +489,9 @@
|
|||||||
"Your last order #{{orderID}}": "Din senaste order #{{orderID}}",
|
"Your last order #{{orderID}}": "Din senaste order #{{orderID}}",
|
||||||
"finished order": "finished order",
|
"finished order": "finished order",
|
||||||
"#43": "Phrases in components/SettingsForm/index.tsx",
|
"#43": "Phrases in components/SettingsForm/index.tsx",
|
||||||
|
"Build-in": "Build-in",
|
||||||
"Dark": "Dark",
|
"Dark": "Dark",
|
||||||
|
"Disabled": "Disabled",
|
||||||
"Fiat": "Fiat",
|
"Fiat": "Fiat",
|
||||||
"Light": "Light",
|
"Light": "Light",
|
||||||
"Mainnet": "Mainnet",
|
"Mainnet": "Mainnet",
|
||||||
|
@ -464,8 +464,8 @@
|
|||||||
"The order has expired": "Agizo limekwisha muda",
|
"The order has expired": "Agizo limekwisha muda",
|
||||||
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
||||||
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Hauwezi kuchukua agizo bado! Subiri {{timeMin}}m {{timeSec}}s",
|
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Hauwezi kuchukua agizo bado! Subiri {{timeMin}}m {{timeSec}}s",
|
||||||
"You receive {{amount}} Sats (Approx)": "Utapokea kupitia Lightning {{amount}} Sats (Takriban)",
|
|
||||||
"You receive via {{method}} {{amount}}": "Utapokea kupitia {{method}} {{amount}}",
|
"You receive via {{method}} {{amount}}": "Utapokea kupitia {{method}} {{amount}}",
|
||||||
|
"You receive {{amount}} Sats (Approx)": "Utapokea kupitia Lightning {{amount}} Sats (Takriban)",
|
||||||
"You send via Lightning {{amount}} Sats (Approx)": "Utatuma kupitia Lightning {{amount}} Sats (Takriban)",
|
"You send via Lightning {{amount}} Sats (Approx)": "Utatuma kupitia Lightning {{amount}} Sats (Takriban)",
|
||||||
"You send via {{method}} {{amount}}": "Utatuma kupitia {{method}} {{amount}}",
|
"You send via {{method}} {{amount}}": "Utatuma kupitia {{method}} {{amount}}",
|
||||||
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%",
|
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%",
|
||||||
@ -489,7 +489,9 @@
|
|||||||
"Your last order #{{orderID}}": "Amri yako ya mwisho #{{orderID}}",
|
"Your last order #{{orderID}}": "Amri yako ya mwisho #{{orderID}}",
|
||||||
"finished order": "finished order",
|
"finished order": "finished order",
|
||||||
"#43": "Phrases in components/SettingsForm/index.tsx",
|
"#43": "Phrases in components/SettingsForm/index.tsx",
|
||||||
|
"Build-in": "Build-in",
|
||||||
"Dark": "Giza",
|
"Dark": "Giza",
|
||||||
|
"Disabled": "Disabled",
|
||||||
"Fiat": "Fiat",
|
"Fiat": "Fiat",
|
||||||
"Light": "Nuru",
|
"Light": "Nuru",
|
||||||
"Mainnet": "Mainnet",
|
"Mainnet": "Mainnet",
|
||||||
|
@ -464,8 +464,8 @@
|
|||||||
"The order has expired": "รายการหมดอายุแล้ว",
|
"The order has expired": "รายการหมดอายุแล้ว",
|
||||||
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
||||||
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "คุณยังไม่สามารถดำเนินรายการได้! รออีก {{timeMin}} นาที {{timeSec}} วินาที",
|
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "คุณยังไม่สามารถดำเนินรายการได้! รออีก {{timeMin}} นาที {{timeSec}} วินาที",
|
||||||
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
|
||||||
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
|
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
|
||||||
|
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
||||||
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
||||||
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
|
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
|
||||||
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - ค่าพรีเมี่ยม: {{premium}}%",
|
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - ค่าพรีเมี่ยม: {{premium}}%",
|
||||||
@ -489,7 +489,9 @@
|
|||||||
"Your last order #{{orderID}}": "รายการล่าสุดของคุณ #{{orderID}}",
|
"Your last order #{{orderID}}": "รายการล่าสุดของคุณ #{{orderID}}",
|
||||||
"finished order": "finished order",
|
"finished order": "finished order",
|
||||||
"#43": "Phrases in components/SettingsForm/index.tsx",
|
"#43": "Phrases in components/SettingsForm/index.tsx",
|
||||||
|
"Build-in": "Build-in",
|
||||||
"Dark": "Dark",
|
"Dark": "Dark",
|
||||||
|
"Disabled": "Disabled",
|
||||||
"Fiat": "Fiat",
|
"Fiat": "Fiat",
|
||||||
"Light": "Light",
|
"Light": "Light",
|
||||||
"Mainnet": "Mainnet",
|
"Mainnet": "Mainnet",
|
||||||
|
@ -464,8 +464,8 @@
|
|||||||
"The order has expired": "订单已到期",
|
"The order has expired": "订单已到期",
|
||||||
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
||||||
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "你暂时还不能吃单!请等{{timeMin}}分 {{timeSec}}秒",
|
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "你暂时还不能吃单!请等{{timeMin}}分 {{timeSec}}秒",
|
||||||
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
|
||||||
"You receive via {{method}} {{amount}}": "你通过{{method}}接收{{amount}}",
|
"You receive via {{method}} {{amount}}": "你通过{{method}}接收{{amount}}",
|
||||||
|
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
||||||
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
||||||
"You send via {{method}} {{amount}}": "你通过{{method}}发送{{amount}}",
|
"You send via {{method}} {{amount}}": "你通过{{method}}发送{{amount}}",
|
||||||
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - 溢价: {{premium}}%",
|
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - 溢价: {{premium}}%",
|
||||||
@ -489,7 +489,9 @@
|
|||||||
"Your last order #{{orderID}}": "你的上一笔交易 #{{orderID}}",
|
"Your last order #{{orderID}}": "你的上一笔交易 #{{orderID}}",
|
||||||
"finished order": "finished order",
|
"finished order": "finished order",
|
||||||
"#43": "Phrases in components/SettingsForm/index.tsx",
|
"#43": "Phrases in components/SettingsForm/index.tsx",
|
||||||
|
"Build-in": "Build-in",
|
||||||
"Dark": "深色",
|
"Dark": "深色",
|
||||||
|
"Disabled": "Disabled",
|
||||||
"Fiat": "法币",
|
"Fiat": "法币",
|
||||||
"Light": "浅色",
|
"Light": "浅色",
|
||||||
"Mainnet": "主网",
|
"Mainnet": "主网",
|
||||||
|
@ -464,8 +464,8 @@
|
|||||||
"The order has expired": "訂單已到期",
|
"The order has expired": "訂單已到期",
|
||||||
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
|
||||||
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "你暫時還不能吃單!請等{{timeMin}}分 {{timeSec}}秒",
|
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "你暫時還不能吃單!請等{{timeMin}}分 {{timeSec}}秒",
|
||||||
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
|
||||||
"You receive via {{method}} {{amount}}": "你通過{{method}}接收{{amount}}",
|
"You receive via {{method}} {{amount}}": "你通過{{method}}接收{{amount}}",
|
||||||
|
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
|
||||||
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
|
||||||
"You send via {{method}} {{amount}}": "你通過{{method}}發送{{amount}}",
|
"You send via {{method}} {{amount}}": "你通過{{method}}發送{{amount}}",
|
||||||
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - 溢價: {{premium}}%",
|
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - 溢價: {{premium}}%",
|
||||||
@ -489,7 +489,9 @@
|
|||||||
"Your last order #{{orderID}}": "你的上一筆交易 #{{orderID}}",
|
"Your last order #{{orderID}}": "你的上一筆交易 #{{orderID}}",
|
||||||
"finished order": "finished order",
|
"finished order": "finished order",
|
||||||
"#43": "Phrases in components/SettingsForm/index.tsx",
|
"#43": "Phrases in components/SettingsForm/index.tsx",
|
||||||
|
"Build-in": "Build-in",
|
||||||
"Dark": "深色",
|
"Dark": "深色",
|
||||||
|
"Disabled": "Disabled",
|
||||||
"Fiat": "法幣",
|
"Fiat": "法幣",
|
||||||
"Light": "淺色",
|
"Light": "淺色",
|
||||||
"Mainnet": "主網",
|
"Mainnet": "主網",
|
||||||
|
@ -71,6 +71,7 @@ const App = () => {
|
|||||||
loadCookie('settings_mode');
|
loadCookie('settings_mode');
|
||||||
loadCookie('settings_light_qr');
|
loadCookie('settings_light_qr');
|
||||||
loadCookie('settings_network');
|
loadCookie('settings_network');
|
||||||
|
loadCookie('settings_use_proxy');
|
||||||
loadCookie('garage_slots').then(() => injectMessageResolve(responseId));
|
loadCookie('garage_slots').then(() => injectMessageResolve(responseId));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -151,8 +151,8 @@ android {
|
|||||||
applicationId "com.robosats"
|
applicationId "com.robosats"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 1
|
versionCode 2
|
||||||
versionName "0.6.0-alpha"
|
versionName "0.6.2-alpha"
|
||||||
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
||||||
|
|
||||||
if (isNewArchitectureEnabled()) {
|
if (isNewArchitectureEnabled()) {
|
||||||
|
12
mobile/fastlane/metadata/en-US/full_description.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<p>RoboSats is a simple and private app to exchange bitcoin for national currencies. Robosats simplifies the P2P user experience and uses lightning hold invoices to minimize custody and trust requirements. The deterministically generated robot avatars help users stick to best privacy practices.</p>
|
||||||
|
<p><br><b>Features:</b></p><ul>
|
||||||
|
<li>Privacy focused: your robot avatar is deterministically generated, no need for registration.</li>
|
||||||
|
<li>More than 10 languages available and over 60 fiat currencies</li>
|
||||||
|
<li>Safe: simply lock a lightning hodl invoice and show you are real and committed.</li>
|
||||||
|
<li>No data collection. Your communication with your peer is PGP encrypted, only you can read it.</li>
|
||||||
|
<li>Lightning fast: the average sovereign trade finishes in ~ 8 minutes. Faster than a single block confirmation!</li>
|
||||||
|
<li>Fully collateralized escrow: your peer is always committed and cannot run away with the funds.</li>
|
||||||
|
<li>Strong incentives system: attempts of cheating are penalized with the slashing of the Sats in the fidelity bond.</li>
|
||||||
|
<li>Guides and video tutorials available at https://learn.robosats.com/watch/en</li>
|
||||||
|
</ul>
|
||||||
|
<p>You can join other cool Robots and get community support at <a href="https://t.me/robosats">our Telegram group</a>.</p>
|
BIN
mobile/fastlane/metadata/en-US/images/icon.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
mobile/fastlane/metadata/en-US/images/phoneScreenshots/01.jpg
Normal file
After Width: | Height: | Size: 89 KiB |
BIN
mobile/fastlane/metadata/en-US/images/phoneScreenshots/02.jpg
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
mobile/fastlane/metadata/en-US/images/phoneScreenshots/03.jpg
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
mobile/fastlane/metadata/en-US/images/phoneScreenshots/04.jpg
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
mobile/fastlane/metadata/en-US/images/phoneScreenshots/05.jpg
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
mobile/fastlane/metadata/en-US/images/phoneScreenshots/06.jpg
Normal file
After Width: | Height: | Size: 56 KiB |
1
mobile/fastlane/metadata/en-US/short_description.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Simple and private app to exchange bitcoin for national currencies.
|
12
mobile/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "robosats",
|
"name": "robosats",
|
||||||
"version": "0.6.0",
|
"version": "0.6.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "robosats",
|
"name": "robosats",
|
||||||
"version": "0.6.0",
|
"version": "0.6.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-native-clipboard/clipboard": "^1.13.2",
|
"@react-native-clipboard/clipboard": "^1.13.2",
|
||||||
"@react-native-community/netinfo": "^11.3.0",
|
"@react-native-community/netinfo": "^11.3.0",
|
||||||
@ -37,7 +37,7 @@
|
|||||||
"eslint-plugin-react-hooks": "^4.6.2",
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"metro-react-native-babel-preset": "^0.75.1",
|
"metro-react-native-babel-preset": "^0.75.1",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.3.2",
|
||||||
"react-test-renderer": "18.2.0",
|
"react-test-renderer": "18.2.0",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.4.5"
|
||||||
}
|
}
|
||||||
@ -12714,9 +12714,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier": {
|
"node_modules/prettier": {
|
||||||
"version": "3.2.5",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz",
|
||||||
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
|
"integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "robosats",
|
"name": "robosats",
|
||||||
"version": "0.6.0",
|
"version": "0.6.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"android": "react-native run-android",
|
"android": "react-native run-android",
|
||||||
@ -41,7 +41,7 @@
|
|||||||
"eslint-plugin-react-hooks": "^4.6.2",
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"metro-react-native-babel-preset": "^0.75.1",
|
"metro-react-native-babel-preset": "^0.75.1",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.3.2",
|
||||||
"react-test-renderer": "18.2.0",
|
"react-test-renderer": "18.2.0",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.4.5"
|
||||||
},
|
},
|
||||||
|
@ -1,41 +1,38 @@
|
|||||||
RoboSats v0.6.0 is now out! :rocket:
|
RoboSats v0.6.2 is now out! :rocket:
|
||||||
|
|
||||||
# Changes
|
# Changes
|
||||||
## New Features
|
## What's new
|
||||||
### Decentralization
|
In v0.6.2 we only fix a regression introduced in v0.6.2 that makes the depth chart crash.
|
||||||
RoboSats v0.6.0 introduces a major upgrade, introducing the RoboSats Federation, a decentralized system of independent coordinators to host orders, enhancing the platform's robustness and user experience. This version is a significant step towards decentralization, allowing users to interact with any coordinator seamlessly.
|
|
||||||
|
|
||||||
It's crucial to choose trustworthy coordinators due to the potential risks of malicious activity. The federated client is available for testing at specific URLs, with a stable release planned. Key features include multiple coordinators competing for users, decentralized instances for increased robustness, and a focus on coordinators profiles and trust.
|
|
||||||
|
|
||||||
Learn more in https://learn.robosats.com/robosats/update/pre-release-robosats-decentralized/
|
|
||||||
|
|
||||||
### New avatar generator
|
|
||||||
Your Robot identity is now generated in your client app, when previously, the robot identity was created by the coordinator. This allows now for super-fast Robot avatar and nickname generation that works even if your connection to the coordinators is down. The new robot avatars, are in fact, more diverse and better looking, however, the same token will now yield a different avatar when compared to v0.5.4 (but the robot identity remains the same, also keeping the same nickname).
|
|
||||||
|
|
||||||
## Bug Fixes and Performance Improvements
|
|
||||||
The whole app architecture is new. There might be new bugs, solved bugs, worse performance and better performance: who knows!! :D
|
|
||||||
|
|
||||||
## Special thanks
|
|
||||||
Special thanks to @KoalaSat who has driven some of the largest development pushes needed to get The Federation Layer fully working.
|
|
||||||
|
|
||||||
# Android
|
# Android
|
||||||
|
|
||||||
The Android app is currently not supported on this early phase of the Federated app.
|
**[Click to download universal RoboSats APK for Android](https://github.com/RoboSats/robosats/releases/download/v0.6.2-alpha/robosats-v0.6.2.alpha-universal.apk)**
|
||||||
|
Smaller bundles for each CPU architecture available in the attachments.
|
||||||
|
|
||||||
|
### Verify the app using GPG:
|
||||||
|
|
||||||
|
1. [Download the ascii armored signature](https://github.com/Reckless-Satoshi/robosats/releases/download/v0.6.2-alpha/robosats-v0.6.2.alpha-universal.apk.asc)
|
||||||
|
|
||||||
|
2. Run this command on a directory that contains the apk file and and the ascii armored signature.
|
||||||
|
`gpg --verify robosats-v0.6.2.alpha-universal.apk.asc`
|
||||||
|
|
||||||
|
3. Verify the signer is actually Reckless-Satoshi (fingerprints match): [B4AB5F19113D4125DDF217739C4585B561315571](https://keys.openpgp.org/vks/v1/by-fingerprint/B4AB5F19113D4125DDF217739C4585B561315571)
|
||||||
|
|
||||||
|
Alternatively you can also verify with the release with the SHA256 checksum.
|
||||||
|
|
||||||
# Docker Images
|
# Docker Images
|
||||||
|
|
||||||
[Coordinator Backend Image v0.6.0-alpha (Docker Hub)](https://hub.docker.com/r/recksato/robosats/tags?page=1&name=v0.6.0-alpha)
|
[Coordinator Backend Image v0.6.2-alpha (Docker Hub)](https://hub.docker.com/r/recksato/robosats/tags?page=1&name=v0.6.2-alpha)
|
||||||
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull recksato/robosats:v0.6.0-alpha
|
docker pull recksato/robosats:v0.6.2-alpha
|
||||||
```
|
```
|
||||||
|
|
||||||
[Client App Image v0.6.0-alpha (Docker Hub)](https://hub.docker.com/r/recksato/robosats-client/tags?page=1&name=v0.6.0-alpha)
|
[Client App Image v0.6.2-alpha (Docker Hub)](https://hub.docker.com/r/recksato/robosats-client/tags?page=1&name=v0.6.2-alpha)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull recksato/robosats-client:v0.6.0-alpha
|
docker pull recksato/robosats-client:v0.6.2-alpha
|
||||||
```
|
```
|
||||||
|
|
||||||
See [nodeapp/docker-compose.yml](https://github.com/Reckless-Satoshi/robosats/blob/2cd9d748706a8dcc0f03006b483acc6000e0572a/nodeapp/docker-compose.yml) for an example docker-compose usage of the `robosats-client` image.
|
See [nodeapp/docker-compose.yml](https://github.com/Reckless-Satoshi/robosats/blob/2cd9d748706a8dcc0f03006b483acc6000e0572a/nodeapp/docker-compose.yml) for an example docker-compose usage of the `robosats-client` image.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
django==5.0.4
|
django==5.0.6
|
||||||
django-admin-relation-links==0.2.5
|
django-admin-relation-links==0.2.5
|
||||||
django-celery-beat==2.6.0
|
django-celery-beat==2.6.0
|
||||||
django-celery-results==2.5.1
|
django-celery-results==2.5.1
|
||||||
@ -16,11 +16,10 @@ Pillow==10.1.0
|
|||||||
python-decouple==3.8
|
python-decouple==3.8
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
ring==0.10.1
|
ring==0.10.1
|
||||||
git+https://github.com/RoboSats/Robohash.git@master
|
|
||||||
gunicorn==22.0.0
|
gunicorn==22.0.0
|
||||||
psycopg2==2.9.9
|
psycopg2==2.9.9
|
||||||
SQLAlchemy==2.0.16
|
SQLAlchemy==2.0.16
|
||||||
django-import-export==3.3.8
|
django-import-export==4.0.7
|
||||||
requests[socks]
|
requests[socks]
|
||||||
shapely==2.0.4
|
shapely==2.0.4
|
||||||
python-gnupg==0.5.2
|
python-gnupg==0.5.2
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from channels.db import database_sync_to_async
|
from channels.db import database_sync_to_async
|
||||||
from channels.middleware import BaseMiddleware
|
from channels.middleware import BaseMiddleware
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.auth.models import AnonymousUser, User, update_last_login
|
from django.contrib.auth.models import AnonymousUser, User, update_last_login
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.deprecation import MiddlewareMixin
|
from django.utils.deprecation import MiddlewareMixin
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
from robohash import Robohash
|
|
||||||
|
|
||||||
from api.nick_generator.nick_generator import NickGenerator
|
from api.nick_generator.nick_generator import NickGenerator
|
||||||
from api.utils import base91_to_hex, hex_to_base91, is_valid_token, validate_pgp_keys
|
from api.utils import base91_to_hex, hex_to_base91, is_valid_token, validate_pgp_keys
|
||||||
@ -19,9 +16,6 @@ NickGen = NickGenerator(
|
|||||||
lang="English", use_adv=False, use_adj=True, use_noun=True, max_num=999
|
lang="English", use_adv=False, use_adj=True, use_noun=True, max_num=999
|
||||||
)
|
)
|
||||||
|
|
||||||
avatar_path = Path(settings.AVATAR_ROOT)
|
|
||||||
avatar_path.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
|
|
||||||
class DisableCSRFMiddleware(object):
|
class DisableCSRFMiddleware(object):
|
||||||
def __init__(self, get_response):
|
def __init__(self, get_response):
|
||||||
@ -164,21 +158,6 @@ class RobotTokenSHA256AuthenticationMiddleWare:
|
|||||||
if not user.robot.encrypted_private_key:
|
if not user.robot.encrypted_private_key:
|
||||||
user.robot.encrypted_private_key = encrypted_private_key
|
user.robot.encrypted_private_key = encrypted_private_key
|
||||||
|
|
||||||
# Generate avatar. Does not replace if existing.
|
|
||||||
image_path = avatar_path.joinpath(nickname + ".webp")
|
|
||||||
if not image_path.exists():
|
|
||||||
rh = Robohash(hash)
|
|
||||||
rh.assemble(roboset="set1", bgset="any") # for backgrounds ON
|
|
||||||
with open(image_path, "wb") as f:
|
|
||||||
rh.img.save(f, format="WEBP", quality=80)
|
|
||||||
|
|
||||||
image_small_path = avatar_path.joinpath(nickname + ".small.webp")
|
|
||||||
with open(image_small_path, "wb") as f:
|
|
||||||
resized_img = rh.img.resize((80, 80))
|
|
||||||
resized_img.save(f, format="WEBP", quality=80)
|
|
||||||
|
|
||||||
user.robot.avatar = "static/assets/avatars/" + nickname + ".webp"
|
|
||||||
|
|
||||||
update_last_login(None, user)
|
update_last_login(None, user)
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
|
@ -23,8 +23,6 @@ from .celery.conf import * # noqa
|
|||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
STATIC_URL = "/static/"
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
|
||||||
|
|
||||||
@ -32,8 +30,10 @@ STATIC_URL = "/static/"
|
|||||||
SECRET_KEY = config("SECRET_KEY")
|
SECRET_KEY = config("SECRET_KEY")
|
||||||
|
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/4.0/howto/static-files/
|
||||||
STATIC_URL = "static/"
|
STATIC_URL = "static/"
|
||||||
STATIC_ROOT = "/usr/src/static/"
|
|
||||||
|
|
||||||
# RoboSats version
|
# RoboSats version
|
||||||
with open("version.json") as f:
|
with open("version.json") as f:
|
||||||
@ -42,9 +42,6 @@ with open("version.json") as f:
|
|||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
if config("DEVELOPMENT", default=False):
|
if config("DEVELOPMENT", default=False):
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
STATIC_ROOT = "frontend/static/"
|
|
||||||
|
|
||||||
AVATAR_ROOT = STATIC_ROOT + "assets/avatars/"
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = [
|
ALLOWED_HOSTS = [
|
||||||
config("HOST_NAME"),
|
config("HOST_NAME"),
|
||||||
@ -228,10 +225,6 @@ USE_I18N = True
|
|||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
|
||||||
# https://docs.djangoproject.com/en/4.0/howto/static-files/
|
|
||||||
|
|
||||||
STATIC_URL = "static/"
|
|
||||||
ASGI_APPLICATION = "robosats.routing.application"
|
ASGI_APPLICATION = "robosats.routing.application"
|
||||||
|
|
||||||
CHANNEL_LAYERS = {
|
CHANNEL_LAYERS = {
|
||||||
|
@ -10,9 +10,9 @@ curl --parallel -o lightning.proto https://raw.githubusercontent.com/lightningne
|
|||||||
-o router.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/routerrpc/router.proto \
|
-o router.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/routerrpc/router.proto \
|
||||||
-o signer.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/signrpc/signer.proto \
|
-o signer.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/signrpc/signer.proto \
|
||||||
-o verrpc.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/verrpc/verrpc.proto \
|
-o verrpc.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/verrpc/verrpc.proto \
|
||||||
-o hold.proto https://raw.githubusercontent.com/daywalker90/lightning/cln-grpc-hold/proto/hold.proto \
|
-o hold.proto https://raw.githubusercontent.com/daywalker90/holdinvoice/master/proto/hold.proto \
|
||||||
-o primitives.proto https://raw.githubusercontent.com/ElementsProject/lightning/v23.08/cln-grpc/proto/primitives.proto \
|
-o primitives.proto https://raw.githubusercontent.com/ElementsProject/lightning/v24.05/cln-grpc/proto/primitives.proto \
|
||||||
-o node.proto https://raw.githubusercontent.com/ElementsProject/lightning/v23.08/cln-grpc/proto/node.proto
|
-o node.proto https://raw.githubusercontent.com/ElementsProject/lightning/v24.05/cln-grpc/proto/node.proto
|
||||||
|
|
||||||
echo -n "Done\nBuilding api from GRPC specs..."
|
echo -n "Done\nBuilding api from GRPC specs..."
|
||||||
python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. lightning.proto invoices.proto router.proto signer.proto verrpc.proto
|
python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. lightning.proto invoices.proto router.proto signer.proto verrpc.proto
|
||||||
|
7
tests/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Run e2e tests
|
||||||
|
|
||||||
|
```
|
||||||
|
docker compose -f docker-tests.yml --env-file tests/compose.env up -d
|
||||||
|
docker exec coordinator coverage run manage.py test
|
||||||
|
docker exec coordinator coverage report
|
||||||
|
```
|
2013
tests/api_specs.yaml
17
tests/bitcoind/entrypoint.sh
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Start bitcoind in the background
|
||||||
|
bitcoind "$@" &
|
||||||
|
|
||||||
|
# Wait for bitcoind to be ready
|
||||||
|
while ! bitcoin-cli --regtest --rpcuser=test --rpcpassword=test getblockchaininfo 2>/dev/null | grep '"verificationprogress":'; do
|
||||||
|
echo "Waiting for bitcoind to be ready..."
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Run initialization commands
|
||||||
|
bitcoin-cli --regtest --rpcuser=test --rpcpassword=test createwallet default
|
||||||
|
bitcoin-cli --regtest --rpcuser=test --rpcpassword=test generatetoaddress 1 $(bitcoin-cli --regtest --rpcuser=test --rpcpassword=test getnewaddress)
|
||||||
|
|
||||||
|
# Bring bitcoind to the foreground
|
||||||
|
wait
|
@ -2,6 +2,6 @@ ROBOSATS_ENVS_FILE=".env-sample"
|
|||||||
|
|
||||||
BITCOIND_VERSION='24.0.1'
|
BITCOIND_VERSION='24.0.1'
|
||||||
LND_VERSION='v0.17.0-beta'
|
LND_VERSION='v0.17.0-beta'
|
||||||
CLN_VERSION='v23.08.1'
|
CLN_VERSION='v24.05'
|
||||||
REDIS_VERSION='7.2.1'
|
REDIS_VERSION='7.2.1'
|
||||||
POSTGRES_VERSION='14.2'
|
POSTGRES_VERSION='14.2'
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"major": 0,
|
"major": 0,
|
||||||
"minor": 6,
|
"minor": 6,
|
||||||
"patch": 0
|
"patch": 2
|
||||||
}
|
}
|
||||||
|