Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: switch assets based on address #503

Merged
merged 9 commits into from
Mar 22, 2024
43 changes: 34 additions & 9 deletions src/components/AddressInput.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,62 @@
import { createEffect, on } from "solid-js";

import { RBTC } from "../consts";
import { LN, RBTC } from "../consts";
import { useCreateContext } from "../context/Create";
import { useGlobalContext } from "../context/Global";
import { decodeAddress } from "../utils/compat";
import { extractAddress } from "../utils/invoice";
import { probeUserInput } from "../utils/compat";
import { extractAddress, extractInvoice } from "../utils/invoice";

const AddressInput = () => {
let inputRef: HTMLInputElement;

const { t } = useGlobalContext();
const { t, notify } = useGlobalContext();
const {
asset,
assetReceive,
reverse,
amountValid,
onchainAddress,
setAddressValid,
setAssetReceive,
setAssetSend,
setOnchainAddress,
setInvoice,
sendAmount,
} = useCreateContext();

const validateAddress = (input: HTMLInputElement) => {
const inputValue = input.value.trim();
const address = extractAddress(inputValue);
const invoice = extractInvoice(inputValue);

try {
input.setCustomValidity("");
input.classList.remove("invalid");
const assetName = asset();
decodeAddress(assetName, address);
setAddressValid(true);
setOnchainAddress(address);

const actualAsset = probeUserInput(assetName, address);

switch (actualAsset) {
case LN:
setAssetReceive(LN);
setAssetSend(asset());
setInvoice(invoice);
notify("success", t("switch_paste"));
break;

case null:
throw new Error();

default:
if (assetName !== actualAsset) {
setAssetReceive(actualAsset);
notify("success", t("switch_paste"));
}

input.setCustomValidity("");
input.classList.remove("invalid");
setAddressValid(true);
setOnchainAddress(address);
break;
}
} catch (e) {
setAddressValid(false);
if (inputValue.length !== 0) {
Expand Down
6 changes: 6 additions & 0 deletions src/components/AssetSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,17 @@ const SelectAsset = () => {
setAssetReceive,
setAssetSelect,
setAssetSend,
setInvoice,
setOnchainAddress,
} = useCreateContext();

const changeAsset = (newAsset: string) => {
if (isSelected(newAsset)) return;

// clear invoice and address
setInvoice("");
setOnchainAddress("");

// set main asset only if it is not LN
if (newAsset !== LN) {
setAsset(newAsset);
Expand Down
32 changes: 28 additions & 4 deletions src/components/InvoiceInput.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import { BigNumber } from "bignumber.js";
import { createEffect, on } from "solid-js";

import { RBTC } from "../consts";
import { LN, RBTC } from "../consts";
import { useCreateContext } from "../context/Create";
import { useGlobalContext } from "../context/Global";
import { calculateSendAmount } from "../utils/calculate";
import { decodeInvoice, extractInvoice, isLnurl } from "../utils/invoice";
import { probeUserInput } from "../utils/compat";
import {
decodeInvoice,
extractAddress,
extractInvoice,
isLnurl,
} from "../utils/invoice";
import { validateInvoice } from "../utils/validation";

const InvoiceInput = () => {
let inputRef: HTMLTextAreaElement;

const { t } = useGlobalContext();
const { t, notify } = useGlobalContext();
const {
asset,
boltzFee,
Expand All @@ -27,10 +33,28 @@ const InvoiceInput = () => {
setLnurl,
setReceiveAmount,
setSendAmount,
setAssetSend,
setAssetReceive,
setOnchainAddress,
} = useCreateContext();

const validate = (input: HTMLTextAreaElement) => {
const inputValue = extractInvoice(input.value.trim());
const val = input.value.trim();

const address = extractAddress(val);
const actualAsset = probeUserInput(LN, address);

// Auto switch direction based on address
if (actualAsset !== LN && actualAsset !== null) {
setAssetSend(LN);
setAssetReceive(actualAsset);
setOnchainAddress(address);
notify("success", t("switch_paste"));
return;
}

const inputValue = extractInvoice(val);

try {
input.setCustomValidity("");
setInvoiceError("");
Expand Down
6 changes: 6 additions & 0 deletions src/i18n/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ const dict = {
paste_invalid:
"Clipboard contains invalid characters or maximum amount is exceeded",
email: "Email",
switch_paste: "Switched swap direction/asset based on pasted content",
},
de: {
language: "Deutsch",
Expand Down Expand Up @@ -357,6 +358,8 @@ const dict = {
paste_invalid:
"Zwischenablage enthält ungültige Zeichen oder der maximale Betrag wurde überschritten",
email: "Email",
switch_paste:
"Swap-Richtung/Asset basierend auf eingefügtem Inhalt gewechselt",
},
es: {
language: "Español",
Expand Down Expand Up @@ -541,6 +544,8 @@ const dict = {
paste_invalid:
"El portapapeles contiene caracteres no válidos o se ha excedido el importe máximo",
email: "Email",
switch_paste:
"Cambiado de dirección/activo de intercambio basado en el contenido pegado",
},
zh: {
language: "中文",
Expand Down Expand Up @@ -707,6 +712,7 @@ const dict = {
swap_in_progress: "此交换仍在进行中。",
paste_invalid: "剪贴板包含无效字符或超出最大金额",
email: "邮箱",
switch_paste: "根据粘贴的内容切换交换方向/资产",
michael1011 marked this conversation as resolved.
Show resolved Hide resolved
},
};

Expand Down
40 changes: 39 additions & 1 deletion src/utils/compat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import {
import { Network as LiquidNetwork } from "liquidjs-lib/src/networks";

import { config } from "../config";
import { LBTC } from "../consts";
import { BTC, LBTC, LN } from "../consts";
import { isInvoice, isLnurl } from "./invoice";

type LiquidTransactionOutputWithKey = LiquidTransactionOutput & {
blindingPrivateKey?: Buffer;
Expand All @@ -37,6 +38,8 @@ type DecodedAddress = { script: Buffer; blindingKey?: Buffer };
export let secp: Secp256k1ZKP;
let confi: confidential.Confidential;

const possibleUserInputTypes = [LN, LBTC, BTC];

const setup = async () => {
if (confi !== undefined) {
return;
Expand Down Expand Up @@ -83,6 +86,40 @@ const decodeAddress = (asset: string, addr: string): DecodedAddress => {
};
};

const probeUserInputOption = (asset: string, input: string): boolean => {
switch (asset) {
case LN:
return isLnurl(input) || isInvoice(input);

default:
try {
decodeAddress(asset, input);
return true;
} catch (e) {
return false;
}
}
};

const probeUserInput = (
expectedAsset: string,
input: string,
): string | null => {
if (probeUserInputOption(expectedAsset, input)) {
return expectedAsset;
}

for (const asset of possibleUserInputTypes.filter(
(type) => type !== expectedAsset,
)) {
if (probeUserInputOption(asset, input)) {
return asset;
}
}

return null;
};

const getNetwork = (
asset: string,
network?: string,
Expand Down Expand Up @@ -190,4 +227,5 @@ export {
getConstructClaimTransaction,
getConstructRefundTransaction,
LiquidTransactionOutputWithKey,
probeUserInput,
};
1 change: 0 additions & 1 deletion tests/components/AddressInput.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ describe("AddressInput", () => {
${true} | ${BTC} | ${"bcrt1q7vq47xpsg4t080205edaulc3sdsjpdxy9svhr3"}
${true} | ${BTC} | ${"bcrt1pjyk4csn4nd4apwqy8s2p5kj5kywtrwzejtjalz3sufeljsycxw3qgrstgd"}
${false} | ${BTC} | ${"02d96eadea3d780104449aca5c93461ce67c1564e2e1d73225fa67dd3b997a6018"}
${false} | ${LBTC} | ${"bcrt1pjyk4csn4nd4apwqy8s2p5kj5kywtrwzejtjalz3sufeljsycxw3qgrstgd"}
${true} | ${LBTC} | ${"CTEyTteD4cQg2NfF1yGWUU1rWSDC8sKHrj5BZJzr8kzyKFXwNCJ8VyDhi45Q98KdSf3jeTkbjJy18JkP"}
${true} | ${LBTC} | ${"AzpjfmC41JpC6ieu3odwFBqtF4isFeY8RHv1e699EM2RgiyHd49og66a8qLLMDrhL8pCLeWAxJat1ebD"}
${true} | ${LBTC} | ${"el1qqt7nl8pw6278yxv38fezzw8lmqh40prpusfurcvsh2xn3sl0pvnz3whllcapdnxcxn2u0wumpu7u2u6anh2juvmz7spx6snmn"}
Expand Down