Skip to content

Commit

Permalink
Update vortex extension
Browse files Browse the repository at this point in the history
- prepare for publish prettier + lint
- Changelog Update
- Enable redemption of tokens regardless of their unit distribution
- Add \'fiat\' transactions
  • Loading branch information
d4rp4t committed Jan 26, 2025
1 parent 2be4880 commit 67ef202
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 32 deletions.
14 changes: 10 additions & 4 deletions extensions/vortex/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
# Alby Changelog

## [Features] - 2024-12-23
- Removed View Balance function - it could be checked in last transactions.
## [Features] - 2025-01-26
- Added Support for creating invoices in fiat.
- Added Support for sending zaps via lightning address in fiat
- Added support for cashu tokens with all units

## [Features] - 2025-01-12

- Removed View Balance function - it could be checked in last transactions.
- Modified Last Transactions function - now user can see details.
- Added Cashu Tokens and LNURL redeeming support in Redeem function.
- Code cleanup.
- Code cleanup.
- Dependency updates.
- Added Inline Command Parameters for Send and Redeem commands.

## [Dependency Updates] - 2024-05-17

- Fixed bug handling lightning addresses with no custom key/value records

## [Initial Version] - 2023-12-22
## [Initial Version] - 2023-12-22
45 changes: 39 additions & 6 deletions extensions/vortex/src/CreateInvoice.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,41 @@
import { setInterval } from "timers";
import { useEffect, useRef, useState } from "react";
import { Action, ActionPanel, Detail, environment, Form, Icon, LaunchProps, showToast, Toast } from "@raycast/api";
import {
Action,
ActionPanel,
Detail,
environment,
Form,
getPreferenceValues,
Icon,
LaunchProps,
showToast,
Toast,
} from "@raycast/api";
import { toDataURL } from "qrcode";
import { webln } from "@getalby/sdk";

import ConnectionError from "./components/ConnectionError";
import { connectWallet } from "./utils/wallet";
import { fiat } from "@getalby/lightning-tools";
import "cross-fetch/polyfill";

export default function CreateInvoice(props: LaunchProps<{ arguments: Arguments.Createinvoice }>) {
const [amount, setAmount] = useState(props.arguments.input);
const [amount, setAmount] = useState<string>(props.arguments.input);
const [description, setDescription] = useState("");
const [invoice, setInvoice] = useState<string | undefined>();
const [invoiceMarkdown, setInvoiceMarkdown] = useState<string | undefined>();
const [invoiceState, setInvoiceState] = useState("pending");
const [loading, setLoading] = useState<boolean>(false);
const [connectionError, setConnectionError] = useState<unknown>(null);
const [isSatDenomination, setSatDenomination] = useState(true);

const invoiceCheckerIntervalRef = useRef<NodeJS.Timeout>();
const invoiceCheckCounterRef = useRef(0);
const nwc = useRef<webln.NostrWebLNProvider>();

const fiatCurrency = getPreferenceValues<{ currency: string }>().currency;

const checkInvoicePayment = (invoice: string) => {
if (invoiceCheckerIntervalRef && invoiceCheckerIntervalRef.current) {
clearInterval(invoiceCheckerIntervalRef.current);
Expand Down Expand Up @@ -56,10 +73,15 @@ export default function CreateInvoice(props: LaunchProps<{ arguments: Arguments.

try {
setLoading(true);
let satoshis: string | number = amount;
nwc.current = await connectWallet();
await showToast(Toast.Style.Animated, "Requesting invoice...");
if (!isSatDenomination) {
satoshis = await fiat.getSatoshiValue({ amount: amount, currency: fiatCurrency });
console.log(satoshis);
}
const invoiceResponse = await nwc.current.makeInvoice({
amount, // This should be the amount in satoshis
amount: satoshis, // This should be the amount in satoshis
defaultMemo: description,
});

Expand All @@ -74,6 +96,7 @@ export default function CreateInvoice(props: LaunchProps<{ arguments: Arguments.
} catch (error) {
setConnectionError(error);
await showToast(Toast.Style.Failure, "Error creating invoice");
console.trace(error);
} finally {
setLoading(false);
}
Expand Down Expand Up @@ -117,13 +140,19 @@ export default function CreateInvoice(props: LaunchProps<{ arguments: Arguments.
actions={
<ActionPanel>
<Action title="Create Invoice" onAction={handleCreateInvoice} />

<Action
title={`Swap Currency to ${isSatDenomination ? fiatCurrency : "Satoshi"}`}
onAction={() => setSatDenomination(!isSatDenomination)}
shortcut={{ modifiers: ["cmd"], key: "s" }}
/>
</ActionPanel>
}
>
<Form.TextField
id="amount"
title="Amount (Satoshis)"
placeholder="Enter the amount in satoshis"
title={`Amount (${isSatDenomination ? "Satoshis" : fiatCurrency})`}
placeholder={`Enter the amount in ${isSatDenomination ? "satoshis" : fiatCurrency}`}
value={amount}
onChange={setAmount}
/>
Expand All @@ -147,7 +176,11 @@ export default function CreateInvoice(props: LaunchProps<{ arguments: Arguments.
}
metadata={
<Detail.Metadata>
<Detail.Metadata.Label title="Amount" text={`${amount} sats`} icon={Icon.Bolt} />
<Detail.Metadata.Label
title="Amount"
text={`${amount} ${isSatDenomination ? "sats" : fiatCurrency}`}
icon={Icon.Bolt}
/>
<Detail.Metadata.Label title="Description" text={description} />
<Detail.Metadata.TagList title="Status">
<Detail.Metadata.TagList.Item
Expand Down
16 changes: 8 additions & 8 deletions extensions/vortex/src/Redeem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,21 +102,21 @@ export default function Redeem(props: LaunchProps<{ arguments: Arguments.Redeem
let token: Token;
try {
token = getDecodedToken(input);

if (!token.unit || token.unit !== "sat") {
setError("For now Vortex can receive only satoshi tokens.");
return;
if (!token.unit) {
setError("Invalid token");
}

const meltQuote = await requestMeltQuote(token);
const meltQuoteResponse = await requestMeltQuote(token);

if (meltQuote) {
if (meltQuoteResponse) {
const { meltQuote, unitPrice } = meltQuoteResponse;
setCashuData({
data: token,
amount: meltQuote.amount - meltQuote.fee_reserve,
fee: meltQuote.fee_reserve,
amount: Math.floor((meltQuote.amount - meltQuote.fee_reserve) * unitPrice),
fee: Math.floor(meltQuote.fee_reserve * unitPrice),
});
}

return;
} catch (e) {
console.debug(e);
Expand Down
5 changes: 3 additions & 2 deletions extensions/vortex/src/Send.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ export default function Send(props: LaunchProps<{ arguments: Arguments.Send }>)
console.error(e);
}
}
if (text && (text.toLowerCase().startsWith("lnbc1") || text.match(LN_ADDRESS_REGEX))) {
//invoice doesn't always start with "lnbc1" as 1 is only bech32 separator and invoice can contain amount.
if (text && (text.toLowerCase().startsWith("lnbc") || text.match(LN_ADDRESS_REGEX))) {
setInput(text);
}
};
Expand All @@ -65,7 +66,7 @@ export default function Send(props: LaunchProps<{ arguments: Arguments.Send }>)
if (!input) {
return;
}
if (input?.toLowerCase().startsWith("lnbc1")) {
if (input?.toLowerCase().startsWith("lnbc")) {
const invoice = new Invoice({ pr: input });
setInvoice(invoice.paymentRequest);
return;
Expand Down
27 changes: 21 additions & 6 deletions extensions/vortex/src/components/PayToLightningAddress.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import "cross-fetch/polyfill";
import { useState, useEffect, useRef } from "react";
import { useEffect, useRef, useState } from "react";

import { Form, ActionPanel, Action, showToast, Toast, popToRoot, Clipboard } from "@raycast/api";
import { LightningAddress } from "@getalby/lightning-tools";
import { Action, ActionPanel, Clipboard, Form, getPreferenceValues, popToRoot, showToast, Toast } from "@raycast/api";
import { fiat, LightningAddress } from "@getalby/lightning-tools";
import { connectWallet } from "../utils/wallet";

export default function PayToLightingAddress(props: { lightningAddress: string }) {
Expand All @@ -12,8 +12,12 @@ export default function PayToLightingAddress(props: { lightningAddress: string }
const [comment, setComment] = useState("");
const [commentAllowed, setCommentAllowed] = useState(true);
const [loading, setLoading] = useState<boolean>(false);
const [isSatDenomination, setSatDenomination] = useState(true);

const amountFieldRef = useRef<Form.TextField>(null);

const fiatCurrency = getPreferenceValues<{ currency: string }>().currency;

useEffect(() => {
lookupLightningAddress();
amountFieldRef.current?.focus();
Expand All @@ -27,7 +31,13 @@ export default function PayToLightingAddress(props: { lightningAddress: string }
};

const requestInvoice = async () => {
const satoshis = parseInt(amount.trim(), 10);
let satoshis;

if (isSatDenomination) {
satoshis = parseInt(amount.trim(), 10);
} else {
satoshis = await fiat.getSatoshiValue({ amount: amount.trim(), currency: fiatCurrency });
}
if (isNaN(satoshis) || satoshis <= 0) {
await showToast(Toast.Style.Failure, "Invalid amount");
return;
Expand Down Expand Up @@ -115,15 +125,20 @@ export default function PayToLightingAddress(props: { lightningAddress: string }
<ActionPanel>
<Action title={`Send Payment to ${lightningAddress} `} onAction={handleSendPayment} />
<Action title={`Copy Invoice`} onAction={handleCopyInvoice} />
<Action
title={`Swap Currency to ${isSatDenomination ? fiatCurrency : "satoshi"}`}
shortcut={{ modifiers: ["cmd"], key: "s" }}
onAction={() => setSatDenomination(!isSatDenomination)}
/>
</ActionPanel>
}
>
{lightningAddress && <Form.Description title="Lightning Address" text={lightningAddress} />}
{lightningAddressInfo && <Form.Description title="Description" text={lightningAddressInfo} />}
<Form.TextField
id="amount"
title="Amount (Satoshis)"
placeholder="Enter the amount in satoshis"
title={`Amount (${isSatDenomination ? "Satoshis" : fiatCurrency})`}
placeholder={`Enter the amount in ${isSatDenomination ? "satoshi" : fiatCurrency}`}
ref={amountFieldRef}
autoFocus={true}
value={amount}
Expand Down
21 changes: 15 additions & 6 deletions extensions/vortex/src/utils/cashuRedeemUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { sumProofs } from "@cashu/cashu-ts/dist/lib/es6/utils";
import { showToast, Toast } from "@raycast/api";
import { NostrWebLNProvider } from "@getalby/sdk/dist/webln";
import Style = Toast.Style;
import { Invoice } from "@getalby/lightning-tools";

let nwc: NostrWebLNProvider;
let mint: CashuMint;
Expand All @@ -21,21 +22,23 @@ const requestMeltQuote = async (token: Token) => {
try {
nwc = await connectWallet();
mint = new CashuMint(token.mint);
wallet = new CashuWallet(mint);
wallet = new CashuWallet(mint, { unit: token.unit });

if (!nwc) {
throw new Error("Connection Error!");
}

const totalAmount = sumProofs(token.proofs);
const invoice = await nwc.makeInvoice(totalAmount);
const amount = sumProofs(token.proofs);
const unitPrice = await getSatoshiRate(wallet);
const satoshiAmount = Math.floor(amount * unitPrice);
const invoice = await nwc.makeInvoice(satoshiAmount);

console.debug("Generated Invoice:", invoice);

const meltQuote = await wallet.createMeltQuote(invoice.paymentRequest);

await showToast(Style.Success, "Successfully retrieved Melt Quote");
return meltQuote;
return { meltQuote, unitPrice };
} catch (error) {
console.error("Error requesting Melt Quote:", error);
await showToast(Style.Failure, "Can't request Melt Quote from Mint!");
Expand All @@ -52,7 +55,7 @@ const meltToken = async (token: Token, amount: number) => {

nwc = await connectWallet();
mint = new CashuMint(token.mint);
wallet = new CashuWallet(mint);
wallet = new CashuWallet(mint, { unit: token.unit });

if (!nwc) {
throw new Error("Connection Error!");
Expand Down Expand Up @@ -87,4 +90,10 @@ const checkMeltQuote = async (meltQuote: MeltQuoteResponse) => {
}
};

export { checkMeltQuote, meltToken, requestMeltQuote };
const getSatoshiRate = async (wallet: CashuWallet) => {
const quote = await wallet.createMintQuote(1);
const inv = new Invoice({ pr: quote.request });
return inv.satoshi;
};

export { checkMeltQuote, meltToken, requestMeltQuote, getSatoshiRate };

0 comments on commit 67ef202

Please sign in to comment.