diff options
| author | Adam Soltys <[email protected]> | 2022-09-29 15:05:13 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2022-09-29 15:05:13 -0700 |
| commit | 64911388f80d52d6dd53d707457a37fe386c194e (patch) | |
| tree | b96d7c1b85857459d6a91673e5ac6598eed7bd98 /src | |
| parent | 9e07792fcdd0ba0b7c07a20fe83db54597388e26 (diff) | |
| download | coinos-ui-64911388f80d52d6dd53d707457a37fe386c194e.tar.gz coinos-ui-64911388f80d52d6dd53d707457a37fe386c194e.zip | |
address parsing
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/Numpad.svelte | 153 | ||||
| -rw-r--r-- | src/components/RegisterOrLogin.svelte | 6 | ||||
| -rw-r--r-- | src/components/index.js | 1 | ||||
| -rw-r--r-- | src/lib/parse.js | 296 | ||||
| -rw-r--r-- | src/lib/utils.js | 4 | ||||
| -rw-r--r-- | src/routes/logout/+page.server.js | 25 | ||||
| -rw-r--r-- | src/routes/scan/+page.svelte | 7 | ||||
| -rw-r--r-- | src/routes/send/+page.svelte | 69 | ||||
| -rw-r--r-- | src/routes/send/bitcoin/[address]/+page.server.js | 8 | ||||
| -rw-r--r-- | src/routes/send/bitcoin/[address]/+page.svelte | 39 | ||||
| -rw-r--r-- | src/routes/send/lightning/+page.svelte | 0 | ||||
| -rw-r--r-- | src/routes/send/lnurl/+page.svelte | 0 |
12 files changed, 287 insertions, 321 deletions
diff --git a/src/components/Numpad.svelte b/src/components/Numpad.svelte new file mode 100644 index 00000000..f00f33c1 --- /dev/null +++ b/src/components/Numpad.svelte @@ -0,0 +1,153 @@ +<script> + import { Icon } from "$comp"; + import { selectedRate, user } from '$lib/store'; + import { f, s, post, warning, sats } from '$lib/utils'; + import { t } from '$lib/translations'; + + export let amount, currency; + + let useFiat = true; + let amountFiat = 0; + let amountSats = 0; + let loading = false; + + let symbol = + { + USD: '$', + CAD: '$', + AUD: '$', + NZD: '$', + MXN: '$', + BRL: '$', + HKD: '$', + TWD: '$', + JPY: '¥', + CNY: '¥', + GBP: '£', + EUR: '€', + KRW: '₩' + }[currency] || ''; + + $: amount = parseInt(useFiat ? Math.round(amountFiat / ($selectedRate / sats)) : amountSats); + + $: amountSatsFormatted = s(amountSats); + + $: amountFiatConverted = f(amountSats * ($selectedRate / sats), $user.currency); + + $: amountSatsConverted = s(amountFiat / ($selectedRate / sats)); + + const numPad = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '0', '<']; + + const handleInput = (value) => { + if (useFiat) { + if (amountFiat === 0 && value !== '.' && value !== '<' && value !== '0') { + amountFiat = value; + } else if ((amountFiat === 0 || amountFiat === '0') && value === '0') { + return; + } else if (amountFiat === 0 && value === '.') { + amountFiat = '0.'; + } else if (amountFiat !== 0 && amountFiat.includes('.') && value === '.') { + return; + } else if (value === '<') { + if (amountFiat !== 0) { + amountFiat = amountFiat.slice(0, amountFiat.length - 1); + if (amountFiat.length === 0) { + amountFiat = 0; + amountSats = 0; + } + } + } else if (value !== '.' && value !== '<' && parseInt(amountFiat + value) > $selectedRate) { + warning($t('user.receive.lessThan1BTCWarning')); + } else if (amountFiat !== 0 && amountFiat.match(/\.../)) { + return; + } else { + amountFiat = amountFiat + value; + } + } else { + if (value === '.') { + return; + } else if (!amountSats && value !== '<' && value !== '0') { + amountSats = parseInt(value); + } else if (value === '<') { + if (amountSats !== 0) { + amountSats = Math.floor(amountSats / 10); + if (amountSats.length === 0) { + amountSats = 0; + amountFiat = 0; + } + } + } else if (value !== '<' && parseInt(amountSats + value) > sats) { + warning($t('user.receive.lessThan1BTCWarning')); + } else { + amountSats = parseInt(amountSats + value); + } + } + }; +</script> + +<div class="flex justify-center items-center mb-3 px-3"> + {#if $selectedRate} + <div class="space-y-5"> + <!-- amounts --> + <div class="text-center"> + <div class="text-5xl md:text-6xl font-semibold tracking-widest mb-1"> + {useFiat ? `${symbol}${amountFiat}` : amountSatsFormatted}<span + class="tracking-normal text-base font-normal">{useFiat ? $user.currency : 'SAT'}</span + > + </div> + <span class="text-secondary mr-1" + >{useFiat ? `${amountSatsConverted} SAT` : amountFiatConverted}</span + > + <button + on:click={() => { + if (useFiat) { + amountSats = parseInt((amountFiat / ($selectedRate / sats)).toFixed(0)); + } else { + amountFiat = + (amountSats * ($selectedRate / sats)).toFixed(2) > 0.0 + ? (amountSats * ($selectedRate / sats)).toFixed(2) + : 0; + } + useFiat = !useFiat; + }}><Icon icon="swap" style="inline hover:opacity-80" /></button + > + </div> + + <!-- numpad --> + <div class="grid grid-cols-3 gap-2 w-full md:w-[300px] mx-auto"> + {#each numPad as value} + {#if value === '<'} + <button + class="bg-primary rounded-xl py-4 px-8 font-semibold active:bg-black active:text-white flex justify-center items-center hover:opacity-80" + on:click={() => handleInput(value)} + > + <svg + class="w-5" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M15 19L8 12L15 5" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + /> + </svg> + </button> + {:else} + <button + class="bg-primary rounded-xl py-4 px-8 font-semibold active:bg-black active:text-white hover:opacity-80" + on:click={() => handleInput(value)}>{value}</button + > + {/if} + {/each} + </div> + </div> + {:else} + <div class="w-full md:w-[300px] h-[475px] md:h-[485px] animate-pulse bg-gray-400 rounded-xl" /> + {/if} +</div> diff --git a/src/components/RegisterOrLogin.svelte b/src/components/RegisterOrLogin.svelte index fa805a1d..8f10fadd 100644 --- a/src/components/RegisterOrLogin.svelte +++ b/src/components/RegisterOrLogin.svelte @@ -3,7 +3,7 @@ import { page } from '$app/stores'; import { tick } from 'svelte'; import { Icon } from '$comp'; - import { post, failure } from '$lib/utils'; + import { focus, post, failure } from '$lib/utils'; import { redirect, user, token } from '$lib/store'; import { auth } from '$lib/socket'; import { goto } from '$app/navigation'; @@ -25,10 +25,6 @@ // TODO: move to server side $: if (browser && $user) goto(`/${$user.username}/receive`); - - let focus = (el) => setTimeout(() => el.focus()); - - console.log('REDIRECT STORE', $redirect); </script> <div class="pt-10"> diff --git a/src/components/index.js b/src/components/index.js index ba0f6516..48d5ecd0 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -1,3 +1,4 @@ +export { default as Numpad } from './Numpad.svelte'; export { default as Spinner } from './Spinner.svelte'; export { default as Check } from './Check.svelte'; export { default as Heart } from './Heart.svelte'; diff --git a/src/lib/parse.js b/src/lib/parse.js index 2a827956..2abc2cba 100644 --- a/src/lib/parse.js +++ b/src/lib/parse.js @@ -1,271 +1,39 @@ -export default (text) => { +// import bolt11 from 'bolt11'; +import validate from 'bitcoin-address-validation'; +import bip21 from 'bip21'; +import { get } from '$lib/utils'; +import { goto } from "$app/navigation"; + +let debounce; +export default async (text) => { if (!text) return; + console.log("parsing") - if (text.includes('@') && text.includes('.')) { - let [name, domain] = text.split('@'); - try { - clearTimeout(debounce); - await new Promise((r) => (debounce = setTimeout(r, 1500))); - ({ data: text } = await Vue.axios.get( - `/encode?domain=${domain}&name=${name}` - )); - } catch (e) {} + // lightning + if (text.startsWith('ln')) { + try { + console.log('lightning'); + } catch (e) {} } - try { - if (text.toLowerCase().slice(0, 10) === 'lightning:') - text = text.slice(10); - let payreq = text.toLowerCase(); - payment.payreq = payreq; - payment.payobj = bolt11.decode(payment.payreq); - let { - network: { bech32 }, - } = payment.payobj; - - if (!networks.includes('lightning')) - return commit('error', 'Lightning not supported in this account'); - - if (bech32 !== process.env.VUE_APP_NETWORK_BECH32) { - dispatch('clearPayment'); - commit( - 'error', - `Wrong network prefix '${bech32}' instead of '${process.env.VUE_APP_NETWORK_BECH32}'` - ); - throw new Error('Wrong network'); - } - - if (user.account.ticker !== 'BTC') - await dispatch( - 'shiftAccount', - user.accounts.find((a) => a.asset === BTC).id - ); - - let { tags, satoshis, millisatoshis } = payment.payobj; - let description = tags.find((t) => t.tagName === 'description'); - if (description) payment.memo = description.data; - payment.amount = millisatoshis - ? Math.round(millisatoshis / 1000) - : satoshis; - payment.fiatAmount = ((payment.amount * getters.rate) / SATS).toFixed( - 2 - ); - payment.network = 'lightning'; + // bitcoin + if (validate(text)) { + console.log('bitcoin'); + goto(`/send/bitcoin/${text}`); + } - await Vue.axios.post('/lightning/query', { payreq }); - if (!getters.payment.recipient) - go({ name: 'send', params: { keep: true } }); + // ln address + if (text.includes('@') && text.includes('.')) { + console.log("ln address") + let [name, domain] = text.split('@'); + try { + clearTimeout(debounce); + await new Promise((r) => (debounce = setTimeout(r, 1500))); + let lnurl = await get(`/encode?domain=${domain}&name=${name}`); + console.log('lnurl', lnurl); + } catch (e) {} + } - return; - } catch (e) { - if (e.response) commit('error', e.response.data); - } - // - // let url; - // try { - // if (nodes.includes('bitcoin')) { - // url = bip21.decode(text); - // if (!networks.includes('bitcoin')) - // return commit('error', 'Bitcoin not supported in this account'); - // payment.network = 'bitcoin'; - // url.options.asset = BTC; - // } - // } catch (e) { - // if (nodes.includes('liquid')) { - // try { - // url = bip21.decode(text, 'liquidnetwork'); - // if (!networks.includes('liquid')) - // return commit('error', 'Liquid not supported in this account'); - // payment.network = 'liquid'; - // } catch (e) { - // /**/ - // } - // } - // } - // - // if (url) { - // let { amount, asset, assetid, message } = url.options; - // if (assetid) asset = assetid; - // if (!asset) asset = BTC; - // let account = user.accounts.find((a) => a.asset === asset); - // if (account) { - // if (account.asset !== user.account.asset) - // await dispatch('shiftAccount', account.id); - // } else return commit('error', 'Unrecognized asset'); - // - // payment.address = url.address; - // - // if (message) payment.memo = message; - // - // if (amount) { - // payment.amount = parseInt((amount * SATS).toFixed(0)); - // payment.fiatAmount = ((payment.amount * getters.rate) / SATS).toFixed( - // 2 - // ); - // } - // - // await dispatch('isInternal'); - // await dispatch('estimateFee'); - // go({ name: 'send', params: { keep: true } }); - // return; - // } - // - // if (nodes.includes('bitcoin') && validate(text)) { - // if (!networks.includes('bitcoin')) - // return commit('error', 'Bitcoin not supported in this account'); - // payment.address = text; - // payment.network = 'bitcoin'; - // await dispatch('isInternal'); - // go({ name: 'send', params: { keep: true } }); - // return; - // } - // - // // Liquid - // if (nodes.includes('liquid') && isLiquid(text)) { - // if (!networks.includes('liquid')) - // return commit('error', 'Liquid not supported in this account'); - // payment.address = text; - // payment.network = 'liquid'; - // await dispatch('isInternal'); - // go({ name: 'send', params: { keep: true } }); - // return; - // } - // - // if (text.startsWith('6P')) { - // commit('text', text); - // return go('/decrypt'); - // } - // - // try { - // let ecpair = ECPair.fromWIF(text, { - // ...this._vm.$network, - // confidentialPrefix: 1, - // assetHash: '', - // }); - // commit('ecpair', ecpair); - // go('/sweep'); - // } catch (e) { - // /* */ - // } - // - // if (text.toLowerCase().startsWith('lnurl:')) { - // try { - // const { data: lnurl } = await Vue.axios.get(`/url?code=${text}`); - // await dispatch('handleScan', lnurl); - // return; - // } catch (e) { - // commit('error', e.response ? e.response.data : e.message); - // } - // } - // - // if (text.toLowerCase().includes('lnurl')) { - // commit('loading', true); - // let txt = text.toLowerCase().match(/lnurl[a-z0-9]+/); - // - // try { - // const { data: params } = await Vue.axios.get(`/decode?text=${txt}`); - // if (params.status === 'ERROR') { - // let { reason } = params; - // try { - // ({ reason } = JSON.parse(reason.replace(/.*{/, '{'))); - // } catch { - // /**/ - // } - // - // commit('loading', false); - // return commit('error', reason); - // } - // - // let { seed } = getters; - // - // switch (params.tag) { - // case 'channelRequest': - // await dispatch('openChannel', params); - // break; - // case 'login': - // if (!seed) ({ seed } = await dispatch('passwordPrompt')); - // try { - // const key = linkingKey(params.domain, seed); - // const signedMessage = secp256k1.ecdsaSign( - // hexToUint8Array(params.k1), - // key.privateKey - // ); - // const signedMessageDER = secp256k1.signatureExport( - // signedMessage.signature - // ); - // const linkingKeyPub = secp256k1.publicKeyCreate( - // key.privateKey, - // true - // ); - // const sig = bytesToHexString(signedMessageDER); - // - // const response = await Vue.axios.post('/login', { - // params, - // sig, - // key: bytesToHexString(linkingKeyPub), - // }); - // - // if (response.data.status === 'OK') { - // commit('snack', 'Login success'); - // } else { - // commit('error', response.status); - // } - // - // go('/home'); - // } catch (e) { - // commit('error', e.response ? e.response.data : e.message); - // } - // break; - // - // case 'withdrawRequest': - // commit('lnurl', params); - // go('/withdraw'); - // break; - // case 'payRequest': - // commit('lnurl', params); - // go('/pay'); - // break; - // } - // } catch (e) { - // commit('error', e.response ? e.response.data : e.message); - // } - // - // commit('loading', false); - // } - // - // if (isUuid(text)) { - // try { - // const { data: payment } = await Vue.axios.post('/checkRedeemCode', { - // redeemcode: text, - // }); - // - // if (payment) { - // window.location.href = `${window.location.protocol}//${window.location.host}/redeem/${text}`; - // return; - // } - // } catch (e) { - // commit('error', e.response ? e.response.data : e.message); - // } - // - // try { - // const { data: invoice } = await Vue.axios.get( - // `/invoice?uuid=${text}` - // ); - // await dispatch('handleScan', invoice.text); - // return; - // } catch (e) { - // commit('error', e.response ? e.response.data : e.message); - // } - // } - // - // if ( - // text.startsWith( - // `${window.location.protocol}//${window.location.host}/redeem` - // ) - // ) { - // window.location.href = text; - // } - // - // commit('stopScanning', true); - // - // return 'no'; + // user + console.log("username") }; diff --git a/src/lib/utils.js b/src/lib/utils.js index 61e2ddac..514e3adb 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -146,6 +146,8 @@ export const toggleFullscreen = (el) => export const sats = 100000000; -export const goBack = () => { +export const back = () => { browser && history.go(-1); }; + +export const focus = (el) => setTimeout(() => el.focus()); diff --git a/src/routes/logout/+page.server.js b/src/routes/logout/+page.server.js index 3eae005f..726094fd 100644 --- a/src/routes/logout/+page.server.js +++ b/src/routes/logout/+page.server.js @@ -7,19 +7,18 @@ const opts = { path: '/' }; -export async function load({ setHeaders }) { - setHeaders({ - 'set-cookie': [ - cookie.serialize('token', '', { - ...opts, - expires: new Date(0) - }), - cookie.serialize('refresh_token', '', { - ...opts, - expires: new Date(0) - }) - ] - }); +export async function load({ cookies }) { + console.log('COOKIES', cookies); + cookies.set('set-cookie', [ + cookies.serialize('token', '', { + ...opts, + expires: new Date(0) + }), + cookies.serialize('refresh_token', '', { + ...opts, + expires: new Date(0) + }) + ]); throw redirect(302, '/logout/done'); } diff --git a/src/routes/scan/+page.svelte b/src/routes/scan/+page.svelte index 5c8c09f6..9b1072e0 100644 --- a/src/routes/scan/+page.svelte +++ b/src/routes/scan/+page.svelte @@ -2,11 +2,12 @@ import { Icon } from "$comp"; import QrScanner from 'qr-scanner'; import { onMount } from 'svelte'; - import { goBack } from "$lib/utils"; + import { back } from "$lib/utils"; + import { goto } from "$app/navigation"; let scanner, vid; onMount(() => { - scanner = new QrScanner(vid, console.log); + scanner = new QrScanner(vid, (text) => goto(`/send/${text}`)); scanner.start(); }); @@ -14,7 +15,7 @@ <button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" - on:click={goBack} + on:click={back} > <Icon icon="arrow-left" style="w-10" /> </button> diff --git a/src/routes/send/+page.svelte b/src/routes/send/+page.svelte index 777f9766..2162d76d 100644 --- a/src/routes/send/+page.svelte +++ b/src/routes/send/+page.svelte @@ -1,40 +1,32 @@ <script> + import { browser } from '$app/environment'; import { t } from '$lib/translations'; import { AppHeader, Icon, Spinner } from '$comp'; + import { back, focus } from '$lib/utils'; + import parse from '$lib/parse'; - let payreq = '', - loading, - payreqField; + let text, loading; - let withdraw = async () => { - loading = true; - try { - await post('/withdraw', { payreq }); - loading = false; - } catch (e) { - failure(e.message); - loading = false; - } + let paste = async () => { + text = await navigator.clipboard.readText(); }; </script> -<AppHeader avatarPosition="left-[calc(50vw-64px)] lg:left-[calc(15vw-64px)]" /> +<button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" on:click={back}> + <Icon icon="arrow-left" style="w-10" /> +</button> <div class="container px-4 mt-20 max-w-xl mx-auto"> <div> - <!-- found missing translation --> - <label for="invoice" class="font-bold mb-1 block" - >Destination <small class="text-secondary">Address, Invoice, User, Email or Phone</small - ></label - > + <label for="invoice" class="font-bold mb-1 block">Destination</label> - <div class="relative"> + <div class="relative mb-8"> <input - bind:this={payreqField} + placeholder="Address, Invoice, Username" type="text" - name="payreq" + name="text" required - bind:value={payreq} + bind:value={text} class="block rounded-2xl p-3 w-full bg-primary" /> @@ -43,20 +35,27 @@ <Icon icon="scan" style="mr-2 w-6 my-auto" /> </div> </a> + + {#if browser && navigator.clipboard} + <div class="flex absolute h-[48px] right-10 top-0 px-2 mt-[1px]" on:click={paste}> + <Icon icon="paste" style="mr-2 w-6 my-auto" /> + </div> + {/if} </div> </div> - <button - disabled={!payreq} - class="{!payreq - ? 'opacity-50' - : 'opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold w-full mt-2 bg-black text-white" - on:click={withdraw} - > - {#if loading} - <Spinner /> - {:else} - {$t('user.dashboard.go')} - {/if} - </button> + <div class="flex w-full"> + <button + class="{!text + ? 'opacity-50' + : 'opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" + on:click={() => parse(text)} + > + {#if loading} + <Spinner /> + {:else} + {$t('user.dashboard.go')} + {/if} + </button> + </div> </div> diff --git a/src/routes/send/bitcoin/[address]/+page.server.js b/src/routes/send/bitcoin/[address]/+page.server.js new file mode 100644 index 00000000..8cdf7276 --- /dev/null +++ b/src/routes/send/bitcoin/[address]/+page.server.js @@ -0,0 +1,8 @@ +import { redirect } from "@sveltejs/kit"; +export const actions = { + default: async ({ request }) => { + let form = Object.fromEntries(await request.formData()); + console.log('send bitcoin', form); + throw redirect(307, '/'); + } +}; diff --git a/src/routes/send/bitcoin/[address]/+page.svelte b/src/routes/send/bitcoin/[address]/+page.svelte new file mode 100644 index 00000000..fd635fd5 --- /dev/null +++ b/src/routes/send/bitcoin/[address]/+page.svelte @@ -0,0 +1,39 @@ +<script> + import { AppHeader, Icon, Numpad } from '$comp'; + import { page } from '$app/stores'; + import { back } from '$lib/utils'; + export let data; + + let { address } = $page.params; + let { currency } = data.user; + let amount, loading; +</script> + +<button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" on:click={back}> + <Icon icon="arrow-left" style="w-10" /> +</button> + +<div class="container px-4 mt-20 max-w-xl mx-auto"> + <div class="text-center mb-8"> + <h1 class="text-3xl md:text-4xl font-semibold mb-2">Send to</h1> + <p class="text-lg text-secondary">{address}</p> + </div> + <Numpad bind:amount {currency} /> + + <form method="POST"> + <input name="address" value={address} type="hidden" /> + <input name="amount" value={amount} type="hidden" /> + <div class="flex w-full"> + <button + type="submit" + class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" + > + {#if loading} + <Spinner /> + {:else} + Send + {/if} + </button> + </div> + </form> +</div> diff --git a/src/routes/send/lightning/+page.svelte b/src/routes/send/lightning/+page.svelte new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/routes/send/lightning/+page.svelte diff --git a/src/routes/send/lnurl/+page.svelte b/src/routes/send/lnurl/+page.svelte new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/routes/send/lnurl/+page.svelte |
