From 90aea0e7a81a60b3215a9dffd7f08d61b1613ee0 Mon Sep 17 00:00:00 2001 From: chedieck Date: Fri, 21 Jun 2024 15:30:49 -0300 Subject: [PATCH 1/4] feat: util for getting coins --- react/lib/util/sideshift.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 react/lib/util/sideshift.ts diff --git a/react/lib/util/sideshift.ts b/react/lib/util/sideshift.ts new file mode 100644 index 00000000..278f7278 --- /dev/null +++ b/react/lib/util/sideshift.ts @@ -0,0 +1,24 @@ +interface TokenDetails { + network: { + contractAddress: string; + }; + decimals: number; + depositOffline?: string[] | boolean; + settleOffline?: string[] | boolean; +} + +interface Coin { + networks: string[]; + coin: string; + name: string; + hasMemo: boolean; + fixedOnly: string[] | boolean; + variableOnly: string[] | boolean; + tokenDetails: TokenDetails[]; +} + +export async function getCoins(): Promise { + const res = await fetch('https://sideshift.ai/api/v2/coins') + const data = await res.json() + return data as Coin[] +} From 13aed4b6204fbfb7de3153428dd97e4760e864a5 Mon Sep 17 00:00:00 2001 From: chedieck Date: Mon, 1 Jul 2024 16:15:33 -0300 Subject: [PATCH 2/4] feat: widget supports selecting alt currency --- react/lib/components/Widget/Widget.tsx | 278 ++++++++++++++++++------- react/lib/util/sideshift.ts | 43 +++- 2 files changed, 236 insertions(+), 85 deletions(-) diff --git a/react/lib/components/Widget/Widget.tsx b/react/lib/components/Widget/Widget.tsx index 51bd659e..342df880 100644 --- a/react/lib/components/Widget/Widget.tsx +++ b/react/lib/components/Widget/Widget.tsx @@ -6,6 +6,8 @@ import { makeStyles, TextField, Grid, + Select, + MenuItem } from '@material-ui/core'; import React, { useEffect, useMemo, useState } from 'react'; import copy from 'copy-to-clipboard'; @@ -33,7 +35,9 @@ import { isValidCashAddress, isValidXecAddress, getCurrencyTypeFromAddress, + resolveNumber, } from '../../util'; +import { Coin, getCoins, getXecPair, Pair } from '../../util/sideshift'; type QRCodeProps = BaseQRCodeProps & { renderAs: 'svg' }; @@ -173,6 +177,14 @@ export const Widget: React.FunctionComponent = props => { const [widgetButtonText, setWidgetButtonText] = useState('Send Payment'); const [opReturn, setOpReturn] = useState(); + const [useSideshift, setUseSideshift] = useState(false); + const [selectedCoin, setSelectedCoin] = useState(); + const [coins, setCoins] = useState([]); + const [loadingPair, setLoadingPair] = useState(false); + const [coinPair, setCoinPair] = useState(); + const [pairAmount, setPairAmount] = useState(undefined); + const [pairButtonText, setPairButtonText] = useState('Send Payment'); + const theme = useTheme(props.theme, isValidXecAddress(to)); const classes = useStyles({ success, loading, theme }); @@ -200,6 +212,14 @@ export const Widget: React.FunctionComponent = props => { color, )}' stroke='%23fff' stroke-width='.6'/%3E%3Cpath d='m7.2979 14.697-2.6964-2.6966 0.89292-0.8934c0.49111-0.49137 0.90364-0.88958 0.91675-0.88491 0.013104 0.0047 0.71923 0.69866 1.5692 1.5422 0.84994 0.84354 1.6548 1.6397 1.7886 1.7692l0.24322 0.23547 7.5834-7.5832 1.8033 1.8033-9.4045 9.4045z' fill='%23fff' stroke-width='.033708'/%3E%3C/svg%3E%0A`; }, [theme]); + useEffect(() => { + (async () => { + if (useSideshift === true) { + const coins = await getCoins() + setCoins(coins) + } + })() + }, [useSideshift]) useEffect((): (() => void) | undefined => { if (!recentlyCopied) return; @@ -354,6 +374,39 @@ export const Widget: React.FunctionComponent = props => { } } }, [to, thisCurrencyObject, price, thisAmount, opReturn, hasPrice]); + const handleSideshiftButtonClick = () => { + console.log('WIP') + } + const handleCoinChange = async (e: React.ChangeEvent<{ name?: string; value: unknown }>) => { + const coinName = e.target.value as string + const selectedCoin = coins.find(c => c.coin === coinName) + setSelectedCoin(selectedCoin) + setLoadingPair(true) + const pair = await getXecPair(`${coinName}-${selectedCoin?.networks[0]}`) + setCoinPair(pair) + const bigNumber = resolveNumber(thisAmount ? +thisAmount / parseFloat(pair.rate) : 0) + if (selectedCoin !== undefined) { + const tokenDetails = selectedCoin.tokenDetails + const decimals = tokenDetails ? tokenDetails[selectedCoin.networks[0]].decimals : 12 + const amountString = bigNumber.toFixed(decimals) + setPairAmount(amountString) + setPairButtonText(`Send ${selectedCoin.coin}`) + } + setLoadingPair(false) + } + + const handlePairAmountChange = (e: React.ChangeEvent) => { + let pairAmount = e.target.value; + if (pairAmount === '') { + pairAmount = '0'; + } + setPairAmount(pairAmount) + + if (coinPair !== undefined) { + const xecAmount = +coinPair.rate * +pairAmount + updateAmount(xecAmount.toString()) + } + }; const handleButtonClick = () => { if (addressType === 'XEC') { @@ -387,6 +440,7 @@ export const Widget: React.FunctionComponent = props => { if (disabled || to === undefined) return; const address = to; let thisAmount: number | undefined; + if (convertedCurrencyObj) { thisAmount = convertedCurrencyObj.float; } else { @@ -447,11 +501,15 @@ export const Widget: React.FunctionComponent = props => { const userEdited = getCurrencyObject(+amount, currency, false); setUserEditedAmount(userEdited); + updateAmount(amount) + }; + + const updateAmount = (amount: string) => { setThisAmount(amount); if (props.setAmount) { props.setAmount(amount); } - }; + } useEffect(() => { try { @@ -573,87 +631,155 @@ export const Widget: React.FunctionComponent = props => { )} )} - - - - {qrCode} - - - - {!disabled && ( - - {copied ? 'Payment copied!' : 'Click to copy'} - - )} - - - - - - {loading && ( - - - - )} - - {editable && ( - - - - - - - - - )} + {// Sideshift region + useSideshift ? + <> - {success || ( - + { coins.length > 0 && <> +
+ { + loadingPair ? 'Loading...' : ( + (coinPair && selectedCoin) && <> + Send {selectedCoin.name} +
+ 1 {selectedCoin.coin} ~= {resolveNumber(coinPair.rate).toFixed(2)} {currency} + + ) + } + + } + {editable ? ( + + + + + + + + + ) : ( + {pairAmount} + )} + + + {setUseSideshift(false)}}>Trade with xec + + + // END: Sideshift region + : <> + + + + {qrCode} + + + + {!disabled && ( + + {copied ? 'Payment copied!' : 'Click to copy'} + + )} + + + + + + {loading && ( + + + + )} - )} + + {editable && ( + + + + + + + + + )} + + {success || ( + + + + )} + + + {setUseSideshift(true)}}>Don't have any XEC? + + + + } {foot && ( {foot} diff --git a/react/lib/util/sideshift.ts b/react/lib/util/sideshift.ts index 278f7278..bf0dc1b2 100644 --- a/react/lib/util/sideshift.ts +++ b/react/lib/util/sideshift.ts @@ -1,24 +1,49 @@ +const BASE_SIDESHIFT_URL='https://sideshift.ai/api/v2/' + interface TokenDetails { - network: { - contractAddress: string; - }; + [network: string]: { + contractAddress: string; decimals: number; - depositOffline?: string[] | boolean; - settleOffline?: string[] | boolean; + } } -interface Coin { +export interface Coin { networks: string[]; coin: string; name: string; hasMemo: boolean; fixedOnly: string[] | boolean; variableOnly: string[] | boolean; - tokenDetails: TokenDetails[]; + tokenDetails: TokenDetails; + depositOffline?: string[] | boolean; + settleOffline?: string[] | boolean; } export async function getCoins(): Promise { - const res = await fetch('https://sideshift.ai/api/v2/coins') + const res = await fetch(BASE_SIDESHIFT_URL + 'coins') const data = await res.json() - return data as Coin[] + + const coins = data as Coin[] + coins.sort((a, b) => a.name < b.name ? -1 : 1) + return coins +} + +export interface Pair { + min: string; + max: string; + rate: string; + depositCoin: string; + settleCoin: string; + depositNetwork: string; + settleNetwork: string; +} + +async function getPair(from: string, to: string): Promise { + const res = await fetch(BASE_SIDESHIFT_URL + `pair/${from}/${to}`) + const data = await res.json() + return data as Pair +} + +export async function getXecPair(from: string): Promise { + return getPair(from, 'ecash-xec') } From 8b60c4ff5fb06d9191f69e5bcea0396ea51bb401 Mon Sep 17 00:00:00 2001 From: chedieck Date: Tue, 2 Jul 2024 11:35:04 -0300 Subject: [PATCH 3/4] feat: disallow selecting same coin --- react/lib/components/Widget/Widget.tsx | 2 +- react/lib/util/sideshift.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/react/lib/components/Widget/Widget.tsx b/react/lib/components/Widget/Widget.tsx index 342df880..677bba3c 100644 --- a/react/lib/components/Widget/Widget.tsx +++ b/react/lib/components/Widget/Widget.tsx @@ -215,7 +215,7 @@ export const Widget: React.FunctionComponent = props => { useEffect(() => { (async () => { if (useSideshift === true) { - const coins = await getCoins() + const coins = await getCoins(addressType) setCoins(coins) } })() diff --git a/react/lib/util/sideshift.ts b/react/lib/util/sideshift.ts index bf0dc1b2..691aa53c 100644 --- a/react/lib/util/sideshift.ts +++ b/react/lib/util/sideshift.ts @@ -19,13 +19,13 @@ export interface Coin { settleOffline?: string[] | boolean; } -export async function getCoins(): Promise { +export async function getCoins(originCoin: string): Promise { const res = await fetch(BASE_SIDESHIFT_URL + 'coins') const data = await res.json() const coins = data as Coin[] coins.sort((a, b) => a.name < b.name ? -1 : 1) - return coins + return coins.filter(coin => coin.coin.toLowerCase() !== originCoin.toLowerCase()) } export interface Pair { From 322d04779ffcaac6b3dcc8631b0e426ec67dae09 Mon Sep 17 00:00:00 2001 From: chedieck Date: Tue, 2 Jul 2024 11:35:14 -0300 Subject: [PATCH 4/4] fix: trade with addressType --- react/lib/components/Widget/Widget.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/react/lib/components/Widget/Widget.tsx b/react/lib/components/Widget/Widget.tsx index 677bba3c..ac0f7a73 100644 --- a/react/lib/components/Widget/Widget.tsx +++ b/react/lib/components/Widget/Widget.tsx @@ -687,7 +687,7 @@ export const Widget: React.FunctionComponent = props => { /> - {setUseSideshift(false)}}>Trade with xec + {setUseSideshift(false)}}>Trade with {addressType} // END: Sideshift region @@ -775,7 +775,7 @@ export const Widget: React.FunctionComponent = props => { )} - {setUseSideshift(true)}}>Don't have any XEC? + {setUseSideshift(true)}}>Don't have any {addressType}?