mirror of
https://github.com/RoboSats/robosats.git
synced 2025-09-13 00:56:22 +00:00
Add WebLN support (#215)
* Add WebLN support * Fix Variable Typo * Invoice Generation Signed-off-by: KoalaSat <111684255+KoalaSat@users.noreply.github.com> * Code Review * Second CR * Catch cancelations * Final Review Signed-off-by: KoalaSat <111684255+KoalaSat@users.noreply.github.com>
This commit is contained in:
29
frontend/package-lock.json
generated
29
frontend/package-lock.json
generated
@ -2930,6 +2930,14 @@
|
|||||||
"@babel/types": "^7.3.0"
|
"@babel/types": "^7.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/chrome": {
|
||||||
|
"version": "0.0.74",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.74.tgz",
|
||||||
|
"integrity": "sha512-hzosS5CkQcIKCgxcsV2AzbJ36KNxG/Db2YEN/erEu7Boprg+KpMDLBQqKFmSo+JkQMGqRcicUyqCowJpuT+C6A==",
|
||||||
|
"requires": {
|
||||||
|
"@types/filesystem": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/eslint": {
|
"@types/eslint": {
|
||||||
"version": "8.4.1",
|
"version": "8.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz",
|
||||||
@ -2956,6 +2964,19 @@
|
|||||||
"integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==",
|
"integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/filesystem": {
|
||||||
|
"version": "0.0.32",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.32.tgz",
|
||||||
|
"integrity": "sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ==",
|
||||||
|
"requires": {
|
||||||
|
"@types/filewriter": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/filewriter": {
|
||||||
|
"version": "0.0.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.29.tgz",
|
||||||
|
"integrity": "sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ=="
|
||||||
|
},
|
||||||
"@types/graceful-fs": {
|
"@types/graceful-fs": {
|
||||||
"version": "4.1.5",
|
"version": "4.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
|
||||||
@ -9338,6 +9359,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
|
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
|
||||||
},
|
},
|
||||||
|
"webln": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/webln/-/webln-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-QMLGIQtHzSVwYldhREjJsVGfVZ37q+hkBpi9KiruxI8FJwD0UocshrP9sbtd1H5N96uAAq53aywesy3/sw+YOA==",
|
||||||
|
"requires": {
|
||||||
|
"@types/chrome": "^0.0.74"
|
||||||
|
}
|
||||||
|
},
|
||||||
"webpack": {
|
"webpack": {
|
||||||
"version": "5.72.0",
|
"version": "5.72.0",
|
||||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.72.0.tgz",
|
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.72.0.tgz",
|
||||||
|
|||||||
@ -65,6 +65,7 @@
|
|||||||
"react-world-flags": "^1.4.0",
|
"react-world-flags": "^1.4.0",
|
||||||
"reconnecting-websocket": "^4.4.0",
|
"reconnecting-websocket": "^4.4.0",
|
||||||
"simple-plist": "^1.3.1",
|
"simple-plist": "^1.3.1",
|
||||||
|
"webln": "^0.3.0",
|
||||||
"websocket": "^1.0.34"
|
"websocket": "^1.0.34"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link as LinkRouter } from "react-router-dom";
|
import { Link as LinkRouter } from "react-router-dom";
|
||||||
|
|
||||||
@ -35,6 +35,7 @@ import { UserNinjaIcon, BitcoinIcon } from "../Icons";
|
|||||||
|
|
||||||
import { getCookie } from "../../utils/cookies";
|
import { getCookie } from "../../utils/cookies";
|
||||||
import { copyToClipboard } from "../../utils/clipboard";
|
import { copyToClipboard } from "../../utils/clipboard";
|
||||||
|
import { getWebln } from "../../utils/webln";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -76,6 +77,14 @@ const ProfileDialog = ({
|
|||||||
const [rewardInvoice, setRewardInvoice] = useState<string>("");
|
const [rewardInvoice, setRewardInvoice] = useState<string>("");
|
||||||
const [showRewards, setShowRewards] = useState<boolean>(false);
|
const [showRewards, setShowRewards] = useState<boolean>(false);
|
||||||
const [openClaimRewards, setOpenClaimRewards] = useState<boolean>(false);
|
const [openClaimRewards, setOpenClaimRewards] = useState<boolean>(false);
|
||||||
|
const [weblnEnabled, setWeblnEnabled] = useState<boolean>(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getWebln()
|
||||||
|
.then((webln) => {
|
||||||
|
setWeblnEnabled(webln !== undefined)
|
||||||
|
})
|
||||||
|
}, [showRewards])
|
||||||
|
|
||||||
const copyTokenHandler = () => {
|
const copyTokenHandler = () => {
|
||||||
const robotToken = getCookie("robot_token");
|
const robotToken = getCookie("robot_token");
|
||||||
@ -90,6 +99,18 @@ const ProfileDialog = ({
|
|||||||
copyToClipboard(`http://${host}/ref/${referralCode}`);
|
copyToClipboard(`http://${host}/ref/${referralCode}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleWeblnInvoiceClicked = async (e: any) =>{
|
||||||
|
e.preventDefault();
|
||||||
|
if (earnedRewards) {
|
||||||
|
const webln = await getWebln();
|
||||||
|
const invoice = webln.makeInvoice(earnedRewards).then(() => {
|
||||||
|
if (invoice) {
|
||||||
|
handleSubmitInvoiceClicked(e, invoice.paymentRequest)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
@ -324,7 +345,6 @@ const ProfileDialog = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid item alignItems="stretch" style={{ display: "flex", maxWidth:80}}>
|
<Grid item alignItems="stretch" style={{ display: "flex", maxWidth:80}}>
|
||||||
<Button
|
<Button
|
||||||
sx={{maxHeight:38}}
|
sx={{maxHeight:38}}
|
||||||
@ -338,6 +358,22 @@ const ProfileDialog = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
{weblnEnabled && (
|
||||||
|
<Grid container style={{ display: "flex", alignItems: "stretch"}}>
|
||||||
|
<Grid item alignItems="stretch" style={{ display: "flex", maxWidth:240}}>
|
||||||
|
<Button
|
||||||
|
sx={{maxHeight:38, minWidth: 230}}
|
||||||
|
onClick={(e) => handleWeblnInvoiceClicked(e)}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
size="small"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
{t("Generate with Webln")}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|||||||
@ -19,11 +19,13 @@ import PriceChangeIcon from '@mui/icons-material/PriceChange';
|
|||||||
import PaymentsIcon from '@mui/icons-material/Payments';
|
import PaymentsIcon from '@mui/icons-material/Payments';
|
||||||
import ArticleIcon from '@mui/icons-material/Article';
|
import ArticleIcon from '@mui/icons-material/Article';
|
||||||
import HourglassTopIcon from '@mui/icons-material/HourglassTop';
|
import HourglassTopIcon from '@mui/icons-material/HourglassTop';
|
||||||
|
import CheckIcon from '@mui/icons-material/Check';
|
||||||
import { SendReceiveIcon } from "./Icons";
|
import { SendReceiveIcon } from "./Icons";
|
||||||
|
|
||||||
import { getCookie } from "../utils/cookies";
|
import { getCookie } from "../utils/cookies";
|
||||||
import { pn } from "../utils/prettyNumbers";
|
import { pn } from "../utils/prettyNumbers";
|
||||||
import { copyToClipboard } from "../utils/clipboard";
|
import { copyToClipboard } from "../utils/clipboard";
|
||||||
|
import { getWebln } from "../utils/webln";
|
||||||
|
|
||||||
class OrderPage extends Component {
|
class OrderPage extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -36,6 +38,8 @@ class OrderPage extends Component {
|
|||||||
openCancel: false,
|
openCancel: false,
|
||||||
openCollaborativeCancel: false,
|
openCollaborativeCancel: false,
|
||||||
openInactiveMaker: false,
|
openInactiveMaker: false,
|
||||||
|
openWeblnDialog: false,
|
||||||
|
waitingWebln: false,
|
||||||
openStoreToken: false,
|
openStoreToken: false,
|
||||||
tabValue: 1,
|
tabValue: 1,
|
||||||
orderId: this.props.match.params.orderId,
|
orderId: this.props.match.params.orderId,
|
||||||
@ -92,7 +96,13 @@ class OrderPage extends Component {
|
|||||||
this.setState({orderId:id})
|
this.setState({orderId:id})
|
||||||
fetch('/api/order' + '?order_id=' + id)
|
fetch('/api/order' + '?order_id=' + id)
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => (this.completeSetState(data) & this.setState({pauseLoading:false})));
|
.then(this.orderDetailsReceived);
|
||||||
|
}
|
||||||
|
|
||||||
|
orderDetailsReceived = (data) =>{
|
||||||
|
if (data.status !== this.state.status) { this.handleWebln(data) }
|
||||||
|
this.completeSetState(data)
|
||||||
|
this.setState({pauseLoading:false})
|
||||||
}
|
}
|
||||||
|
|
||||||
// These are used to refresh the data
|
// These are used to refresh the data
|
||||||
@ -103,7 +113,7 @@ class OrderPage extends Component {
|
|||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
clearInterval(this.interval);
|
clearInterval(this.interval);
|
||||||
this.interval = setInterval(this.tick, this.state.delay);
|
this.interval = setInterval(this.tick, this.state.delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
@ -113,6 +123,48 @@ class OrderPage extends Component {
|
|||||||
this.getOrderDetails(this.state.orderId);
|
this.getOrderDetails(this.state.orderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleWebln = async (data) => {
|
||||||
|
const webln = await getWebln();
|
||||||
|
// If Webln implements locked payments compatibility, this logic might be simplier
|
||||||
|
if (data.is_maker & data.status == 0) {
|
||||||
|
webln.sendPayment(data.bond_invoice);
|
||||||
|
this.setState({ waitingWebln: true, openWeblnDialog: true});
|
||||||
|
} else if (data.is_taker & data.status == 3) {
|
||||||
|
webln.sendPayment(data.bond_invoice);
|
||||||
|
this.setState({ waitingWebln: true, openWeblnDialog: true});
|
||||||
|
} else if (data.is_seller & (data.status == 6 || data.status == 7 )) {
|
||||||
|
webln.sendPayment(data.escrow_invoice);
|
||||||
|
this.setState({ waitingWebln: true, openWeblnDialog: true});
|
||||||
|
} else if (data.is_buyer & (data.status == 6 || data.status == 8 )) {
|
||||||
|
this.setState({ waitingWebln: true, openWeblnDialog: true});
|
||||||
|
webln.makeInvoice(data.trade_satoshis)
|
||||||
|
.then((invoice) => {
|
||||||
|
if (invoice) {
|
||||||
|
this.sendWeblnInvoice(invoice.paymentRequest);
|
||||||
|
this.setState({ waitingWebln: false, openWeblnDialog: false });
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
this.setState({ waitingWebln: false, openWeblnDialog: false });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({ waitingWebln: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendWeblnInvoice = (invoice) => {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
|
||||||
|
body: JSON.stringify({
|
||||||
|
'action':'update_invoice',
|
||||||
|
'invoice': invoice,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
fetch('/api/order/' + '?order_id=' + this.state.orderId, requestOptions)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => this.completeSetState(data));
|
||||||
|
}
|
||||||
|
|
||||||
// Countdown Renderer callback with condition
|
// Countdown Renderer callback with condition
|
||||||
countdownRenderer = ({ total, hours, minutes, seconds, completed }) => {
|
countdownRenderer = ({ total, hours, minutes, seconds, completed }) => {
|
||||||
const { t } = this.props;
|
const { t } = this.props;
|
||||||
@ -263,7 +315,7 @@ class OrderPage extends Component {
|
|||||||
};
|
};
|
||||||
fetch('/api/order/' + '?order_id=' + this.state.orderId, requestOptions)
|
fetch('/api/order/' + '?order_id=' + this.state.orderId, requestOptions)
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => this.completeSetState(data));
|
.then((data) => this.handleWebln(data) & this.completeSetState(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
// set delay to the one matching the order status. If null order status, delay goes to 9999999.
|
// set delay to the one matching the order status. If null order status, delay goes to 9999999.
|
||||||
@ -744,6 +796,7 @@ class OrderPage extends Component {
|
|||||||
:
|
:
|
||||||
(this.state.is_participant ?
|
(this.state.is_participant ?
|
||||||
<>
|
<>
|
||||||
|
{this.weblnDialog()}
|
||||||
{/* Desktop View */}
|
{/* Desktop View */}
|
||||||
<MediaQuery minWidth={920}>
|
<MediaQuery minWidth={920}>
|
||||||
{this.doubleOrderPageDesktop()}
|
{this.doubleOrderPageDesktop()}
|
||||||
@ -761,6 +814,44 @@ class OrderPage extends Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleCloseWeblnDialog = () => {
|
||||||
|
this.setState({openWeblnDialog: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
weblnDialog =() =>{
|
||||||
|
const { t } = this.props;
|
||||||
|
|
||||||
|
return(
|
||||||
|
<Dialog
|
||||||
|
open={this.state.openWeblnDialog}
|
||||||
|
onClose={this.handleCloseWeblnDialog}
|
||||||
|
aria-labelledby="webln-dialog-title"
|
||||||
|
aria-describedby="webln-dialog-description"
|
||||||
|
>
|
||||||
|
<DialogTitle id="webln-dialog-title">
|
||||||
|
{t("WebLN")}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText id="webln-dialog-description">
|
||||||
|
{this.state.waitingWebln ?
|
||||||
|
<>
|
||||||
|
<CircularProgress size={16} thickness={5} style={{ marginRight: 10 }}/>
|
||||||
|
{this.state.is_buyer ? t("Invoice not received, please check your WebLN wallet.") : t("Payment not received, please check your WebLN wallet.")}
|
||||||
|
</>
|
||||||
|
: <>
|
||||||
|
<CheckIcon color="success"/>
|
||||||
|
{t("You can close now your WebLN wallet popup.")}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={this.handleCloseWeblnDialog} autoFocus>{t("Done")}</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
render (){
|
render (){
|
||||||
return (
|
return (
|
||||||
// Only so nothing shows while requesting the first batch of data
|
// Only so nothing shows while requesting the first batch of data
|
||||||
|
|||||||
18
frontend/src/utils/webln.ts
Normal file
18
frontend/src/utils/webln.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { requestProvider, WeblnProvider } from "webln";
|
||||||
|
|
||||||
|
export const getWebln = async (): Promise<WeblnProvider> => {
|
||||||
|
const resultPromise = new Promise<WeblnProvider>(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const webln = await requestProvider()
|
||||||
|
if (webln) {
|
||||||
|
webln.enable()
|
||||||
|
resolve(webln)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log("Coulnd't connect to Webln")
|
||||||
|
reject()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return resultPromise
|
||||||
|
}
|
||||||
@ -302,6 +302,10 @@
|
|||||||
"This order has been cancelled collaborativelly":"This order has been cancelled collaboratively",
|
"This order has been cancelled collaborativelly":"This order has been cancelled collaboratively",
|
||||||
"This order is not available":"This order is not available",
|
"This order is not available":"This order is not available",
|
||||||
"The Robotic Satoshis working in the warehouse did not understand you. Please, fill a Bug Issue in Github https://github.com/reckless-satoshi/robosats/issues":"The Robotic Satoshis working in the warehouse did not understand you. Please, fill a Bug Issue in Github https://github.com/reckless-satoshi/robosats/issues",
|
"The Robotic Satoshis working in the warehouse did not understand you. Please, fill a Bug Issue in Github https://github.com/reckless-satoshi/robosats/issues":"The Robotic Satoshis working in the warehouse did not understand you. Please, fill a Bug Issue in Github https://github.com/reckless-satoshi/robosats/issues",
|
||||||
|
"WebLN": "WebLN",
|
||||||
|
"Payment not received, please check your WebLN wallet.": "Payment not received, please check your WebLN wallet.",
|
||||||
|
"Invoice not received, please check your WebLN wallet.": "Invoice not received, please check your WebLN wallet.",
|
||||||
|
"Payment detected, you can close now your WebLN wallet popup.": "Payment detected, you can close now your WebLN wallet popup.",
|
||||||
|
|
||||||
"CHAT BOX - Chat.js":"Chat Box",
|
"CHAT BOX - Chat.js":"Chat Box",
|
||||||
"You":"You",
|
"You":"You",
|
||||||
|
|||||||
@ -302,7 +302,10 @@
|
|||||||
"This order has been cancelled collaborativelly":"Esta orden se ha cancelado colaborativamente",
|
"This order has been cancelled collaborativelly":"Esta orden se ha cancelado colaborativamente",
|
||||||
"This order is not available": "Esta orden no está disponible",
|
"This order is not available": "Esta orden no está disponible",
|
||||||
"The Robotic Satoshis working in the warehouse did not understand you. Please, fill a Bug Issue in Github https://github.com/reckless-satoshi/robosats/issues": "Los Satoshis Robóticos del almacén no te entendieron. Por favor rellena un Bug Issue en Github https://github.com/reckless-satoshi/robosats/issues",
|
"The Robotic Satoshis working in the warehouse did not understand you. Please, fill a Bug Issue in Github https://github.com/reckless-satoshi/robosats/issues": "Los Satoshis Robóticos del almacén no te entendieron. Por favor rellena un Bug Issue en Github https://github.com/reckless-satoshi/robosats/issues",
|
||||||
|
"WebLN": "WebLN",
|
||||||
|
"Payment not received, please check your WebLN wallet.": "No se ha recibido el pago, echa un vistazo a tu wallet WebLN.",
|
||||||
|
"Invoice not received, please check your WebLN wallet.": "No se ha recibido la factura, echa un vistazo a tu wallet WebLN.",
|
||||||
|
"You can close now your WebLN wallet popup.": "Ahora puedes cerrar el popup de tu wallet WebLN.",
|
||||||
|
|
||||||
"CHAT BOX - Chat.js": "Ventana del chat",
|
"CHAT BOX - Chat.js": "Ventana del chat",
|
||||||
"You": "Tú",
|
"You": "Tú",
|
||||||
|
|||||||
@ -300,6 +300,10 @@
|
|||||||
"This order has been cancelled collaborativelly":"Этот ордер был отменён совместно",
|
"This order has been cancelled collaborativelly":"Этот ордер был отменён совместно",
|
||||||
"You are not allowed to see this order":"Вы не можете увидеть этот ордер",
|
"You are not allowed to see this order":"Вы не можете увидеть этот ордер",
|
||||||
"The Robotic Satoshis working in the warehouse did not understand you. Please, fill a Bug Issue in Github https://github.com/reckless-satoshi/robosats/issues":"Роботизированные Сатоши, работающие на складе, не поняли Вас. Пожалуйста, заполните вопрос об ошибке в Github https://github.com/reckless-satoshi/robosats/issues",
|
"The Robotic Satoshis working in the warehouse did not understand you. Please, fill a Bug Issue in Github https://github.com/reckless-satoshi/robosats/issues":"Роботизированные Сатоши, работающие на складе, не поняли Вас. Пожалуйста, заполните вопрос об ошибке в Github https://github.com/reckless-satoshi/robosats/issues",
|
||||||
|
"WebLN": "WebLN",
|
||||||
|
"Payment not received, please check your WebLN wallet.": "Платёж не получен. Пожалуйста, проверьте Ваш WebLN Кошелёк.",
|
||||||
|
"Invoice not received, please check your WebLN wallet.": "Платёж не получен. Пожалуйста, проверьте Ваш WebLN Кошелёк.",
|
||||||
|
"You can close now your WebLN wallet popup.": "Вы можете закрыть всплывающее окно WebLN Кошелька",
|
||||||
|
|
||||||
"CHAT BOX - Chat.js":"Chat Box",
|
"CHAT BOX - Chat.js":"Chat Box",
|
||||||
"You":"Вы",
|
"You":"Вы",
|
||||||
|
|||||||
Reference in New Issue
Block a user