From dc133d5b0aa6fa4cbb58c7af0dc31dc013033357 Mon Sep 17 00:00:00 2001 From: William Swanson Date: Wed, 31 Jan 2024 09:58:57 -0800 Subject: [PATCH 1/4] Alphabetize plugin names --- src/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4f1b9202..a0c629fb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,17 +24,17 @@ const plugins = { changenow: makeChangeNowPlugin, exolix: makeExolixPlugin, godex: makeGodexPlugin, + letsexchange: makeLetsExchangePlugin, lifi: makeLifiPlugin, sideshift: makeSideshiftPlugin, spookySwap: makeSpookySwapPlugin, - tombSwap: makeTombSwapPlugin, swapuz: makeSwapuzPlugin, thorchain: makeThorchainPlugin, thorchainda: makeThorchainDaPlugin, + tombSwap: makeTombSwapPlugin, transfer: makeTransferPlugin, velodrome: makeVelodromePlugin, - xrpdex: makeXrpDexPlugin, - letsexchange: makeLetsExchangePlugin + xrpdex: makeXrpDexPlugin } declare global { From 1aac5cef3eb695d91b892f9d398427dc95a05265 Mon Sep 17 00:00:00 2001 From: William Swanson Date: Tue, 30 Jan 2024 16:53:17 -0800 Subject: [PATCH 2/4] Move centralized swap plugins to a folder --- src/index.ts | 14 +++++++------- src/swap/{ => central}/changehero.ts | 6 +++--- src/swap/{ => central}/changenow.ts | 6 +++--- src/swap/{ => central}/exolix.ts | 6 +++--- src/swap/{ => central}/godex.ts | 6 +++--- src/swap/{ => central}/letsexchange.ts | 6 +++--- src/swap/{ => central}/sideshift.ts | 6 +++--- src/swap/{ => central}/swapuz.ts | 8 ++++---- 8 files changed, 29 insertions(+), 29 deletions(-) rename src/swap/{ => central}/changehero.ts (98%) rename src/swap/{ => central}/changenow.ts (98%) rename src/swap/{ => central}/exolix.ts (98%) rename src/swap/{ => central}/godex.ts (98%) rename src/swap/{ => central}/letsexchange.ts (98%) rename src/swap/{ => central}/sideshift.ts (98%) rename src/swap/{ => central}/swapuz.ts (98%) diff --git a/src/index.ts b/src/index.ts index a0c629fb..01599dc7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,8 +2,13 @@ import 'regenerator-runtime/runtime' import type { EdgeCorePlugins } from 'edge-core-js/types' -import { makeChangeHeroPlugin } from './swap/changehero' -import { makeChangeNowPlugin } from './swap/changenow' +import { makeChangeHeroPlugin } from './swap/central/changehero' +import { makeChangeNowPlugin } from './swap/central/changenow' +import { makeExolixPlugin } from './swap/central/exolix' +import { makeGodexPlugin } from './swap/central/godex' +import { makeLetsExchangePlugin } from './swap/central/letsexchange' +import { makeSideshiftPlugin } from './swap/central/sideshift' +import { makeSwapuzPlugin } from './swap/central/swapuz' import { makeLifiPlugin } from './swap/defi/lifi' import { makeThorchainPlugin } from './swap/defi/thorchain' import { makeThorchainDaPlugin } from './swap/defi/thorchainDa' @@ -11,11 +16,6 @@ import { makeSpookySwapPlugin } from './swap/defi/uni-v2-based/plugins/spookySwa import { makeTombSwapPlugin } from './swap/defi/uni-v2-based/plugins/tombSwap' import { makeVelodromePlugin } from './swap/defi/uni-v2-based/plugins/velodrome' import { makeXrpDexPlugin } from './swap/defi/xrpDex' -import { makeExolixPlugin } from './swap/exolix' -import { makeGodexPlugin } from './swap/godex' -import { makeLetsExchangePlugin } from './swap/letsexchange' -import { makeSideshiftPlugin } from './swap/sideshift' -import { makeSwapuzPlugin } from './swap/swapuz' import { makeTransferPlugin } from './swap/transfer' const plugins = { diff --git a/src/swap/changehero.ts b/src/swap/central/changehero.ts similarity index 98% rename from src/swap/changehero.ts rename to src/swap/central/changehero.ts index 664f179b..e381ecab 100644 --- a/src/swap/changehero.ts +++ b/src/swap/central/changehero.ts @@ -27,9 +27,9 @@ import { InvalidCurrencyCodes, makeSwapPluginQuote, SwapOrder -} from '../util/swapHelpers' -import { convertRequest, getAddress } from '../util/utils' -import { EdgeSwapRequestPlugin, StringMap } from './types' +} from '../../util/swapHelpers' +import { convertRequest, getAddress } from '../../util/utils' +import { EdgeSwapRequestPlugin, StringMap } from '../types' const pluginId = 'changehero' diff --git a/src/swap/changenow.ts b/src/swap/central/changenow.ts similarity index 98% rename from src/swap/changenow.ts rename to src/swap/central/changenow.ts index 7c601a8c..cb26298f 100644 --- a/src/swap/changenow.ts +++ b/src/swap/central/changenow.ts @@ -28,9 +28,9 @@ import { InvalidCurrencyCodes, makeSwapPluginQuote, SwapOrder -} from '../util/swapHelpers' -import { convertRequest, getAddress } from '../util/utils' -import { EdgeSwapRequestPlugin } from './types' +} from '../../util/swapHelpers' +import { convertRequest, getAddress } from '../../util/utils' +import { EdgeSwapRequestPlugin } from '../types' const pluginId = 'changenow' const swapInfo: EdgeSwapInfo = { diff --git a/src/swap/exolix.ts b/src/swap/central/exolix.ts similarity index 98% rename from src/swap/exolix.ts rename to src/swap/central/exolix.ts index 67b19a27..faa303a6 100644 --- a/src/swap/exolix.ts +++ b/src/swap/central/exolix.ts @@ -26,9 +26,9 @@ import { InvalidCurrencyCodes, makeSwapPluginQuote, SwapOrder -} from '../util/swapHelpers' -import { convertRequest, getAddress } from '../util/utils' -import { EdgeSwapRequestPlugin } from './types' +} from '../../util/swapHelpers' +import { convertRequest, getAddress } from '../../util/utils' +import { EdgeSwapRequestPlugin } from '../types' const pluginId = 'exolix' diff --git a/src/swap/godex.ts b/src/swap/central/godex.ts similarity index 98% rename from src/swap/godex.ts rename to src/swap/central/godex.ts index bd77b958..f7299a9d 100644 --- a/src/swap/godex.ts +++ b/src/swap/central/godex.ts @@ -27,9 +27,9 @@ import { InvalidCurrencyCodes, makeSwapPluginQuote, SwapOrder -} from '../util/swapHelpers' -import { convertRequest, getAddress } from '../util/utils' -import { asNumberString, EdgeSwapRequestPlugin } from './types' +} from '../../util/swapHelpers' +import { convertRequest, getAddress } from '../../util/utils' +import { asNumberString, EdgeSwapRequestPlugin } from '../types' const pluginId = 'godex' diff --git a/src/swap/letsexchange.ts b/src/swap/central/letsexchange.ts similarity index 98% rename from src/swap/letsexchange.ts rename to src/swap/central/letsexchange.ts index ee4b0d97..38b2714a 100644 --- a/src/swap/letsexchange.ts +++ b/src/swap/central/letsexchange.ts @@ -19,10 +19,10 @@ import { InvalidCurrencyCodes, makeSwapPluginQuote, SwapOrder -} from '../util/swapHelpers' -import { convertRequest, getAddress } from '../util/utils' +} from '../../util/swapHelpers' +import { convertRequest, getAddress } from '../../util/utils' +import { asNumberString, EdgeSwapRequestPlugin } from '../types' import { asOptionalBlank } from './changenow' -import { asNumberString, EdgeSwapRequestPlugin } from './types' const pluginId = 'letsexchange' diff --git a/src/swap/sideshift.ts b/src/swap/central/sideshift.ts similarity index 98% rename from src/swap/sideshift.ts rename to src/swap/central/sideshift.ts index 5b99a57b..6df3f9ca 100644 --- a/src/swap/sideshift.ts +++ b/src/swap/central/sideshift.ts @@ -22,9 +22,9 @@ import { InvalidCurrencyCodes, makeSwapPluginQuote, SwapOrder -} from '../util/swapHelpers' -import { convertRequest, getAddress } from '../util/utils' -import { EdgeSwapRequestPlugin } from './types' +} from '../../util/swapHelpers' +import { convertRequest, getAddress } from '../../util/utils' +import { EdgeSwapRequestPlugin } from '../types' // See https://help.sideshift.ai/en/articles/4559664-which-coins-and-tokens-are-listed for list of supported currencies const MAINNET_CODE_TRANSCRIPTION = { diff --git a/src/swap/swapuz.ts b/src/swap/central/swapuz.ts similarity index 98% rename from src/swap/swapuz.ts rename to src/swap/central/swapuz.ts index 75be9a99..9b8fbdb9 100644 --- a/src/swap/swapuz.ts +++ b/src/swap/central/swapuz.ts @@ -18,7 +18,7 @@ import { SwapCurrencyError } from 'edge-core-js/types' -import { div18 } from '../util/biggystringplus' +import { div18 } from '../../util/biggystringplus' import { checkInvalidCodes, ensureInFuture, @@ -28,9 +28,9 @@ import { isLikeKind, makeSwapPluginQuote, SwapOrder -} from '../util/swapHelpers' -import { convertRequest, getAddress } from '../util/utils' -import { EdgeSwapRequestPlugin } from './types' +} from '../../util/swapHelpers' +import { convertRequest, getAddress } from '../../util/utils' +import { EdgeSwapRequestPlugin } from '../types' const pluginId = 'swapuz' From eedf398e80ebe411c7ad8a5962e20b5de8db6b1e Mon Sep 17 00:00:00 2001 From: William Swanson Date: Wed, 31 Jan 2024 10:01:16 -0800 Subject: [PATCH 3/4] Add a code-splitting infrastructure --- src/util/makeSwapPlugin.ts | 64 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/util/makeSwapPlugin.ts diff --git a/src/util/makeSwapPlugin.ts b/src/util/makeSwapPlugin.ts new file mode 100644 index 00000000..54eb1243 --- /dev/null +++ b/src/util/makeSwapPlugin.ts @@ -0,0 +1,64 @@ +import { + EdgeCorePluginOptions, + EdgeSwapInfo, + EdgeSwapPlugin, + EdgeSwapQuote, + EdgeSwapRequest, + JsonObject +} from 'edge-core-js' + +type EdgeCorePluginFactory = (env: EdgeCorePluginOptions) => EdgeSwapPlugin + +export interface PluginEnvironment extends EdgeCorePluginOptions { + swapInfo: EdgeSwapInfo +} + +/** + * These methods involve expensive crypto libraries + * that we don't want to load unless we actually perform a swap. + */ +export interface InnerPlugin { + fetchSwapQuote: ( + env: PluginEnvironment, + request: EdgeSwapRequest, + userSettings: JsonObject | undefined, + opts: { promoCode?: string } + ) => Promise +} + +/** + * These methods involve cheap, static information, + * so we don't have to load any crypto libraries. + */ +export interface OuterPlugin { + swapInfo: EdgeSwapInfo + checkEnvironment?: () => void + getInnerPlugin: () => Promise +} + +export function makeSwapPlugin(template: OuterPlugin): EdgeCorePluginFactory { + const { swapInfo, checkEnvironment = () => {} } = template + + return (env: EdgeCorePluginOptions): EdgeSwapPlugin => { + const innerEnv = { ...env, swapInfo } + let pluginPromise: Promise | undefined + + return { + swapInfo, + async fetchSwapQuote(request, userSettings, opts) { + checkEnvironment() + if (pluginPromise == null) { + pluginPromise = template.getInnerPlugin() + } + const plugin = await pluginPromise + + return await plugin.fetchSwapQuote( + innerEnv, + request, + userSettings, + opts + ) + } + } + } +} From 98f06bb1bcccbf5e7110bcbb973ec36530b0be9b Mon Sep 17 00:00:00 2001 From: William Swanson Date: Wed, 31 Jan 2024 10:14:35 -0800 Subject: [PATCH 4/4] Split the XRP DEX plugin --- CHANGELOG.md | 2 + src/index.ts | 4 +- src/swap/defi/xrpDex.ts | 442 +++++++++++++++++++--------------------- src/swap/xrpDexInfo.ts | 22 ++ 4 files changed, 239 insertions(+), 231 deletions(-) create mode 100644 src/swap/xrpDexInfo.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 1498fb10..e9c56001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- fixed: Do not crash at load time if `BigInt` is not present. + ## 2.0.1 (2023-01-09) - fixed: Error when swapping from tokens diff --git a/src/index.ts b/src/index.ts index 01599dc7..62609f90 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,8 +15,8 @@ import { makeThorchainDaPlugin } from './swap/defi/thorchainDa' import { makeSpookySwapPlugin } from './swap/defi/uni-v2-based/plugins/spookySwap' import { makeTombSwapPlugin } from './swap/defi/uni-v2-based/plugins/tombSwap' import { makeVelodromePlugin } from './swap/defi/uni-v2-based/plugins/velodrome' -import { makeXrpDexPlugin } from './swap/defi/xrpDex' import { makeTransferPlugin } from './swap/transfer' +import { xrpdex } from './swap/xrpDexInfo' const plugins = { // Swap plugins: @@ -34,7 +34,7 @@ const plugins = { tombSwap: makeTombSwapPlugin, transfer: makeTransferPlugin, velodrome: makeVelodromePlugin, - xrpdex: makeXrpDexPlugin + xrpdex } declare global { diff --git a/src/swap/defi/xrpDex.ts b/src/swap/defi/xrpDex.ts index 9abb60c7..d4988978 100644 --- a/src/swap/defi/xrpDex.ts +++ b/src/swap/defi/xrpDex.ts @@ -1,9 +1,6 @@ import { gt, round, sub } from 'biggystring' import { asArray, asNumber, asObject, asOptional, asString } from 'cleaners' import { - EdgeCorePluginOptions, - EdgeSwapInfo, - EdgeSwapPlugin, EdgeSwapQuote, EdgeSwapRequest, EdgeTxActionSwap, @@ -13,6 +10,7 @@ import { } from 'edge-core-js/types' import { Client } from 'xrpl' +import { PluginEnvironment } from '../../util/makeSwapPlugin' import { makeSwapPluginQuote, SwapOrder } from '../../util/swapHelpers' import { convertRequest, @@ -24,15 +22,7 @@ import { import { EdgeSwapRequestPlugin, MakeTxParams } from '../types' import { getBuyQuote, getSellQuote } from './xrp/xrpDexHelpers' -const pluginId = 'xrpdex' -const swapInfo: EdgeSwapInfo = { - pluginId, - isDex: true, - displayName: 'XRP DEX', - supportEmail: 'support@edge.app' -} - -export const asInitOptions = asObject({ +const asInitOptions = asObject({ appId: asOptional(asString, 'edge') }) @@ -49,7 +39,7 @@ const DUMMY_XRP_ADDRESS = 'rfuESo7eHUnvebxgaFjfYxfwXhM2uBPAj3' // too many decimals (not sure how much), we for now cap swap amounts to 6 decimals const MAX_DECIMALS_MULTIPLIER = 1000000 -export const asExchangeInfo = asObject({ +const asExchangeInfo = asObject({ swap: asObject({ plugins: asObject({ xrpdex: asObject({ @@ -65,247 +55,241 @@ type ExchangeInfo = ReturnType let exchangeInfo: ExchangeInfo | undefined let exchangeInfoLastUpdate: number = 0 -export function makeXrpDexPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin { - const { io, log } = opts +const fetchSwapQuoteInner = async ( + env: PluginEnvironment, + request: EdgeSwapRequestPlugin +): Promise => { + const { io, log, swapInfo } = env const { fetchCors = io.fetch } = io - const { appId } = asInitOptions(opts.initOptions) - - const fetchSwapQuoteInner = async ( - request: EdgeSwapRequestPlugin - ): Promise => { - const { - fromCurrencyCode, - fromTokenId, - toCurrencyCode, - toTokenId, - nativeAmount, - fromWallet, - toWallet, - quoteFor - } = request - - // Only support ripple wallets - if ( - fromWallet.currencyInfo.pluginId !== 'ripple' || - toWallet.currencyInfo.pluginId !== 'ripple' - ) { - throw new SwapCurrencyError(swapInfo, request) - } - - // Source and dest wallet must be the same - if (fromWallet.id !== toWallet.id) { - throw new Error('XRP DEX must use same wallet for source and destination') - } + const { appId } = asInitOptions(env.initOptions) + + const { + fromCurrencyCode, + fromTokenId, + toCurrencyCode, + toTokenId, + nativeAmount, + fromWallet, + toWallet, + quoteFor + } = request + + // Only support ripple wallets + if ( + fromWallet.currencyInfo.pluginId !== 'ripple' || + toWallet.currencyInfo.pluginId !== 'ripple' + ) { + throw new SwapCurrencyError(swapInfo, request) + } - // Do not support transfer between same assets - if (request.fromTokenId === request.toTokenId) { - throw new SwapCurrencyError(swapInfo, request) - } + // Source and dest wallet must be the same + if (fromWallet.id !== toWallet.id) { + throw new Error('XRP DEX must use same wallet for source and destination') + } - const rippleServers: string[] = RIPPLE_SERVERS_DEFAULT - const volatilitySpread: number = VOLATILITY_SPREAD_DEFAULT - - const fromIssuer = - fromTokenId != null ? fromTokenId.split('-')[1] : undefined - const toIssuer = toTokenId != null ? toTokenId.split('-')[1] : undefined - const fromCurrency = - fromTokenId != null ? fromTokenId.split('-')[0] : fromCurrencyCode - const toCurrency = - toTokenId != null ? toTokenId.split('-')[0] : toCurrencyCode - - // Grab addresses: - const toAddress = await getAddress(toWallet) - - const now = Date.now() - if ( - now - exchangeInfoLastUpdate > EXCHANGE_INFO_UPDATE_FREQ_MS || - exchangeInfo == null - ) { - try { - const exchangeInfoResponse = await promiseWithTimeout( - fetchInfo(fetchCors, `v1/exchangeInfo/${appId}`) - ) - - if (exchangeInfoResponse.ok === true) { - exchangeInfo = asExchangeInfo(await exchangeInfoResponse.json()) - exchangeInfoLastUpdate = now - } else { - // Error is ok. We just use defaults - log('Error getting info server exchangeInfo. Using defaults...') - } - } catch (e: any) { - log( - 'Error getting info server exchangeInfo. Using defaults...', - e.message - ) - } - } + // Do not support transfer between same assets + if (request.fromTokenId === request.toTokenId) { + throw new SwapCurrencyError(swapInfo, request) + } - // ---------------------------------------------------------------- - // Get a quote - // ---------------------------------------------------------------- + const rippleServers: string[] = RIPPLE_SERVERS_DEFAULT + const volatilitySpread: number = VOLATILITY_SPREAD_DEFAULT - const client = await getXrplConnectedClient(rippleServers) + const fromIssuer = fromTokenId != null ? fromTokenId.split('-')[1] : undefined + const toIssuer = toTokenId != null ? toTokenId.split('-')[1] : undefined + const fromCurrency = + fromTokenId != null ? fromTokenId.split('-')[0] : fromCurrencyCode + const toCurrency = + toTokenId != null ? toTokenId.split('-')[0] : toCurrencyCode - let quote: number - let fromNativeAmount: string - let toNativeAmount: string - const taker = await getAddress(fromWallet) + // Grab addresses: + const toAddress = await getAddress(toWallet) - if (quoteFor === 'from') { - fromNativeAmount = nativeAmount - const exchangeAmount = await fromWallet.nativeToDenomination( - nativeAmount, - fromCurrencyCode + const now = Date.now() + if ( + now - exchangeInfoLastUpdate > EXCHANGE_INFO_UPDATE_FREQ_MS || + exchangeInfo == null + ) { + try { + const exchangeInfoResponse = await promiseWithTimeout( + fetchInfo(fetchCors, `v1/exchangeInfo/${appId}`) ) - quote = await getSellQuote( - { - weSell: { - currency: fromCurrency, - issuer: fromIssuer - }, - weSellAmountOfTokens: Number(exchangeAmount), - counterCurrency: { - currency: toCurrency, - issuer: toIssuer - }, - taker - }, - { client, showLogs: false } - ) - quote = quote * (1 - volatilitySpread) - quote = - Math.round(quote * MAX_DECIMALS_MULTIPLIER) / MAX_DECIMALS_MULTIPLIER - toNativeAmount = await toWallet.denominationToNative( - String(quote), - toCurrencyCode - ) - toNativeAmount = round(toNativeAmount, 0) - } else { - toNativeAmount = nativeAmount - const exchangeAmount = await toWallet.nativeToDenomination( - nativeAmount, - toCurrencyCode + if (exchangeInfoResponse.ok === true) { + exchangeInfo = asExchangeInfo(await exchangeInfoResponse.json()) + exchangeInfoLastUpdate = now + } else { + // Error is ok. We just use defaults + log('Error getting info server exchangeInfo. Using defaults...') + } + } catch (e: any) { + log( + 'Error getting info server exchangeInfo. Using defaults...', + e.message ) + } + } - quote = await getBuyQuote( - { - weWant: { - currency: toCurrency, - issuer: toIssuer - }, - weWantAmountOfToken: Number(exchangeAmount), - counterCurrency: { - currency: fromCurrency, - issuer: fromIssuer - }, - taker - }, - { client, showLogs: false } - ) - quote = quote * (1 + volatilitySpread) - quote = - Math.round(quote * MAX_DECIMALS_MULTIPLIER) / MAX_DECIMALS_MULTIPLIER + // ---------------------------------------------------------------- + // Get a quote + // ---------------------------------------------------------------- - fromNativeAmount = await fromWallet.denominationToNative( - String(quote), - fromCurrencyCode - ) - fromNativeAmount = round(fromNativeAmount, 0) - } + const client = await getXrplConnectedClient(rippleServers) - const timestampNow = Date.now() - const expiration = timestampNow / 1000 + DEX_MAX_FULLFILLMENT_TIME_S - - const savedAction: EdgeTxActionSwap = { - actionType: 'swap', - swapInfo, - isEstimate: false, - toAsset: { - pluginId: toWallet.currencyInfo.pluginId, - tokenId: toTokenId, - nativeAmount: toNativeAmount + let quote: number + let fromNativeAmount: string + let toNativeAmount: string + const taker = await getAddress(fromWallet) + + if (quoteFor === 'from') { + fromNativeAmount = nativeAmount + const exchangeAmount = await fromWallet.nativeToDenomination( + nativeAmount, + fromCurrencyCode + ) + + quote = await getSellQuote( + { + weSell: { + currency: fromCurrency, + issuer: fromIssuer + }, + weSellAmountOfTokens: Number(exchangeAmount), + counterCurrency: { + currency: toCurrency, + issuer: toIssuer + }, + taker }, - fromAsset: { - pluginId: fromWallet.currencyInfo.pluginId, - tokenId: fromTokenId, - nativeAmount: fromNativeAmount + { client, showLogs: false } + ) + quote = quote * (1 - volatilitySpread) + quote = + Math.round(quote * MAX_DECIMALS_MULTIPLIER) / MAX_DECIMALS_MULTIPLIER + toNativeAmount = await toWallet.denominationToNative( + String(quote), + toCurrencyCode + ) + toNativeAmount = round(toNativeAmount, 0) + } else { + toNativeAmount = nativeAmount + const exchangeAmount = await toWallet.nativeToDenomination( + nativeAmount, + toCurrencyCode + ) + + quote = await getBuyQuote( + { + weWant: { + currency: toCurrency, + issuer: toIssuer + }, + weWantAmountOfToken: Number(exchangeAmount), + counterCurrency: { + currency: fromCurrency, + issuer: fromIssuer + }, + taker }, - payoutAddress: toAddress, - payoutWalletId: toWallet.id - } + { client, showLogs: false } + ) + quote = quote * (1 + volatilitySpread) + quote = + Math.round(quote * MAX_DECIMALS_MULTIPLIER) / MAX_DECIMALS_MULTIPLIER + + fromNativeAmount = await fromWallet.denominationToNative( + String(quote), + fromCurrencyCode + ) + fromNativeAmount = round(fromNativeAmount, 0) + } - const makeTxParams: MakeTxParams = { - type: 'MakeTxDexSwap', - assetAction: { assetActionType: 'swap' }, - savedAction, - fromTokenId, - fromNativeAmount, - toTokenId, - toNativeAmount, - expiration - } + const timestampNow = Date.now() + const expiration = timestampNow / 1000 + DEX_MAX_FULLFILLMENT_TIME_S - await client.disconnect() + const savedAction: EdgeTxActionSwap = { + actionType: 'swap', + swapInfo, + isEstimate: false, + toAsset: { + pluginId: toWallet.currencyInfo.pluginId, + tokenId: toTokenId, + nativeAmount: toNativeAmount + }, + fromAsset: { + pluginId: fromWallet.currencyInfo.pluginId, + tokenId: fromTokenId, + nativeAmount: fromNativeAmount + }, + payoutAddress: toAddress, + payoutWalletId: toWallet.id + } - return { - canBePartial: true, - maxFulfillmentSeconds: DEX_MAX_FULLFILLMENT_TIME_S, - request, - makeTxParams, - swapInfo, - fromNativeAmount, - expirationDate: new Date(Date.now() + EXPIRATION_MS) - } + const makeTxParams: MakeTxParams = { + type: 'MakeTxDexSwap', + assetAction: { assetActionType: 'swap' }, + savedAction, + fromTokenId, + fromNativeAmount, + toTokenId, + toNativeAmount, + expiration } - const out: EdgeSwapPlugin = { - swapInfo, + await client.disconnect() - async fetchSwapQuote( - req: EdgeSwapRequest, - userSettings: JsonObject | undefined, - opts: { promoCode?: string } - ): Promise { - const request = convertRequest(req) - const { fromTokenId, fromWallet, quoteFor } = request - - // Get the balance of the wallet minus reserve - const maxSpendable = await fromWallet.getMaxSpendable({ - tokenId: request.fromTokenId, - spendTargets: [ - { - publicAddress: DUMMY_XRP_ADDRESS - } - ] - }) + return { + canBePartial: true, + maxFulfillmentSeconds: DEX_MAX_FULLFILLMENT_TIME_S, + request, + makeTxParams, + swapInfo, + fromNativeAmount, + expirationDate: new Date(Date.now() + EXPIRATION_MS) + } +} - let swapOrder: SwapOrder - if (quoteFor === 'max') { - request.quoteFor = 'from' - request.nativeAmount = fromWallet.balanceMap.get(fromTokenId) ?? '0' - swapOrder = await fetchSwapQuoteInner(request) - if (fromTokenId == null) { - // We can swap all mainnet coins minus the expected fee - const quote = await makeSwapPluginQuote(swapOrder) - const swapFee = quote.networkFee.nativeAmount - - request.nativeAmount = sub(maxSpendable, swapFee) - return await this.fetchSwapQuote(request, userSettings, opts) - } - } else { - swapOrder = await fetchSwapQuoteInner(request) - if (gt(swapOrder.fromNativeAmount, maxSpendable)) { - throw new InsufficientFundsError({ - tokenId: swapOrder.request.fromTokenId - }) - } +export async function fetchSwapQuote( + env: PluginEnvironment, + req: EdgeSwapRequest, + userSettings: JsonObject | undefined, + opts: { promoCode?: string } +): Promise { + const request = convertRequest(req) + const { fromTokenId, fromWallet, quoteFor } = request + + // Get the balance of the wallet minus reserve + const maxSpendable = await fromWallet.getMaxSpendable({ + tokenId: request.fromTokenId, + spendTargets: [ + { + publicAddress: DUMMY_XRP_ADDRESS } - return await makeSwapPluginQuote(swapOrder) + ] + }) + + let swapOrder: SwapOrder + if (quoteFor === 'max') { + request.quoteFor = 'from' + request.nativeAmount = fromWallet.balanceMap.get(fromTokenId) ?? '0' + swapOrder = await fetchSwapQuoteInner(env, request) + if (fromTokenId == null) { + // We can swap all mainnet coins minus the expected fee + const quote = await makeSwapPluginQuote(swapOrder) + const swapFee = quote.networkFee.nativeAmount + + request.nativeAmount = sub(maxSpendable, swapFee) + return await fetchSwapQuote(env, request, userSettings, opts) + } + } else { + swapOrder = await fetchSwapQuoteInner(env, request) + if (gt(swapOrder.fromNativeAmount, maxSpendable)) { + throw new InsufficientFundsError({ + tokenId: swapOrder.request.fromTokenId + }) } } - return out + return await makeSwapPluginQuote(swapOrder) } const getXrplConnectedClient = async (servers: string[]): Promise => { diff --git a/src/swap/xrpDexInfo.ts b/src/swap/xrpDexInfo.ts new file mode 100644 index 00000000..080ad585 --- /dev/null +++ b/src/swap/xrpDexInfo.ts @@ -0,0 +1,22 @@ +import { EdgeSwapInfo } from 'edge-core-js' + +import { makeSwapPlugin } from '../util/makeSwapPlugin' + +const swapInfo: EdgeSwapInfo = { + pluginId: 'xrpdex', + isDex: true, + displayName: 'XRP DEX', + supportEmail: 'support@edge.app' +} + +export const xrpdex = makeSwapPlugin({ + swapInfo, + + checkEnvironment() { + if (typeof BigInt === 'undefined') { + throw new Error('XRP DEX requires BigInt support') + } + }, + + getInnerPlugin: async () => await import('./defi/xrpDex') +})