diff --git a/README.md b/README.md index ae6140ff..feb952ed 100644 --- a/README.md +++ b/README.md @@ -93,10 +93,16 @@ Prereleased tags will be created for master. #### Release +##### Android + First bump up the `versionCode` and `versionName` in `./andriod/app/build.gradle`. The `versionCode` must always go up by one when making a release. The `versionName` can mimic `package.json` with an extra build number like `0.4.3-1` to make it easier to keep things looking like they are in sync when android only releases go out. Publish a new tag like `0.4.3-1` in order to trigger a signed release version running in mainnet mode. +##### iOS + +In `ios/App/App.xcodeproj/project.pbxproj` bump `MARKETING_VERSION` and then do whatever needs to be done in testflight to get it released. + ### Creating keys for the first time 1. Generate a new signing key diff --git a/android/app/build.gradle b/android/app/build.gradle index b3f1ca8a..6cbd298a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.mutinywallet.mutinywallet" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 33 - versionName "0.4.32" + versionCode 34 + versionName "0.4.33" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj index 7d19f377..9025ffd9 100644 --- a/ios/App/App.xcodeproj/project.pbxproj +++ b/ios/App/App.xcodeproj/project.pbxproj @@ -357,7 +357,7 @@ INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 1.4.32; + MARKETING_VERSION = 1.4.34; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = com.mutinywallet.mutiny; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -383,7 +383,7 @@ INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 1.4.32; + MARKETING_VERSION = 1.4.34; PRODUCT_BUNDLE_IDENTIFIER = com.mutinywallet.mutiny; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/package.json b/package.json index fd0cf16c..1b3a791c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mutiny-wallet", - "version": "0.4.32", + "version": "0.4.34", "license": "MIT", "packageManager": "pnpm@8.6.6", "scripts": { @@ -57,7 +57,7 @@ "@kobalte/core": "^0.9.8", "@kobalte/tailwindcss": "^0.5.0", "@modular-forms/solid": "^0.18.1", - "@mutinywallet/mutiny-wasm": "0.4.32", + "@mutinywallet/mutiny-wasm": "0.4.34", "@mutinywallet/waila-wasm": "^0.2.4", "@solid-primitives/upload": "^0.0.111", "@solid-primitives/websocket": "^1.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 71be13e8..c68ad9af 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,8 +54,8 @@ importers: specifier: ^0.18.1 version: 0.18.1(solid-js@1.8.5) '@mutinywallet/mutiny-wasm': - specifier: 0.4.32 - version: 0.4.32 + specifier: 0.4.34 + version: 0.4.34 '@mutinywallet/waila-wasm': specifier: ^0.2.4 version: 0.2.4 @@ -2573,8 +2573,8 @@ packages: solid-js: 1.8.5 dev: false - /@mutinywallet/mutiny-wasm@0.4.32: - resolution: {integrity: sha512-O+futHE6a2mBIWXLQvzsATW9eU7adYe4OBjt4Gnxgln1xfNxyLD4u8JBQJF1Dj2EaJotD0A6WP+q3GS77NhEmg==} + /@mutinywallet/mutiny-wasm@0.4.34: + resolution: {integrity: sha512-pM3kRz2acXM4E+xGGjIz1rubyj/9Sg+8XfZL5GD4wLZop+mF/YRFY8r1XkJuknNBZ6kTrlD4zbjxNsnVOCLFpA==} dev: false /@mutinywallet/waila-wasm@0.2.4: diff --git a/src/components/AmountEditable.tsx b/src/components/AmountEditable.tsx index 4f6be057..96632da1 100644 --- a/src/components/AmountEditable.tsx +++ b/src/components/AmountEditable.tsx @@ -22,9 +22,7 @@ import { useI18n } from "~/i18n/context"; import { Network } from "~/logic/mutinyWalletSetup"; import { useMegaStore } from "~/state/megaStore"; import { DIALOG_CONTENT, DIALOG_POSITIONER } from "~/styles/dialogs"; -import { fiatToSats, satsToFiat } from "~/utils"; - -import { Currency } from "./ChooseCurrency"; +import { Currency, fiatToSats, satsToFiat } from "~/utils"; // Checks the users locale to determine if decimals should be a "." or a "," const decimalDigitDivider = Number(1.0) diff --git a/src/components/ChooseCurrency.tsx b/src/components/ChooseCurrency.tsx index db8dc56a..b3522258 100644 --- a/src/components/ChooseCurrency.tsx +++ b/src/components/ChooseCurrency.tsx @@ -1,119 +1,37 @@ import { createForm } from "@modular-forms/solid"; -import { createSignal, Show } from "solid-js"; +import { createSignal, For, Show } from "solid-js"; import { useNavigate } from "solid-start"; -import { - Button, - ExternalLink, - InfoBox, - NiceP, - SelectField, - VStack -} from "~/components"; +import { Button, ExternalLink, InfoBox, NiceP, VStack } from "~/components"; import { useI18n } from "~/i18n/context"; import { useMegaStore } from "~/state/megaStore"; -import { eify, timeout } from "~/utils"; - -export interface Currency { - value: string; - label: string; - hasSymbol?: string; - maxFractionalDigits: number; -} +import { + BTC_OPTION, + Currency, + eify, + FIAT_OPTIONS, + timeout, + USD_OPTION +} from "~/utils"; type ChooseCurrencyForm = { fiatCurrency: string; }; -/** - * FIAT_OPTIONS is an array of possible currencies - * All available currencies can be found here https://api.coingecko.com/api/v3/simple/supported_vs_currencies - * @Currency - * @param {string} label - should be in the format {Name} {ISO code} - * @param {string} values - are uppercase ISO 4217 currency code - * @param {string?} hasSymbol - if the currency has a symbol it should be represented as a string - * @param {number} maxFractionalDigits - the standard fractional units used by the currency should be set with maxFractionalDigits - * - * Bitcoin is represented as: - * { - * label: "bitcoin BTC", - * value: "BTC", - * hasSymbol: "₿", - * maxFractionalDigits: 8 - * } - */ - -export const FIAT_OPTIONS: Currency[] = [ - { - label: "Bitcoin BTC", - value: "BTC", - hasSymbol: "₿", - maxFractionalDigits: 8 - }, - { - label: "United States Dollar USD", - value: "USD", - hasSymbol: "$", - maxFractionalDigits: 2 - }, - { label: "Swiss Franc CHF", value: "CHF", maxFractionalDigits: 2 }, - { - label: "Chinese Yuan CNY", - value: "CNY", - hasSymbol: "¥", - maxFractionalDigits: 2 - }, - { - label: "Euro EUR", - value: "EUR", - hasSymbol: "€", - maxFractionalDigits: 2 - }, - { - label: "Brazilian Real BRL", - value: "BRL", - hasSymbol: "R$", - maxFractionalDigits: 2 - }, - { - label: "British Pound GBP", - value: "GBP", - hasSymbol: "₤", - maxFractionalDigits: 2 - }, - { - label: "Australia Dollar AUD", - value: "AUD", - hasSymbol: "$", - maxFractionalDigits: 2 - }, - { - label: "Japanese Yen JPY", - value: "JPY", - hasSymbol: "¥", - maxFractionalDigits: 0 - }, - { - label: "Korean Won KRW", - value: "KRW", - hasSymbol: "₩", - maxFractionalDigits: 0 - }, - { label: "Kuwaiti Dinar KWD", value: "KWD", maxFractionalDigits: 3 } -].sort((a, b) => (a.value > b.value ? 1 : b.value > a.value ? -1 : 0)); - -export const USD_INDEX = FIAT_OPTIONS.findIndex((fo) => fo.value === "USD"); -export const BTC_INDEX = FIAT_OPTIONS.findIndex((fo) => fo.value === "BTC"); +const COMBINED_OPTIONS: Currency[] = [USD_OPTION, BTC_OPTION, ...FIAT_OPTIONS]; export function ChooseCurrency() { const i18n = useI18n(); const [error, setError] = createSignal(); - const [state, actions] = useMegaStore(); + const [_state, actions] = useMegaStore(); const [loading, setLoading] = createSignal(false); const navigate = useNavigate(); function findCurrencyByValue(value: string) { - return FIAT_OPTIONS.find((currency) => currency.value === value); + return ( + COMBINED_OPTIONS.find((currency) => currency.value === value) ?? + USD_OPTION + ); } const [_chooseCurrencyForm, { Form, Field }] = @@ -135,7 +53,7 @@ export function ChooseCurrency() { const handleFormSubmit = async (f: ChooseCurrencyForm) => { setLoading(true); try { - actions.saveFiat(findCurrencyByValue(f.fiatCurrency) || state.fiat); + actions.saveFiat(findCurrencyByValue(f.fiatCurrency)); await timeout(1000); navigate("/"); @@ -157,16 +75,17 @@ export function ChooseCurrency() { {(field, props) => ( - + class="w-full rounded-lg bg-m-grey-750 py-2 pl-4 pr-12 text-base font-normal text-white" + > + + {({ value, label }) => ( + + )} + + )} diff --git a/src/components/PendingNwc.tsx b/src/components/PendingNwc.tsx index ecd0b4f8..a1adea1e 100644 --- a/src/components/PendingNwc.tsx +++ b/src/components/PendingNwc.tsx @@ -1,4 +1,3 @@ -import { NwcProfile } from "@mutinywallet/mutiny-wasm"; import { createEffect, createResource, @@ -42,10 +41,11 @@ export function PendingNwc() { const [error, setError] = createSignal(); async function fetchPendingRequests() { - const profiles: NwcProfile[] = - await state.mutiny_wallet?.get_nwc_profiles(); + const profiles = await state.mutiny_wallet?.get_nwc_profiles(); + if (!profiles) return []; const pending = await state.mutiny_wallet?.get_pending_nwc_invoices(); + if (!pending) return []; const pendingItems: PendingItem[] = []; diff --git a/src/components/SetupErrorDisplay.tsx b/src/components/SetupErrorDisplay.tsx index a29cc6d7..64c253cd 100644 --- a/src/components/SetupErrorDisplay.tsx +++ b/src/components/SetupErrorDisplay.tsx @@ -186,6 +186,9 @@ export function SetupErrorDisplay(props: { initialError: Error }) { {i18n.t("error.on_boot.loading_failed.description")} + {i18n.t( "error.on_boot.loading_failed.repair_options" diff --git a/src/components/TagEditor.tsx b/src/components/TagEditor.tsx index bd6d5258..5946b5d6 100644 --- a/src/components/TagEditor.tsx +++ b/src/components/TagEditor.tsx @@ -2,30 +2,31 @@ import { createOptions, Select } from "@thisbeyond/solid-select"; import "~/styles/solid-select.css"; +import { TagItem, TagKind } from "@mutinywallet/mutiny-wasm"; import { createMemo, createSignal, onMount } from "solid-js"; import { useMegaStore } from "~/state/megaStore"; -import { MutinyTagItem, sortByLastUsed } from "~/utils"; +import { sortByLastUsed } from "~/utils"; -const createLabelValue = (label: string): Partial => { - return { name: label, kind: "Contact" }; +const createLabelValue = (label: string): Partial => { + return { name: label, kind: TagKind.Contact }; }; export function TagEditor(props: { - selectedValues: Partial[]; - setSelectedValues: (value: Partial[]) => void; + selectedValues: Partial[]; + setSelectedValues: (value: Partial[]) => void; placeholder: string; autoFillTag?: string | undefined; }) { const [_state, actions] = useMegaStore(); - const [availableTags, setAvailableTags] = createSignal([]); + const [availableTags, setAvailableTags] = createSignal([]); onMount(async () => { const tags = await actions.listTags(); if (tags) { setAvailableTags( tags - .filter((tag) => tag.kind === "Contact") + .filter((tag) => tag.kind === TagKind.Contact) .sort(sortByLastUsed) ); if (props.autoFillTag && availableTags()) { @@ -51,7 +52,7 @@ export function TagEditor(props: { }); }); - const onChange = (selected: MutinyTagItem[]) => { + const onChange = (selected: TagItem[]) => { props.setSelectedValues(selected); const lastValue = selected[selected.length - 1]; diff --git a/src/components/layout/Misc.tsx b/src/components/layout/Misc.tsx index b57b3e47..b3b7d87b 100644 --- a/src/components/layout/Misc.tsx +++ b/src/components/layout/Misc.tsx @@ -4,6 +4,7 @@ import { Checkbox as KCheckbox, Separator } from "@kobalte/core"; +import { TagItem, TagKind } from "@mutinywallet/mutiny-wasm"; import { createResource, createSignal, @@ -27,7 +28,7 @@ import { } from "~/components"; import { useI18n } from "~/i18n/context"; import { useMegaStore } from "~/state/megaStore"; -import { generateGradient, MutinyTagItem } from "~/utils"; +import { generateGradient } from "~/utils"; export const SmallHeader: ParentComponent<{ class?: string }> = (props) => { return ( @@ -268,7 +269,7 @@ export const TinyText: ParentComponent = (props) => { export const TinyButton: ParentComponent<{ onClick: () => void; - tag?: MutinyTagItem; + tag?: TagItem; }> = (props) => { // TODO: don't need to run this if it's not a contact const [gradient] = createResource(async () => { @@ -276,7 +277,7 @@ export const TinyButton: ParentComponent<{ }); const bg = () => - props.tag?.name && props.tag?.kind === "Contact" + props.tag?.name && props.tag?.kind === TagKind.Contact ? gradient() : "rgb(255 255 255 / 0.1)"; diff --git a/src/components/layout/SelectField.tsx b/src/components/layout/SelectField.tsx deleted file mode 100644 index f7598db5..00000000 --- a/src/components/layout/SelectField.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { Select as KSelect } from "@kobalte/core"; -import { JSX, Show, splitProps } from "solid-js"; - -import check from "~/assets/icons/check.svg"; -import upDown from "~/assets/icons/up-down.svg"; - -interface IOption { - value: string; - label: string; -} - -type SelectProps = { - options: IOption[]; - multiple?: boolean; - size?: string | number; - caption?: string; - name: string; - label?: string | undefined; - placeholder?: string | undefined; - value: string | undefined; - error: string; - required?: boolean | undefined; - disabled?: boolean | undefined; - ref: (element: HTMLSelectElement) => void; - onInput: JSX.EventHandler; - onChange: JSX.EventHandler; - onBlur: JSX.EventHandler; -}; - -export function SelectField(props: SelectProps) { - // Split select element props - const [rootProps, selectProps] = splitProps( - props, - ["name", "placeholder", "options", "required", "disabled"], - ["placeholder", "ref", "onInput", "onChange", "onBlur"] - ); - - return ( -
- ( - - - - {props.item.rawValue.label} - - - - check - - - )} - > - - - {props.label} - - - - - > - {(state) => state.selectedOption().label} - - - upDown - - - - - {props.caption} - - - - - - - - - {props.error} - - -
- ); -} diff --git a/src/components/layout/index.ts b/src/components/layout/index.ts index 6c52a441..3407fdb2 100644 --- a/src/components/layout/index.ts +++ b/src/components/layout/index.ts @@ -6,7 +6,6 @@ export * from "./Linkify"; export * from "./Misc"; export * from "./ProgressBar"; export * from "./Radio"; -export * from "./SelectField"; export * from "./TextField"; export * from "./ExternalLink"; export * from "./LoadingSpinner"; diff --git a/src/logic/mutinyWalletSetup.ts b/src/logic/mutinyWalletSetup.ts index 176c043c..9e7d0106 100644 --- a/src/logic/mutinyWalletSetup.ts +++ b/src/logic/mutinyWalletSetup.ts @@ -187,7 +187,16 @@ export async function doubleInitDefense() { export async function initializeWasm() { // Actually intialize the WASM, this should be the first thing that requires the WASM blob to be downloaded - await initMutinyWallet(); + + // If WASM is already initialized, don't init twice + try { + const _sats_the_standard = MutinyWallet.convert_btc_to_sats(1); + console.debug("MutinyWallet WASM already initialized, skipping init"); + return; + } catch (e) { + console.debug("MutinyWallet WASM about to be initialized"); + await initMutinyWallet(); + } } export async function setupMutinyWallet( diff --git a/src/routes/Receive.tsx b/src/routes/Receive.tsx index 79b0c4b5..1acd6764 100644 --- a/src/routes/Receive.tsx +++ b/src/routes/Receive.tsx @@ -3,7 +3,8 @@ import { Contact, MutinyBip21RawMaterials, - MutinyInvoice + MutinyInvoice, + TagItem } from "@mutinywallet/mutiny-wasm"; import { createEffect, @@ -49,12 +50,7 @@ import { } from "~/components"; import { useI18n } from "~/i18n/context"; import { useMegaStore } from "~/state/megaStore"; -import { - eify, - MutinyTagItem, - objectToSearchParams, - vibrateSuccess -} from "~/utils"; +import { eify, objectToSearchParams, vibrateSuccess } from "~/utils"; type OnChainTx = { transaction: { @@ -126,9 +122,7 @@ export default function Receive() { const [lspFee, setLspFee] = createSignal(0n); // Tagging stuff - const [selectedValues, setSelectedValues] = createSignal( - [] - ); + const [selectedValues, setSelectedValues] = createSignal([]); // The data we get after a payment const [paymentTx, setPaymentTx] = createSignal(); @@ -215,7 +209,7 @@ export default function Receive() { } async function processContacts( - contacts: Partial[] + contacts: Partial[] ): Promise { if (contacts.length) { const first = contacts![0]; diff --git a/src/routes/Send.tsx b/src/routes/Send.tsx index c43d742a..1b43e962 100644 --- a/src/routes/Send.tsx +++ b/src/routes/Send.tsx @@ -1,6 +1,6 @@ import { Clipboard } from "@capacitor/clipboard"; import { Capacitor } from "@capacitor/core"; -import { Contact, MutinyInvoice } from "@mutinywallet/mutiny-wasm"; +import { Contact, MutinyInvoice, TagItem } from "@mutinywallet/mutiny-wasm"; import { createEffect, createMemo, @@ -48,7 +48,7 @@ import { import { useI18n } from "~/i18n/context"; import { ParsedParams } from "~/logic/waila"; import { useMegaStore } from "~/state/megaStore"; -import { eify, MutinyTagItem, vibrateSuccess } from "~/utils"; +import { eify, vibrateSuccess } from "~/utils"; export type SendSource = "lightning" | "onchain"; @@ -250,7 +250,7 @@ export default function Send() { // Tagging stuff const [selectedContacts, setSelectedContacts] = createSignal< - Partial[] + Partial[] >([]); // Details Modal @@ -476,7 +476,7 @@ export default function Send() { } async function processContacts( - contacts: Partial[] + contacts: Partial[] ): Promise { if (contacts.length) { const first = contacts![0]; diff --git a/src/routes/settings/Connections.tsx b/src/routes/settings/Connections.tsx index 394f429b..ea506cab 100644 --- a/src/routes/settings/Connections.tsx +++ b/src/routes/settings/Connections.tsx @@ -219,8 +219,8 @@ function Nwc() { async function fetchNwcProfiles() { try { - const profiles: NwcProfile[] = - await state.mutiny_wallet?.get_nwc_profiles(); + const profiles = await state.mutiny_wallet?.get_nwc_profiles(); + if (!profiles) return []; return profiles; } catch (e) { diff --git a/src/routes/settings/Gift.tsx b/src/routes/settings/Gift.tsx index 510749c9..bf11ce9f 100644 --- a/src/routes/settings/Gift.tsx +++ b/src/routes/settings/Gift.tsx @@ -104,8 +104,8 @@ function ExistingGifts() { const [giftNWCProfiles, { refetch }] = createResource(async () => { try { - const profiles: NwcProfile[] = - await state.mutiny_wallet?.get_nwc_profiles(); + const profiles = await state.mutiny_wallet?.get_nwc_profiles(); + if (!profiles) return []; const filteredForGifts = profiles.filter((p) => p.tag === "Gift"); diff --git a/src/state/megaStore.tsx b/src/state/megaStore.tsx index 20822b13..5e13c94d 100644 --- a/src/state/megaStore.tsx +++ b/src/state/megaStore.tsx @@ -1,7 +1,11 @@ /* @refresh reload */ // Inspired by https://github.com/solidjs/solid-realworld/blob/main/src/store/index.js -import { MutinyBalance, MutinyWallet } from "@mutinywallet/mutiny-wasm"; +import { + MutinyBalance, + MutinyWallet, + TagItem +} from "@mutinywallet/mutiny-wasm"; import { createContext, onCleanup, @@ -12,12 +16,6 @@ import { import { createStore } from "solid-js/store"; import { useNavigate, useSearchParams } from "solid-start"; -import { - BTC_INDEX, - Currency, - FIAT_OPTIONS, - USD_INDEX -} from "~/components/ChooseCurrency"; import { checkBrowserCompatibility } from "~/logic/browserCompatibility"; import { doubleInitDefense, @@ -27,7 +25,13 @@ import { setupMutinyWallet } from "~/logic/mutinyWalletSetup"; import { ParsedParams, toParsedParams } from "~/logic/waila"; -import { eify, MutinyTagItem, subscriptionValid } from "~/utils"; +import { + BTC_OPTION, + Currency, + eify, + subscriptionValid, + USD_OPTION +} from "~/utils"; const MegaStoreContext = createContext(); @@ -69,7 +73,7 @@ export type MegaStore = [ setScanResult(scan_result: ParsedParams | undefined): void; sync(): Promise; setHasBackedUp(): void; - listTags(): Promise; + listTags(): Promise; checkForSubscription(justPaid?: boolean): Promise; fetchPrice(fiat: Currency): Promise; saveFiat(fiat: Currency): void; @@ -97,7 +101,7 @@ export const Provider: ParentComponent = (props) => { price: 0, fiat: localStorage.getItem("fiat_currency") ? (JSON.parse(localStorage.getItem("fiat_currency")!) as Currency) - : FIAT_OPTIONS[USD_INDEX], + : USD_OPTION, has_backed_up: localStorage.getItem("has_backed_up") === "true", balance: undefined as MutinyBalance | undefined, last_sync: undefined as number | undefined, @@ -148,6 +152,16 @@ export const Provider: ParentComponent = (props) => { throw state.setup_error; } + // If there's already a mutiny wallet in state abort! + if (state.mutiny_wallet) { + setState({ + setup_error: new Error( + "Existing Mutiny Wallet already running, aborting setup" + ) + }); + return; + } + setState({ wallet_loading: true, load_stage: "checking_double_init" @@ -272,7 +286,7 @@ export const Provider: ParentComponent = (props) => { balance: newBalance, last_sync: Date.now(), price: 1, - fiat: FIAT_OPTIONS[BTC_INDEX] + fiat: BTC_OPTION }); } } @@ -306,9 +320,9 @@ export const Provider: ParentComponent = (props) => { localStorage.setItem("has_backed_up", "true"); setState({ has_backed_up: true }); }, - async listTags(): Promise { + async listTags(): Promise { try { - return state.mutiny_wallet?.get_tag_items() as MutinyTagItem[]; + return state.mutiny_wallet?.get_tag_items(); } catch (e) { console.error(e); return []; diff --git a/src/utils/conversions.ts b/src/utils/conversions.ts index 6ec7c35a..7830fbff 100644 --- a/src/utils/conversions.ts +++ b/src/utils/conversions.ts @@ -1,6 +1,6 @@ import { MutinyWallet } from "@mutinywallet/mutiny-wasm"; -import { Currency } from "~/components/ChooseCurrency"; +import { Currency } from "./currencies"; /** satsToFiat * returns a toLocaleString() based on the bitcoin price in the chosen currency diff --git a/src/utils/currencies.ts b/src/utils/currencies.ts new file mode 100644 index 00000000..6d17c02e --- /dev/null +++ b/src/utils/currencies.ts @@ -0,0 +1,296 @@ +export interface Currency { + value: string; + label: string; + hasSymbol?: string; + maxFractionalDigits: number; +} + +/** + * BTC_USD_OPTIONS is an array of BTC and USD currencies + * These are separated from the rest of the list for ease of access by the user and necessary access in the megaStore + */ + +export const BTC_OPTION: Currency = { + label: "Bitcoin BTC", + value: "BTC", + hasSymbol: "₿", + maxFractionalDigits: 8 +}; + +export const USD_OPTION: Currency = { + label: "United States Dollar USD", + value: "USD", + hasSymbol: "$", + maxFractionalDigits: 2 +}; + +/** + * FIAT_OPTIONS is an array of all available currencies on the coingecko api https://api.coingecko.com/api/v3/simple/supported_vs_currencies + * @Currency + * @param {string} label - should be in the format {Name} {ISO code} + * @param {string} values - are uppercase ISO 4217 currency code + * @param {string?} hasSymbol - if the currency has a symbol it should be represented as a string + * @param {number} maxFractionalDigits - the standard fractional units used by the currency should be set with maxFractionalDigits + * + * Bitcoin is represented as: + * { + * label: "bitcoin BTC", + * value: "BTC", + * hasSymbol: "₿", + * maxFractionalDigits: 8 + * } + */ + +export const FIAT_OPTIONS: Currency[] = [ + { + label: "United Arab Emirates Dirham AED", + value: "AED", + maxFractionalDigits: 2 + }, + { + label: "Argentine Peso ARS", + value: "ARS", + hasSymbol: "$", + maxFractionalDigits: 2 + }, + { + label: "Australian Dollar AUD", + value: "AUD", + hasSymbol: "$", + maxFractionalDigits: 2 + }, + { + label: "Bangladeshi Taka BDT", + value: "BDT", + hasSymbol: "৳", + maxFractionalDigits: 2 + }, + { + label: "Bahraini Dinar BHD", + value: "BHD", + hasSymbol: "BD", + maxFractionalDigits: 3 + }, + { + label: "Bermuda Dollar BMD", + value: "BMD", + hasSymbol: "$", + maxFractionalDigits: 2 + }, + { + label: "Brazilian Real BRL", + value: "BRL", + hasSymbol: "R$", + maxFractionalDigits: 2 + }, + { + label: "Canadian Dollar CAD", + value: "CAD", + hasSymbol: "$", + maxFractionalDigits: 2 + }, + { label: "Swiss Franc CHF", value: "CHF", maxFractionalDigits: 2 }, + { + label: "Chilean Peso CLP", + value: "CLP", + hasSymbol: "$", + maxFractionalDigits: 0 + }, + { + label: "Chinese Yuan CNY", + value: "CNY", + hasSymbol: "¥", + maxFractionalDigits: 2 + }, + { + label: "Czech Koruna CZK", + value: "CZK", + hasSymbol: "Kč", + maxFractionalDigits: 2 + }, + { + label: "Danish Krone DKK", + value: "DKK", + hasSymbol: "kr", + maxFractionalDigits: 2 + }, + { + label: "Euro EUR", + value: "EUR", + hasSymbol: "€", + maxFractionalDigits: 2 + }, + { + label: "British Pound GBP", + value: "GBP", + hasSymbol: "₤", + maxFractionalDigits: 2 + }, + { + label: "Hong Kong Dollar HKD", + value: "HKD", + hasSymbol: "$", + maxFractionalDigits: 2 + }, + { + label: "Hungarian Forint HUF", + value: "HUF", + hasSymbol: "Ft", + maxFractionalDigits: 2 + }, + { + label: "Israeli New Shekel ILS", + value: "ILS", + hasSymbol: "₪", + maxFractionalDigits: 2 + }, + { + label: "Indian Rupee INR", + value: "INR", + hasSymbol: "₹", + maxFractionalDigits: 2 + }, + { + label: "Japanese Yen JPY", + value: "JPY", + hasSymbol: "¥", + maxFractionalDigits: 0 + }, + { + label: "Korean Won KRW", + value: "KRW", + hasSymbol: "₩", + maxFractionalDigits: 0 + }, + { + label: "Kuwaiti Dinar KWD", + value: "KWD", + maxFractionalDigits: 3 + }, + { + label: "Sri Lankan Rupee LKR", + value: "LKR", + hasSymbol: "Rs", + maxFractionalDigits: 2 + }, + { + label: "Myanmar Kyat MMK", + value: "MMK", + hasSymbol: "Ks", + maxFractionalDigits: 2 + }, + { + label: "Mexican Peso MXN", + value: "MXN", + hasSymbol: "$", + maxFractionalDigits: 2 + }, + { + label: "Malaysian Ringgit MYR", + value: "MYR", + hasSymbol: "RM", + maxFractionalDigits: 2 + }, + { + label: "Norwegian Krone NOK", + value: "NOK", + hasSymbol: "kr", + maxFractionalDigits: 2 + }, + { + label: "Nigerian Naira NGN", + value: "NGN", + hasSymbol: "₦", + maxFractionalDigits: 2 + }, + { + label: "New Zealand Dollar NZD", + value: "NZD", + hasSymbol: "$", + maxFractionalDigits: 2 + }, + { + label: "Philippine Peso PHP", + value: "PHP", + hasSymbol: "₱", + maxFractionalDigits: 2 + }, + { + label: "Pakistani Rupee PKR", + value: "PKR", + hasSymbol: "₨", + maxFractionalDigits: 2 + }, + { + label: "Polish Złoty PLN", + value: "PLN", + hasSymbol: "zł", + maxFractionalDigits: 2 + }, + { + label: "Russian Ruble RUB", + value: "RUB", + hasSymbol: "₽", + maxFractionalDigits: 2 + }, + { + label: "Saudi Riyal SAR", + value: "SAR", + hasSymbol: "SR", + maxFractionalDigits: 2 + }, + { + label: "Swedish Krona SEK", + value: "SEK", + hasSymbol: "kr", + maxFractionalDigits: 2 + }, + { + label: "Singapore Dollar SGD", + value: "SGD", + hasSymbol: "$", + maxFractionalDigits: 2 + }, + { + label: "Thai Baht THB", + value: "THB", + hasSymbol: "฿", + maxFractionalDigits: 2 + }, + { + label: "Turkish Lira TRY", + value: "TRY", + hasSymbol: "₺", + maxFractionalDigits: 2 + }, + { + label: "New Taiwan Dollar TWD", + value: "TWD", + hasSymbol: "NT$", + maxFractionalDigits: 2 + }, + { + label: "Ukrainian Hryvnia UAH", + value: "UAH", + hasSymbol: "₴", + maxFractionalDigits: 2 + }, + { + label: "Venezuelan Bolívar VEF", + value: "VEF", + hasSymbol: "Bs", + maxFractionalDigits: 2 + }, + { + label: "Vietnamese Dong VND", + value: "VND", + hasSymbol: "₫", + maxFractionalDigits: 0 + }, + { + label: "South African Rand ZAR", + value: "ZAR", + hasSymbol: "R", + maxFractionalDigits: 2 + } +]; diff --git a/src/utils/index.ts b/src/utils/index.ts index daf031e5..3da0de7a 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -18,3 +18,4 @@ export * from "./fetchZaps"; export * from "./vibrate"; export * from "./openLinkProgrammatically"; export * from "./nostr"; +export * from "./currencies"; diff --git a/src/utils/tags.ts b/src/utils/tags.ts index 0f944d94..1ca7adaa 100644 --- a/src/utils/tags.ts +++ b/src/utils/tags.ts @@ -1,47 +1,12 @@ import { TagItem } from "@mutinywallet/mutiny-wasm"; -export type MutinyTagItem = { - id: string; - kind: "Label" | "Contact"; - name: string; - last_used_time: bigint; - npub?: string; - ln_address?: string; - lnurl?: string; -}; - -export const UNKNOWN_TAG: MutinyTagItem = { - id: "Unknown", - kind: "Label", - name: "Unknown", - last_used_time: 0n -}; - -export function tagsToIds(tags?: MutinyTagItem[]): string[] { +export function tagsToIds(tags?: TagItem[]): string[] { if (!tags) { return []; } return tags.filter((tag) => tag.id !== "Unknown").map((tag) => tag.id); } -export function tagToMutinyTag(tag: TagItem): MutinyTagItem { - let kind: MutinyTagItem["kind"]; - - switch (tag.kind) { - case 0: { - kind = "Label"; - break; - } - case 1: - default: { - kind = "Contact"; - break; - } - } - - return { ...tag, kind }; -} - -export function sortByLastUsed(a: MutinyTagItem, b: MutinyTagItem) { +export function sortByLastUsed(a: TagItem, b: TagItem) { return Number(b.last_used_time - a.last_used_time); }