diff --git a/packages/demo-wallet-client/src/components/AnchorQuotesModal.tsx b/packages/demo-wallet-client/src/components/AnchorQuotesModal.tsx index a81a808b..a13ededb 100644 --- a/packages/demo-wallet-client/src/components/AnchorQuotesModal.tsx +++ b/packages/demo-wallet-client/src/components/AnchorQuotesModal.tsx @@ -42,6 +42,8 @@ export const AnchorQuotesModal = ({ const [quoteAsset, setQuoteAsset] = useState(); const [assetBuyDeliveryMethod, setAssetBuyDeliveryMethod] = useState(); + const [assetSellDeliveryMethod, setAssetSellDeliveryMethod] = + useState(); const [assetCountryCode, setAssetCountryCode] = useState(); const [assetPrice, setAssetPrice] = useState(); @@ -52,47 +54,87 @@ export const AnchorQuotesModal = ({ return new BigNumber(amount).div(rate).toFixed(2); }; - // Exclude sell asset from quote assets - const renderAssets = data.sellAsset - ? data.assets.filter((a) => a.asset !== data.sellAsset) + const userAsset = context === "sep31" ? data.sellAsset : data.buyAsset; + + // Exclude selected asset from quote assets + const renderAssets = userAsset + ? data.assets.filter((a) => a.asset !== userAsset) : []; const handleGetAssetRates = () => { - if (data.serverUrl && data.sellAsset && data.sellAmount) { - dispatch( - fetchSep38QuotesPricesAction({ - token, - anchorQuoteServerUrl: data.serverUrl, - options: { - sellAsset: data.sellAsset, - sellAmount: data.sellAmount, - buyDeliveryMethod: assetBuyDeliveryMethod, - countryCode: assetCountryCode, - }, - }), - ); + if (context === "sep31") { + if (data.serverUrl && data.sellAsset && data.amount) { + dispatch( + fetchSep38QuotesPricesAction({ + token, + anchorQuoteServerUrl: data.serverUrl, + options: { + sellAsset: data.sellAsset, + sellAmount: data.amount, + buyDeliveryMethod: assetBuyDeliveryMethod, + sellDeliveryMethod: assetSellDeliveryMethod, + countryCode: assetCountryCode, + }, + }), + ); + } + } else if (context === "sep6") { + if (data.serverUrl && data.amount && quoteAsset?.asset) { + dispatch( + fetchSep38QuotesPricesAction({ + token, + anchorQuoteServerUrl: data.serverUrl, + options: { + sellAsset: quoteAsset.asset, + sellAmount: data.amount, + buyDeliveryMethod: assetBuyDeliveryMethod, + sellDeliveryMethod: assetSellDeliveryMethod, + countryCode: assetCountryCode, + }, + }), + ); + } } }; const handleGetQuote = () => { - if ( - data.serverUrl && - data.sellAsset && - data.sellAmount && - quoteAsset?.asset - ) { - dispatch( - postSep38QuoteAction({ - token, - anchorQuoteServerUrl: data.serverUrl, - sell_asset: data.sellAsset, - buy_asset: quoteAsset.asset, - sell_amount: data.sellAmount, - buy_delivery_method: assetBuyDeliveryMethod, - country_code: assetCountryCode, - context, - }), - ); + if (context === "sep31") { + if ( + data.serverUrl && + data.sellAsset && + data.amount && + quoteAsset?.asset + ) { + dispatch( + postSep38QuoteAction({ + token, + anchorQuoteServerUrl: data.serverUrl, + sell_asset: data.sellAsset, + buy_asset: quoteAsset.asset, + sell_amount: data.amount, + buy_delivery_method: assetBuyDeliveryMethod, + sell_delivery_method: assetSellDeliveryMethod, + country_code: assetCountryCode, + context, + }), + ); + } + } else if (context === "sep6") { + if (data.serverUrl && data.buyAsset && data.amount && quoteAsset?.asset) { + dispatch( + postSep38QuoteAction({ + token, + anchorQuoteServerUrl: data.serverUrl, + sell_asset: quoteAsset.asset, + buy_asset: data.buyAsset, + sell_amount: data.amount, + buy_delivery_method: assetBuyDeliveryMethod, + sell_delivery_method: assetSellDeliveryMethod, + country_code: assetCountryCode, + context, + }), + ); + } } }; @@ -178,37 +220,41 @@ export const AnchorQuotesModal = ({ } if (data.prices?.length > 0 && quoteAsset?.asset) { - const sellAssetCode = data.sellAsset?.split(":")[1]; + const sellAssetCode = + context === "sep31" + ? data.sellAsset?.split(":")[1] + : data.buyAsset?.split(":")[1]; const buyAssetCode = quoteAsset?.asset.split(":")[1]; + const prices = + context === "sep31" + ? data.prices + : data.prices.filter((p) => p.asset === data.buyAsset); + return ( <>

Rates (not final)

- {data.prices - .filter((p) => p.asset === quoteAsset.asset) - .map((p) => ( - { - setAssetPrice(p.price); - }} - /> - ))} + {prices.map((p) => ( + { + setAssetPrice(p.price); + }} + /> + ))}
- {data.sellAmount && assetPrice ? ( + {data.amount && assetPrice ? (
{`Estimated total of ${calculateTotal( - data.sellAmount, + data.amount, assetPrice, - )} ${buyAssetCode} for ${ - data.sellAmount - } ${sellAssetCode}`}
+ )} ${buyAssetCode} for ${data.amount} ${sellAssetCode}`} ) : null}
@@ -240,7 +286,8 @@ export const AnchorQuotesModal = ({ asset: a.asset, }); setAssetCountryCode(""); - setAssetBuyDeliveryMethod(""); + setAssetBuyDeliveryMethod(undefined); + setAssetSellDeliveryMethod(undefined); }} checked={a.asset === quoteAsset?.asset} /> @@ -295,6 +342,31 @@ export const AnchorQuotesModal = ({ ) : null} + + {a.sell_delivery_methods && + a.sell_delivery_methods.length > 0 ? ( + <> +
Sell delivery methods
+
+ {a.sell_delivery_methods?.map((b) => ( + { + setAssetSellDeliveryMethod(b.name); + }} + checked={ + a.asset === quoteAsset?.asset && + b.name === assetSellDeliveryMethod + } + /> + ))} +
+ + ) : null} ))} diff --git a/packages/demo-wallet-client/src/components/Sep31Send.tsx b/packages/demo-wallet-client/src/components/Sep31Send.tsx index 423fce41..4213e093 100644 --- a/packages/demo-wallet-client/src/components/Sep31Send.tsx +++ b/packages/demo-wallet-client/src/components/Sep31Send.tsx @@ -22,7 +22,7 @@ import { setStatusAction, } from "ducks/sep31Send"; import { - fetchSep38QuotesInfoAction, + fetchSep38QuotesSep31InfoAction, resetSep38QuotesAction, } from "ducks/sep38Quotes"; @@ -134,10 +134,10 @@ export const Sep31Send = () => { const { assetCode, assetIssuer } = sep31Send.data; dispatch( - fetchSep38QuotesInfoAction({ + fetchSep38QuotesSep31InfoAction({ anchorQuoteServerUrl: sep31Send.data?.anchorQuoteServer, sellAsset: `stellar:${assetCode}:${assetIssuer}`, - sellAmount: formData.amount.amount, + amount: formData.amount.amount, }), ); dispatch(setStatusAction(ActionStatus.ANCHOR_QUOTES)); diff --git a/packages/demo-wallet-client/src/components/Sep6/Sep6Deposit.tsx b/packages/demo-wallet-client/src/components/Sep6/Sep6Deposit.tsx index 12cfd0d8..09a17b63 100644 --- a/packages/demo-wallet-client/src/components/Sep6/Sep6Deposit.tsx +++ b/packages/demo-wallet-client/src/components/Sep6/Sep6Deposit.tsx @@ -23,7 +23,7 @@ import { submitSep6DepositWithQuotesFields, } from "ducks/sep6DepositAsset"; import { - fetchSep38QuotesInfoAction, + fetchSep38QuotesSep6InfoAction, resetSep38QuotesAction, } from "ducks/sep38Quotes"; import { useRedux } from "hooks/useRedux"; @@ -175,10 +175,10 @@ export const Sep6Deposit = () => { const { assetCode, assetIssuer } = sep6DepositAsset.data; dispatch( - fetchSep38QuotesInfoAction({ + fetchSep38QuotesSep6InfoAction({ anchorQuoteServerUrl: sep6DepositAsset.data?.anchorQuoteServer, - sellAsset: `stellar:${assetCode}:${assetIssuer}`, - sellAmount: formData.amount, + buyAsset: `stellar:${assetCode}:${assetIssuer}`, + amount: formData.amount, }), ); dispatch(setStatusAction(ActionStatus.ANCHOR_QUOTES)); @@ -201,8 +201,8 @@ export const Sep6Deposit = () => { ...formData, amount: formData.amount, quoteId, - destinationAsset: sellAsset, - sourceAsset: buyAsset, + destinationAsset: buyAsset, + sourceAsset: sellAsset, }), ); }; diff --git a/packages/demo-wallet-client/src/ducks/sep38Quotes.ts b/packages/demo-wallet-client/src/ducks/sep38Quotes.ts index e810f93a..0de3f08b 100644 --- a/packages/demo-wallet-client/src/ducks/sep38Quotes.ts +++ b/packages/demo-wallet-client/src/ducks/sep38Quotes.ts @@ -19,35 +19,71 @@ import { Sep38QuotesInitialState, } from "types/types"; -export const fetchSep38QuotesInfoAction = createAsyncThunk< +export const fetchSep38QuotesSep31InfoAction = createAsyncThunk< { assets: AnchorQuoteAsset[]; sellAsset: string; - sellAmount: string; + amount: string; serverUrl: string | undefined; }, { anchorQuoteServerUrl: string | undefined; sellAsset: string; - sellAmount: string; + amount: string; }, { rejectValue: RejectMessage; state: RootState } >( - "sep38Quotes/fetchSep38QuotesInfoAction", - async ( - { anchorQuoteServerUrl, sellAsset, sellAmount }, - { rejectWithValue }, - ) => { + "sep38Quotes/fetchSep38QuotesSep31InfoAction", + async ({ anchorQuoteServerUrl, sellAsset, amount }, { rejectWithValue }) => { try { - const result = await getInfo(anchorQuoteServerUrl, { - sell_asset: sellAsset, - sell_amount: sellAmount, + const result = await getInfo({ + context: "sep31", + anchorQuoteServerUrl, + options: { sell_amount: amount, sell_asset: sellAsset! }, }); return { assets: result.assets, sellAsset, - sellAmount, + amount, + serverUrl: anchorQuoteServerUrl, + }; + } catch (error) { + const errorMessage = getErrorMessage(error); + + log.error({ + title: errorMessage, + }); + return rejectWithValue({ + errorString: errorMessage, + }); + } + }, +); + +export const fetchSep38QuotesSep6InfoAction = createAsyncThunk< + { + assets: AnchorQuoteAsset[]; + buyAsset: string; + amount: string; + serverUrl: string | undefined; + }, + { + anchorQuoteServerUrl: string | undefined; + buyAsset: string; + amount: string; + }, + { rejectValue: RejectMessage; state: RootState } +>( + "sep38Quotes/fetchSep38QuotesSep6InfoAction", + async ({ anchorQuoteServerUrl, buyAsset, amount }, { rejectWithValue }) => { + try { + const result = await getInfo({ context: "sep6", anchorQuoteServerUrl }); + + return { + assets: result.assets, + buyAsset, + amount, serverUrl: anchorQuoteServerUrl, }; } catch (error) { @@ -170,7 +206,8 @@ const initialState: Sep38QuotesInitialState = { data: { serverUrl: undefined, sellAsset: undefined, - sellAmount: undefined, + buyAsset: undefined, + amount: undefined, assets: [], prices: [], quote: undefined, @@ -187,26 +224,60 @@ const sep38QuotesSlice = createSlice({ }, extraReducers: (builder) => { builder.addCase( - fetchSep38QuotesInfoAction.pending, + fetchSep38QuotesSep31InfoAction.pending, (state = initialState) => { state.status = ActionStatus.PENDING; state.data = { ...state.data, prices: [], quote: undefined }; }, ); - builder.addCase(fetchSep38QuotesInfoAction.fulfilled, (state, action) => { - state.data = { - ...state.data, - assets: action.payload.assets, - sellAsset: action.payload.sellAsset, - sellAmount: action.payload.sellAmount, - serverUrl: action.payload.serverUrl, - }; - state.status = ActionStatus.SUCCESS; - }); - builder.addCase(fetchSep38QuotesInfoAction.rejected, (state, action) => { - state.errorString = action.payload?.errorString; - state.status = ActionStatus.ERROR; - }); + builder.addCase( + fetchSep38QuotesSep31InfoAction.fulfilled, + (state, action) => { + state.data = { + ...state.data, + assets: action.payload.assets, + sellAsset: action.payload.sellAsset, + amount: action.payload.amount, + serverUrl: action.payload.serverUrl, + }; + state.status = ActionStatus.SUCCESS; + }, + ); + builder.addCase( + fetchSep38QuotesSep31InfoAction.rejected, + (state, action) => { + state.errorString = action.payload?.errorString; + state.status = ActionStatus.ERROR; + }, + ); + + builder.addCase( + fetchSep38QuotesSep6InfoAction.pending, + (state = initialState) => { + state.status = ActionStatus.PENDING; + state.data = { ...state.data, prices: [], quote: undefined }; + }, + ); + builder.addCase( + fetchSep38QuotesSep6InfoAction.fulfilled, + (state, action) => { + state.data = { + ...state.data, + assets: action.payload.assets, + buyAsset: action.payload.buyAsset, + amount: action.payload.amount, + serverUrl: action.payload.serverUrl, + }; + state.status = ActionStatus.SUCCESS; + }, + ); + builder.addCase( + fetchSep38QuotesSep6InfoAction.rejected, + (state, action) => { + state.errorString = action.payload?.errorString; + state.status = ActionStatus.ERROR; + }, + ); builder.addCase( fetchSep38QuotesPricesAction.pending, diff --git a/packages/demo-wallet-client/src/types/types.ts b/packages/demo-wallet-client/src/types/types.ts index a93e07b2..346db10e 100644 --- a/packages/demo-wallet-client/src/types/types.ts +++ b/packages/demo-wallet-client/src/types/types.ts @@ -331,7 +331,8 @@ export interface Sep38QuotesInitialState { data: { serverUrl: string | undefined; sellAsset: string | undefined; - sellAmount: string | undefined; + buyAsset: string | undefined; + amount: string | undefined; assets: AnchorQuoteAsset[]; prices: AnchorBuyAsset[]; quote: AnchorQuote | undefined; diff --git a/packages/demo-wallet-shared/methods/sep38Quotes/getInfo.ts b/packages/demo-wallet-shared/methods/sep38Quotes/getInfo.ts index 388d8697..96755c31 100644 --- a/packages/demo-wallet-shared/methods/sep38Quotes/getInfo.ts +++ b/packages/demo-wallet-shared/methods/sep38Quotes/getInfo.ts @@ -1,35 +1,50 @@ import { log } from "../../helpers/log"; import { AnchorQuoteAsset } from "../../types/types"; -export const getInfo = async ( - anchorQuoteServerUrl: string | undefined, - options?: { - /* eslint-disable camelcase */ - sell_asset: string; - sell_amount: string; - sell_delivery_method?: string; - buy_delivery_method?: string; - country_code?: string; - /* eslint-enable camelcase */ - }, -): Promise<{ assets: AnchorQuoteAsset[] }> => { +type Sep38GetInfo = ( + | { + context: "sep6"; + options?: undefined; + } + | { + context: "sep31"; + options?: { + /* eslint-disable camelcase */ + sell_asset: string; + sell_amount: string; + sell_delivery_method?: string; + buy_delivery_method?: string; + country_code?: string; + /* eslint-enable camelcase */ + }; + } +) & { + anchorQuoteServerUrl: string | undefined; +}; + +export const getInfo = async ({ + context, + anchorQuoteServerUrl, + options, +}: Sep38GetInfo): Promise<{ assets: AnchorQuoteAsset[] }> => { if (!anchorQuoteServerUrl) { throw new Error("Anchor quote server URL is required"); } - const params = options - ? Object.entries(options).reduce((res: any, [key, value]) => { - if (value) { - res[key] = value; - } + const params = + context === "sep31" && options + ? Object.entries(options).reduce((res: any, [key, value]) => { + if (value) { + res[key] = value; + } - return res; - }, {}) - : undefined; + return res; + }, {}) + : undefined; const urlParams = params ? new URLSearchParams(params) : undefined; log.instruction({ - title: `Checking \`/info\` endpoint for \`${anchorQuoteServerUrl}\` to get anchor quotes details`, + title: `Checking \`/info\` endpoint for \`${anchorQuoteServerUrl}\` to get anchor quotes details for ${context.toLocaleUpperCase()}`, ...(params ? { body: params } : {}), }); @@ -39,7 +54,9 @@ export const getInfo = async ( }); const result = await fetch( - `${anchorQuoteServerUrl}/info?${urlParams?.toString()}`, + `${anchorQuoteServerUrl}/info${ + urlParams ? `?${urlParams?.toString()}` : "" + }`, ); const resultJson = await result.json();