diff --git a/package.json b/package.json
index 5c0649a965..e7e052f9b3 100644
--- a/package.json
+++ b/package.json
@@ -66,6 +66,7 @@
"lodash.merge": "^4.6.2",
"lodash.pick": "^4.4.0",
"lodash.snakecase": "^4.1.1",
+ "nostr-tools": "^1.17.0",
"pubsub-js": "^1.9.4",
"react": "^18.2.0",
"react-confetti": "^6.1.0",
diff --git a/src/app/router/connectorRoutes.tsx b/src/app/router/connectorRoutes.tsx
index 4cd32fdb20..2ea902deb9 100644
--- a/src/app/router/connectorRoutes.tsx
+++ b/src/app/router/connectorRoutes.tsx
@@ -3,6 +3,7 @@ import ConnectBtcpay from "@screens/connectors/ConnectBtcpay";
import ConnectCitadel from "@screens/connectors/ConnectCitadel";
import ConnectEclair from "@screens/connectors/ConnectEclair";
import ConnectGaloy, { galoyUrls } from "@screens/connectors/ConnectGaloy";
+import ConnectLaWallet from "@screens/connectors/ConnectLaWallet";
import ConnectLnbits from "@screens/connectors/ConnectLnbits";
import ConnectLnc from "@screens/connectors/ConnectLnc";
import ConnectLnd from "@screens/connectors/ConnectLnd";
@@ -23,6 +24,7 @@ import core_ln from "/static/assets/icons/core_ln.svg";
import eclair from "/static/assets/icons/eclair.jpg";
import galoyBitcoinJungle from "/static/assets/icons/galoy_bitcoin_jungle.png";
import galoyBlink from "/static/assets/icons/galoy_blink.png";
+import lawallet from "/static/assets/icons/lawallet.png";
import lightning_node from "/static/assets/icons/lightning_node.png";
import lightning_terminal from "/static/assets/icons/lightning_terminal.png";
import lnbits from "/static/assets/icons/lnbits.png";
@@ -165,6 +167,12 @@ const connectorMap: { [key: string]: ConnectorRoute } = {
title: i18n.t("translation:choose_connector.nwc.title"),
logo: nwc,
},
+ lawallet: {
+ path: "lawallet",
+ element: ,
+ title: i18n.t("translation:choose_connector.lawallet.title"),
+ logo: lawallet,
+ },
};
function getDistribution(key: string): ConnectorRoute {
@@ -252,6 +260,13 @@ function getConnectorRoutes(): ConnectorRoute[] {
connectorMap["voltage"],
connectorMap[galoyPaths.blink],
connectorMap[galoyPaths.bitcoinJungle],
+ getDistribution("citadel"),
+ getDistribution("umbrel"),
+ getDistribution("mynode"),
+ getDistribution("startos"),
+ getDistribution("raspiblitz"),
+ connectorMap["nwc"],
+ connectorMap["lawallet"],
connectorMap["lnd-hub-go"],
connectorMap["eclair"],
];
diff --git a/src/app/screens/connectors/ConnectLaWallet/LaWalletToast.tsx b/src/app/screens/connectors/ConnectLaWallet/LaWalletToast.tsx
new file mode 100644
index 0000000000..2d12977171
--- /dev/null
+++ b/src/app/screens/connectors/ConnectLaWallet/LaWalletToast.tsx
@@ -0,0 +1,44 @@
+import { Trans, useTranslation } from "react-i18next";
+
+export default function LaWalletToast({ domain }: { domain: string }) {
+ const { t } = useTranslation("translation", {
+ keyPrefix: "choose_connector.lawallet.errors.toast",
+ });
+
+ return (
+ <>
+
+ {t("title")} (
+ {t("message", { domain })})
+
+
+ {t("verify")}
+
+ - {t("match")}
+ -
+ ]}
+ />
+
+ -
+ ]}
+ />
+
+ -
+ ]}
+ />
+
+
+ >
+ );
+}
diff --git a/src/app/screens/connectors/ConnectLaWallet/index.tsx b/src/app/screens/connectors/ConnectLaWallet/index.tsx
new file mode 100644
index 0000000000..36ed8f6a35
--- /dev/null
+++ b/src/app/screens/connectors/ConnectLaWallet/index.tsx
@@ -0,0 +1,206 @@
+import ConnectorForm from "@components/ConnectorForm";
+import TextField from "@components/form/TextField";
+import ConnectionErrorToast from "@components/toasts/ConnectionErrorToast";
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { useNavigate } from "react-router-dom";
+import toast from "~/app/components/Toast";
+import msg from "~/common/lib/msg";
+
+import Button from "~/app/components/Button";
+import PasswordViewAdornment from "~/app/components/PasswordViewAdornment";
+import LaWalletToast from "~/app/screens/connectors/ConnectLaWallet/LaWalletToast";
+import LaWallet, {
+ HttpError,
+} from "~/extension/background-script/connectors/lawallet";
+import Nostr from "~/extension/background-script/nostr";
+import { ConnectorType } from "~/types";
+import logo from "/static/assets/icons/lawallet.png";
+
+const initialFormData = {
+ private_key: "",
+ api_endpoint: "https://api.lawallet.ar",
+ identity_endpoint: "https://lawallet.ar",
+ ledger_public_key:
+ "bd9b0b60d5cd2a9df282fc504e88334995e6fac8b148fa89e0f8c09e2a570a84",
+ urlx_public_key:
+ "e17feb5f2cf83546bcf7fd9c8237b05275be958bd521543c2285ffc6c2d654b3",
+ relay_url: "wss://relay.lawallet.ar",
+};
+
+export default function ConnectLaWallet() {
+ const navigate = useNavigate();
+ const { t } = useTranslation("translation", {
+ keyPrefix: "choose_connector.lawallet",
+ });
+ const [passwordViewVisible, setPasswordViewVisible] = useState(false);
+ const { t: tCommon } = useTranslation("common");
+ const [formData, setFormData] = useState(initialFormData);
+ const [loading, setLoading] = useState(false);
+ const [showAdvanced, setShowAdvanced] = useState(false);
+
+ function getConnectorType(): ConnectorType {
+ return "lawallet";
+ }
+
+ function handleChange(event: React.ChangeEvent) {
+ setFormData({
+ ...formData,
+ [event.target.name]: event.target.value.trim(),
+ });
+ }
+
+ async function handleSubmit(event: React.FormEvent) {
+ event.preventDefault();
+ setLoading(true);
+ const {
+ private_key,
+ api_endpoint,
+ identity_endpoint,
+ ledger_public_key,
+ urlx_public_key,
+ relay_url,
+ } = formData;
+
+ const publicKey = new Nostr(private_key).getPublicKey();
+ const domain = identity_endpoint.replace(/https?:\/\//, "");
+
+ let username;
+ try {
+ const response = await LaWallet.request<{ username: string }>(
+ identity_endpoint,
+ "GET",
+ `/api/pubkey/${publicKey}`,
+ undefined
+ );
+ username = response.username;
+ } catch (e) {
+ if (e instanceof HttpError && e.status === 404) {
+ toast.error(, {
+ position: "top-center",
+ });
+ } else {
+ toast.error();
+ }
+
+ setLoading(false);
+ return;
+ }
+
+ const account = {
+ name: `${username}@${domain}`,
+ config: {
+ privateKey: private_key,
+ apiEndpoint: api_endpoint,
+ identityEndpoint: identity_endpoint,
+ ledgerPublicKey: ledger_public_key,
+ urlxPublicKey: urlx_public_key,
+ relayUrl: relay_url,
+ },
+ connector: getConnectorType(),
+ };
+
+ await msg.request("validateAccount", account);
+
+ try {
+ const addResult = await msg.request("addAccount", account);
+ if (addResult.accountId) {
+ await msg.request("selectAccount", {
+ id: addResult.accountId,
+ });
+ navigate("/test-connection");
+ }
+ } catch (e) {
+ console.error(e);
+ let message = "";
+ if (e instanceof Error) {
+ message += `${e.message}`;
+ }
+ toast.error();
+ }
+ setLoading(false);
+ }
+
+ return (
+
+
+ {
+ setPasswordViewVisible(passwordView);
+ }}
+ />
+ }
+ />
+
+
+
+
+ {showAdvanced && (
+
+
+
+
+
+
+
+ )}
+
+ );
+}
diff --git a/src/extension/background-script/connectors/index.ts b/src/extension/background-script/connectors/index.ts
index 95bbaef680..0a682d66b6 100644
--- a/src/extension/background-script/connectors/index.ts
+++ b/src/extension/background-script/connectors/index.ts
@@ -4,6 +4,7 @@ import Commando from "./commando";
import Eclair from "./eclair";
import Galoy from "./galoy";
import Kollider from "./kollider";
+import LaWallet from "./lawallet";
import LnBits from "./lnbits";
import Lnc from "./lnc";
import Lnd from "./lnd";
@@ -38,6 +39,7 @@ const connectors = {
commando: Commando,
alby: Alby,
nwc: NWC,
+ lawallet: LaWallet,
};
export default connectors;
diff --git a/src/extension/background-script/connectors/lawallet.ts b/src/extension/background-script/connectors/lawallet.ts
new file mode 100644
index 0000000000..999059686a
--- /dev/null
+++ b/src/extension/background-script/connectors/lawallet.ts
@@ -0,0 +1,531 @@
+import { schnorr } from "@noble/curves/secp256k1";
+import * as secp256k1 from "@noble/secp256k1";
+import type { ResponseType } from "axios";
+import { Method } from "axios";
+import lightningPayReq from "bolt11-signet";
+import Hex from "crypto-js/enc-hex";
+import sha256 from "crypto-js/sha256";
+import { nip04, relayInit, type Relay } from "nostr-tools";
+import { Event, EventKind } from "~/extension/providers/nostr/types";
+import { Account } from "~/types";
+
+import toast from "~/app/components/Toast";
+import { getEventHash } from "~/extension/background-script/actions/nostr/helpers";
+import Nostr from "~/extension/background-script/nostr";
+import Connector, {
+ CheckPaymentArgs,
+ CheckPaymentResponse,
+ ConnectPeerResponse,
+ ConnectorTransaction,
+ GetBalanceResponse,
+ GetInfoResponse,
+ GetTransactionsResponse,
+ KeysendArgs,
+ MakeInvoiceArgs,
+ MakeInvoiceResponse,
+ SendPaymentArgs,
+ SendPaymentResponse,
+ SignMessageArgs,
+ SignMessageResponse,
+} from "./connector.interface";
+
+interface Config {
+ privateKey: string;
+ apiEndpoint: string;
+ identityEndpoint: string;
+ ledgerPublicKey: string;
+ urlxPublicKey: string;
+ relayList: string[];
+ relayUrl: string;
+}
+
+export class HttpError extends Error {
+ status: number;
+ error: Error | undefined;
+
+ constructor(status: number, message: string, error?: Error) {
+ super(message);
+ this.status = status;
+ this.error = error;
+ }
+}
+
+export default class LaWallet implements Connector {
+ account: Account;
+ config: Config;
+ relay: Relay;
+ public_key: string;
+ access_token?: string;
+ access_token_created?: number;
+ refresh_token?: string;
+ refresh_token_created?: number;
+ noRetry?: boolean;
+
+ invoices_paid: InvoiceCache = {};
+ last_invoice_check: number = 0;
+
+ constructor(account: Account, config: Config) {
+ this.account = account;
+ this.config = config;
+ this.public_key = new Nostr(config.privateKey).getPublicKey();
+ this.relay = relayInit(config.relayUrl);
+ }
+
+ async init() {
+ return Promise.resolve();
+ }
+
+ unload() {
+ this.relay.close();
+ return Promise.resolve();
+ }
+
+ get supportedMethods() {
+ return [
+ "getInfo",
+ "makeInvoice",
+ "sendPayment",
+ "signMessage",
+ "getBalance",
+ "getTransactions",
+ ];
+ }
+
+ async connectPeer(): Promise {
+ console.error(
+ `${this.constructor.name} does not implement the getInvoices call`
+ );
+ throw new Error("Not yet supported with the currently used account.");
+ }
+
+ async getTransactions(): Promise {
+ const _transactions: Event[] = await LaWallet.request(
+ this.config.apiEndpoint,
+ "POST",
+ "/nostr/fetch",
+ {
+ authors: [this.config.ledgerPublicKey, this.config.urlxPublicKey],
+ kinds: [1112],
+ since: 0,
+ "#t": ["internal-transaction-ok", "inbound-transaction-start"],
+ "#p": [this.public_key],
+ }
+ );
+
+ const transactions = _transactions.map((event) => {
+ return {
+ ...event,
+ kind: event.kind as EventKind,
+ };
+ }) as Event[];
+
+ const parsedTransactions: ConnectorTransaction[] = await Promise.all(
+ transactions.map(
+ parseTransaction.bind(this, this.public_key, this.config.privateKey)
+ )
+ );
+
+ return {
+ data: {
+ transactions: parsedTransactions.sort(
+ (a, b) => b.settleDate - a.settleDate
+ ),
+ },
+ };
+ }
+
+ async getInfo(): Promise<
+ GetInfoResponse<{
+ alias: string;
+ pubkey: string;
+ lightning_address: string;
+ }>
+ > {
+ const { username, nodeAlias } = await LaWallet.request<{
+ username: string;
+ nodeAlias?: string;
+ }>(
+ this.config.identityEndpoint,
+ "GET",
+ `/api/pubkey/${this.public_key}`,
+ undefined
+ );
+ const domain = this.config.identityEndpoint.replace("https://", "");
+ return {
+ data: {
+ alias: nodeAlias || domain,
+ pubkey: this.public_key,
+ lightning_address: `${username}@${domain}`,
+ },
+ };
+ }
+
+ async getBalance(): Promise {
+ const filter = {
+ authors: [this.config.ledgerPublicKey],
+ kinds: [31111],
+ "#d": [`balance:BTC:${this.public_key}`],
+ };
+
+ const events: Event[] = await LaWallet.request(
+ this.config.apiEndpoint,
+ "POST",
+ "/nostr/fetch",
+ filter
+ );
+
+ const balanceEvent = events[0];
+
+ return {
+ data: {
+ balance: balanceEvent
+ ? parseInt(
+ balanceEvent?.tags.find(
+ (tag) => tag[0] === "amount"
+ )?.[1] as string
+ ) / 1000
+ : 0,
+ },
+ };
+ }
+
+ async sendPayment(args: SendPaymentArgs): Promise {
+ const paymentRequestDetails = lightningPayReq.decode(args.paymentRequest);
+ const unsignedEvent: Event = {
+ kind: 1112 as EventKind,
+ created_at: Math.floor(Date.now() / 1000),
+ tags: [
+ ["t", "internal-transaction-start"],
+ ["p", this.config.ledgerPublicKey],
+ ["p", this.config.urlxPublicKey],
+ ["bolt11", args.paymentRequest],
+ ],
+ content: JSON.stringify({
+ tokens: { BTC: paymentRequestDetails.millisatoshis?.toString() },
+ }),
+ };
+
+ const event: Event = finishEvent(unsignedEvent, this.config.privateKey);
+
+ try {
+ await LaWallet.request(
+ this.config.apiEndpoint,
+ "POST",
+ "/nostr/publish",
+ event,
+ "blob"
+ );
+ this.relay.connect();
+ return this.getPaymentStatus(event);
+ } catch (e) {
+ console.error(e);
+ if (e instanceof Error) toast.error(`${e.message}`);
+ throw e;
+ }
+ }
+
+ async keysend(args: KeysendArgs): Promise {
+ console.error(
+ `${this.constructor.name} does not implement the keysend call`
+ );
+ throw new Error("Keysend not yet supported.");
+ }
+
+ private onZapReceipt(event: Event) {
+ const pr = event.tags.find((tag) => tag[0] === "bolt11")?.[1] as string;
+ const paymentHash = lightningPayReq.decode(pr).tagsObject.payment_hash!;
+ this.invoices_paid[paymentHash] = true;
+ }
+
+ private async getPaymentStatus(event: Event): Promise {
+ const paymentRequestDetails = lightningPayReq.decode(
+ event.tags.find((tag) => tag[0] === "bolt11")?.[1] as string
+ );
+ const amountInSats = paymentRequestDetails.satoshis || 0;
+ const payment_route = { total_amt: amountInSats, total_fees: 0 };
+
+ await this.relay.connect();
+ return new Promise((resolve, reject) => {
+ const sub = this.relay.sub([
+ {
+ authors: [this.config.ledgerPublicKey, this.config.urlxPublicKey],
+ "#e": [event.id!],
+ "#t": [
+ "internal-transaction-error",
+ "internal-transaction-ok",
+ "outbound-transaction-start",
+ ],
+ },
+ ]);
+
+ sub.on("event", async (event) => {
+ const tag = event.tags.find((tag) => tag[0] === "t")![1];
+ const content = JSON.parse(event.content);
+ switch (tag) {
+ case "internal-transaction-ok": // Refund
+ if (event.tags[1][1] === this.public_key && !!content.memo) {
+ return reject(new Error(content.memo));
+ }
+ break;
+ case "internal-transaction-error": // No funds or ledger error
+ return reject(new Error(content.messages[0]));
+ case "outbound-transaction-start": // Payment done
+ return resolve({
+ data: {
+ preimage: await extractPreimage(event, this.config.privateKey),
+ paymentHash: paymentRequestDetails.tagsObject.payment_hash!,
+ route: payment_route,
+ },
+ });
+ }
+ });
+ });
+ }
+
+ async checkPayment(args: CheckPaymentArgs): Promise {
+ const zapReceipts = await this.getZapReceipts(10, this.last_invoice_check);
+ zapReceipts.forEach(this.onZapReceipt.bind(this));
+ return {
+ data: {
+ paid: !!this.invoices_paid[args.paymentHash],
+ },
+ };
+ }
+
+ signMessage(args: SignMessageArgs): Promise {
+ if (!this.config.apiEndpoint || !this.config.privateKey) {
+ return Promise.reject(new Error("Missing config"));
+ }
+ if (!args.message) {
+ return Promise.reject(new Error("Invalid message"));
+ }
+
+ return Promise.resolve({
+ data: {
+ message: args.message,
+ signature: schnorr
+ .sign(sha256(args.message).toString(Hex), this.config.privateKey)
+ .toString(),
+ },
+ });
+ }
+
+ async makeInvoice(args: MakeInvoiceArgs): Promise {
+ const unsignedZapEvent = makeZapRequest({
+ profile: this.public_key,
+ event: null,
+ amount: (args.amount as number) * 1000,
+ comment: args.memo,
+ relays: this.config.relayList,
+ });
+
+ const zapEvent: Event = finishEvent(
+ unsignedZapEvent,
+ this.config.privateKey
+ );
+
+ const params = {
+ amount: String((args.amount as number) * 1000),
+ comment: args.memo,
+ nostr: JSON.stringify(zapEvent),
+ lnurl: this.public_key,
+ };
+
+ const url = `/lnurlp/${this.public_key}/callback?${new URLSearchParams(
+ params
+ )}`;
+
+ const data = await LaWallet.request<{
+ pr: string;
+ }>(this.config.apiEndpoint, "GET", url);
+
+ const paymentRequestDetails = lightningPayReq.decode(data.pr);
+
+ this.last_invoice_check = Math.floor(Date.now() / 1000);
+
+ return {
+ data: {
+ paymentRequest: data.pr,
+ rHash: paymentRequestDetails.tagsObject.payment_hash!,
+ },
+ };
+ }
+
+ private async getZapReceipts(
+ limit: number = 10,
+ since: number = 0
+ ): Promise {
+ const filter = {
+ authors: [this.config.urlxPublicKey],
+ kinds: [9735],
+ since,
+ limit,
+ "#p": [this.public_key],
+ };
+
+ const zapEvents: Event[] = await LaWallet.request(
+ this.config.apiEndpoint,
+ "POST",
+ "/nostr/fetch",
+ filter
+ );
+
+ return zapEvents;
+ }
+
+ // Static Methods
+
+ /**
+ *
+ * @param url The URL to fetch data from.
+ * @param method HTTP Method
+ * @param path API path
+ * @param args POST arguments
+ * @param responseType
+ * @returns
+ * @throws {HttpError} When the response has an HTTP error status.
+ */
+ static async request(
+ url: string,
+ method: Method,
+ path: string,
+ args: Record = {},
+ responseType: ResponseType = "json"
+ ): Promise {
+ const headers = new Headers();
+ headers.append("Accept", "application/json");
+ headers.append("Content-Type", "application/json");
+
+ let body = null;
+ let res = null;
+
+ if (method !== "GET") {
+ body = JSON.stringify(args);
+ }
+
+ try {
+ res = await fetch(`${url}${path}`, {
+ headers,
+ method,
+ body,
+ });
+ } catch (e: unknown) {
+ throw new HttpError(0, "Network error", e as Error);
+ }
+
+ if (!res.ok) {
+ throw new HttpError(res.status, await res.text());
+ }
+ return await (responseType === "json" ? res.json() : res.text());
+ }
+}
+
+interface TransactionEventContent {
+ tokens: { BTC: number };
+ memo?: string;
+}
+
+interface InvoiceCache {
+ [paymentHash: string]: boolean; // paid
+}
+
+/** Utils Functions **/
+
+async function extractPreimage(
+ event: Event,
+ privateKey: string
+): Promise {
+ try {
+ const encrypted = event.tags.find(
+ (tag) => tag[0] === "preimage"
+ )?.[1] as string;
+
+ const messageKeyHex: string = await nip04.decrypt(
+ privateKey,
+ event.pubkey as string,
+ encrypted
+ );
+
+ return messageKeyHex;
+ } catch (e) {
+ return "";
+ }
+}
+
+export async function parseTransaction(
+ userPubkey: string,
+ privateKey: string,
+ event: Event
+): Promise {
+ const content = JSON.parse(event.content) as TransactionEventContent;
+ // Get bolt11 tag
+ const bolt11 = event.tags.find((tag) => tag[0] === "bolt11")?.[1] as string;
+
+ let paymentHash = event.id;
+ let memo = content.memo || "";
+
+ // Check if the event is a payment request
+ if (bolt11) {
+ const paymentRequestDetails = lightningPayReq.decode(bolt11);
+ paymentHash = paymentRequestDetails.tagsObject.payment_hash!;
+ memo = paymentRequestDetails.tagsObject.description || memo;
+ }
+
+ return {
+ id: event.id!,
+ preimage: await extractPreimage(event, privateKey),
+ settled: true,
+ settleDate: event.created_at * 1000,
+ totalAmount: content.tokens.BTC / 1000,
+ type: event.tags[1][1] === userPubkey ? "received" : "sent",
+ custom_records: {},
+ memo: memo,
+ payment_hash: paymentHash,
+ };
+}
+
+export function makeZapRequest({
+ profile,
+ event,
+ amount,
+ relays = ["wss://relay.lawallet.ar"],
+ comment = "",
+}: {
+ profile: string;
+ event: string | null;
+ amount: number;
+ comment: string;
+ relays: string[];
+}): Event {
+ if (!amount) throw new Error("amount not given");
+ if (!profile) throw new Error("profile not given");
+
+ const zr: Event = {
+ kind: 9734,
+ created_at: Math.round(Date.now() / 1000),
+ content: comment,
+ tags: [
+ ["p", profile],
+ ["amount", String(amount)],
+ ["relays", ...relays],
+ ],
+ };
+
+ if (event) {
+ zr.tags.push(["e", event]);
+ }
+
+ return zr;
+}
+
+export function finishEvent(event: Event, privateKey: string): Event {
+ event.pubkey = new Nostr(privateKey).getPublicKey();
+ event.id = getEventHash(event);
+ event.sig = signEvent(event, privateKey);
+ return event;
+}
+
+export function signEvent(event: Event, key: string) {
+ const signedEvent = schnorr.sign(getEventHash(event), key);
+ return secp256k1.etc.bytesToHex(signedEvent);
+}
diff --git a/src/extension/providers/nostr/types.ts b/src/extension/providers/nostr/types.ts
index 85e8f2f357..1ad1bf2a35 100644
--- a/src/extension/providers/nostr/types.ts
+++ b/src/extension/providers/nostr/types.ts
@@ -15,4 +15,6 @@ export enum EventKind {
Contacts = 3,
DM = 4,
Deleted = 5,
+ ZapRequest = 9734,
+ ZapReceipt = 9735,
}
diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json
index 6f2d44102a..02dfef3ae2 100644
--- a/src/i18n/locales/en/translation.json
+++ b/src/i18n/locales/en/translation.json
@@ -268,6 +268,30 @@
"connection_failed": "Connection failed. Is the BTCPay connection URL correct and accessible?"
}
},
+ "lawallet": {
+ "title": "LaWallet.io",
+ "page": {
+ "title": "Connect to LaWallet",
+ "instructions": "Set your LaWallet credentials below"
+ },
+ "private_key": "Private Key",
+ "api_endpoint": "API Endpoint",
+ "identity_endpoint": "Identity Endpoint",
+ "ledger_public_key": "Ledger Public key",
+ "urlx_public_key": "URLx Public key",
+ "relay_url": "Relay URL",
+ "errors": {
+ "toast": {
+ "title": "Identity not found",
+ "message": "You have no lightning address in the identity \"{{domain}}\"",
+ "verify": "Verify the following",
+ "match": "Identity Endpoint must match your Lightning Domain",
+ "walias": "Is your lightning address USERNAME<0>@{{domain}}0> or its using another domain?",
+ "endpoint": "The correct endpoint is built from https://app.<0>{{domain}}0>",
+ "http": "The identity provider is using the same protocol (<0>HTTP0> or <0>HTTPS0>)"
+ }
+ }
+ },
"commando": {
"title": "Core Lightning",
"page": {
diff --git a/src/i18n/locales/es/translation.json b/src/i18n/locales/es/translation.json
index 81038166c7..9b329004b7 100644
--- a/src/i18n/locales/es/translation.json
+++ b/src/i18n/locales/es/translation.json
@@ -178,6 +178,30 @@
"connection_failed": "La conexión falló. ¿Es correcto el URI de LNDHub?"
}
},
+ "lawallet": {
+ "title": "LaWallet.io",
+ "page": {
+ "title": "Conectar a LaWallet",
+ "instructions": "Ingresá los datos de LaWallet.io a continuación."
+ },
+ "private_key": "Llave privada",
+ "api_endpoint": "API Endpoint",
+ "identity_endpoint": "Identity Endpoint",
+ "ledger_public_key": "Ledger Public key",
+ "urlx_public_key": "URLx Public key",
+ "relay_url": "Relay URL",
+ "errors": {
+ "toast": {
+ "title": "No se encontró la identidad",
+ "message": "No hay una lightning address asignada a \"{{domain}}\"",
+ "verify": "Verificá lo siguiente",
+ "match": "El Identity Endpoint debe coincidir con el Lightning Domain",
+ "walias": "Tu lightning address es USERNAME<0>@{{domain}}0> o está utilizando otro dominio?",
+ "endpoint": "El Identity endpoint se deriva de esta url https://app.<0>{{domain}}0> de App",
+ "http": "El Identity endpoint utiliza el mismo protocolo (<0>HTTP0> or <0>HTTPS0>)"
+ }
+ }
+ },
"commando": {
"pubkey": {
"label": "Clave Pública"
diff --git a/static/assets/icons/lawallet.png b/static/assets/icons/lawallet.png
new file mode 100644
index 0000000000..c19899c053
Binary files /dev/null and b/static/assets/icons/lawallet.png differ